the count in the file can be far more rectangles than the rest of the record could hold Signed-off-by: Caolán McNamara <caolan.mcnamara@collabora.com> Change-Id: I220e5eb3c5a99f7a7d6114aaa429234fa4a31906 Reviewed-on: https://gerrit.collaboraoffice.com/c/online/+/12357 Reviewed-by: Noel Grandin <noel.grandin@collabora.com> Tested-by: Jenkins CPCI <releng@collaboraoffice.com> (cherry picked from commit f92f34584421532774a762ec98688db7196badb0) Reviewed-on: https://gerrit.libreoffice.org/c/core/+/211408 Tested-by: Jenkins Reviewed-by: Xisco Fauli <xiscofauli@libreoffice.org>
2931 lines
151 KiB
C++
2931 lines
151 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 "emfpcustomlinecap.hxx"
|
|
#include "emfphelperdata.hxx"
|
|
#include "emfpbrush.hxx"
|
|
#include "emfppen.hxx"
|
|
#include "emfppath.hxx"
|
|
#include "emfpregion.hxx"
|
|
#include "emfpimage.hxx"
|
|
#include "emfpimageattributes.hxx"
|
|
#include "emfpfont.hxx"
|
|
#include "emfpstringformat.hxx"
|
|
#include <wmfemfhelper.hxx>
|
|
#include <drawinglayer/attribute/fillgraphicattribute.hxx>
|
|
#include <drawinglayer/attribute/fontattribute.hxx>
|
|
#include <drawinglayer/primitive2d/PolyPolygonColorPrimitive2D.hxx>
|
|
#include <drawinglayer/primitive2d/PolyPolygonGraphicPrimitive2D.hxx>
|
|
#include <drawinglayer/primitive2d/PolyPolygonRGBAPrimitive2D.hxx>
|
|
#include <drawinglayer/primitive2d/PolyPolygonStrokePrimitive2D.hxx>
|
|
#include <drawinglayer/primitive2d/PolygonStrokeArrowPrimitive2D.hxx>
|
|
#include <drawinglayer/primitive2d/bitmapprimitive2d.hxx>
|
|
#include <drawinglayer/primitive2d/maskprimitive2d.hxx>
|
|
#include <drawinglayer/primitive2d/metafileprimitive2d.hxx>
|
|
#include <drawinglayer/primitive2d/svggradientprimitive2d.hxx>
|
|
#include <drawinglayer/primitive2d/textdecoratedprimitive2d.hxx>
|
|
#include <drawinglayer/primitive2d/textlayoutdevice.hxx>
|
|
#include <drawinglayer/primitive2d/textprimitive2d.hxx>
|
|
#include <drawinglayer/primitive2d/transformprimitive2d.hxx>
|
|
#include <drawinglayer/primitive2d/unifiedtransparenceprimitive2d.hxx>
|
|
#include <basegfx/color/bcolor.hxx>
|
|
#include <basegfx/color/bcolormodifier.hxx>
|
|
#include <basegfx/matrix/b2dhommatrixtools.hxx>
|
|
#include <basegfx/polygon/b2dpolygonclipper.hxx>
|
|
#include <basegfx/polygon/b2dpolygontools.hxx>
|
|
#include <basegfx/polygon/b2dpolypolygoncutter.hxx>
|
|
#include <basegfx/vector/b2dsize.hxx>
|
|
#include <sal/log.hxx>
|
|
#include <vcl/alpha.hxx>
|
|
#include <vcl/svapp.hxx>
|
|
#include <vcl/settings.hxx>
|
|
#include <vcl/BitmapWriteAccess.hxx>
|
|
#include <i18nlangtag/languagetag.hxx>
|
|
|
|
#include <algorithm>
|
|
|
|
namespace emfplushelper
|
|
{
|
|
|
|
enum
|
|
{
|
|
WrapModeTile = 0x00000000,
|
|
WrapModeTileFlipX = 0x00000001,
|
|
WrapModeTileFlipY = 0x00000002,
|
|
WrapModeTileFlipXY = 0x00000003,
|
|
WrapModeClamp = 0x00000004
|
|
};
|
|
|
|
const char* emfTypeToName(sal_uInt16 type)
|
|
{
|
|
switch (type)
|
|
{
|
|
case EmfPlusRecordTypeHeader: return "EmfPlusRecordTypeHeader";
|
|
case EmfPlusRecordTypeEndOfFile: return "EmfPlusRecordTypeEndOfFile";
|
|
case EmfPlusRecordTypeComment: return "EmfPlusRecordTypeComment";
|
|
case EmfPlusRecordTypeGetDC: return "EmfPlusRecordTypeGetDC";
|
|
case EmfPlusRecordTypeObject: return "EmfPlusRecordTypeObject";
|
|
case EmfPlusRecordTypeFillRects: return "EmfPlusRecordTypeFillRects";
|
|
case EmfPlusRecordTypeDrawRects: return "EmfPlusRecordTypeDrawRects";
|
|
case EmfPlusRecordTypeFillPolygon: return "EmfPlusRecordTypeFillPolygon";
|
|
case EmfPlusRecordTypeDrawLines: return "EmfPlusRecordTypeDrawLines";
|
|
case EmfPlusRecordTypeFillClosedCurve: return "EmfPlusRecordTypeFillClosedCurve";
|
|
case EmfPlusRecordTypeDrawClosedCurve: return "EmfPlusRecordTypeDrawClosedCurve";
|
|
case EmfPlusRecordTypeDrawCurve: return "EmfPlusRecordTypeDrawCurve";
|
|
case EmfPlusRecordTypeFillEllipse: return "EmfPlusRecordTypeFillEllipse";
|
|
case EmfPlusRecordTypeDrawEllipse: return "EmfPlusRecordTypeDrawEllipse";
|
|
case EmfPlusRecordTypeFillPie: return "EmfPlusRecordTypeFillPie";
|
|
case EmfPlusRecordTypeDrawPie: return "EmfPlusRecordTypeDrawPie";
|
|
case EmfPlusRecordTypeDrawArc: return "EmfPlusRecordTypeDrawArc";
|
|
case EmfPlusRecordTypeFillRegion: return "EmfPlusRecordTypeFillRegion";
|
|
case EmfPlusRecordTypeFillPath: return "EmfPlusRecordTypeFillPath";
|
|
case EmfPlusRecordTypeDrawPath: return "EmfPlusRecordTypeDrawPath";
|
|
case EmfPlusRecordTypeDrawBeziers: return "EmfPlusRecordTypeDrawBeziers";
|
|
case EmfPlusRecordTypeDrawImage: return "EmfPlusRecordTypeDrawImage";
|
|
case EmfPlusRecordTypeDrawImagePoints: return "EmfPlusRecordTypeDrawImagePoints";
|
|
case EmfPlusRecordTypeDrawString: return "EmfPlusRecordTypeDrawString";
|
|
case EmfPlusRecordTypeSetRenderingOrigin: return "EmfPlusRecordTypeSetRenderingOrigin";
|
|
case EmfPlusRecordTypeSetAntiAliasMode: return "EmfPlusRecordTypeSetAntiAliasMode";
|
|
case EmfPlusRecordTypeSetTextRenderingHint: return "EmfPlusRecordTypeSetTextRenderingHint";
|
|
case EmfPlusRecordTypeSetTextContrast: return "EmfPlusRecordTypeSetTextContrast";
|
|
case EmfPlusRecordTypeSetInterpolationMode: return "EmfPlusRecordTypeSetInterpolationMode";
|
|
case EmfPlusRecordTypeSetPixelOffsetMode: return "EmfPlusRecordTypeSetPixelOffsetMode";
|
|
case EmfPlusRecordTypeSetCompositingQuality: return "EmfPlusRecordTypeSetCompositingQuality";
|
|
case EmfPlusRecordTypeSave: return "EmfPlusRecordTypeSave";
|
|
case EmfPlusRecordTypeRestore: return "EmfPlusRecordTypeRestore";
|
|
case EmfPlusRecordTypeBeginContainer: return "EmfPlusRecordTypeBeginContainer";
|
|
case EmfPlusRecordTypeBeginContainerNoParams: return "EmfPlusRecordTypeBeginContainerNoParams";
|
|
case EmfPlusRecordTypeEndContainer: return "EmfPlusRecordTypeEndContainer";
|
|
case EmfPlusRecordTypeSetWorldTransform: return "EmfPlusRecordTypeSetWorldTransform";
|
|
case EmfPlusRecordTypeResetWorldTransform: return "EmfPlusRecordTypeResetWorldTransform";
|
|
case EmfPlusRecordTypeMultiplyWorldTransform: return "EmfPlusRecordTypeMultiplyWorldTransform";
|
|
case EmfPlusRecordTypeTranslateWorldTransform: return "EmfPlusRecordTypeTranslateWorldTransform";
|
|
case EmfPlusRecordTypeScaleWorldTransform: return "EmfPlusRecordTypeScaleWorldTransform";
|
|
case EmfPlusRecordTypeSetPageTransform: return "EmfPlusRecordTypeSetPageTransform";
|
|
case EmfPlusRecordTypeResetClip: return "EmfPlusRecordTypeResetClip";
|
|
case EmfPlusRecordTypeSetClipRect: return "EmfPlusRecordTypeSetClipRect";
|
|
case EmfPlusRecordTypeSetClipPath: return "EmfPlusRecordTypeSetClipPath";
|
|
case EmfPlusRecordTypeSetClipRegion: return "EmfPlusRecordTypeSetClipRegion";
|
|
case EmfPlusRecordTypeOffsetClip: return "EmfPlusRecordTypeOffsetClip";
|
|
case EmfPlusRecordTypeDrawDriverString: return "EmfPlusRecordTypeDrawDriverString";
|
|
}
|
|
return "";
|
|
}
|
|
|
|
static OUString emfObjectToName(sal_uInt16 type)
|
|
{
|
|
switch (type)
|
|
{
|
|
case EmfPlusObjectTypeBrush: return u"EmfPlusObjectTypeBrush"_ustr;
|
|
case EmfPlusObjectTypePen: return u"EmfPlusObjectTypePen"_ustr;
|
|
case EmfPlusObjectTypePath: return u"EmfPlusObjectTypePath"_ustr;
|
|
case EmfPlusObjectTypeRegion: return u"EmfPlusObjectTypeRegion"_ustr;
|
|
case EmfPlusObjectTypeImage: return u"EmfPlusObjectTypeImage"_ustr;
|
|
case EmfPlusObjectTypeFont: return u"EmfPlusObjectTypeFont"_ustr;
|
|
case EmfPlusObjectTypeStringFormat: return u"EmfPlusObjectTypeStringFormat"_ustr;
|
|
case EmfPlusObjectTypeImageAttributes: return u"EmfPlusObjectTypeImageAttributes"_ustr;
|
|
case EmfPlusObjectTypeCustomLineCap: return u"EmfPlusObjectTypeCustomLineCap"_ustr;
|
|
}
|
|
return u""_ustr;
|
|
}
|
|
|
|
static OUString PixelOffsetModeToString(sal_uInt16 nPixelOffset)
|
|
{
|
|
switch (nPixelOffset)
|
|
{
|
|
case PixelOffsetMode::PixelOffsetModeDefault: return u"PixelOffsetModeDefault"_ustr;
|
|
case PixelOffsetMode::PixelOffsetModeHighSpeed: return u"PixelOffsetModeHighSpeed"_ustr;
|
|
case PixelOffsetMode::PixelOffsetModeHighQuality: return u"PixelOffsetModeHighQuality"_ustr;
|
|
case PixelOffsetMode::PixelOffsetModeNone: return u"PixelOffsetModeNone"_ustr;
|
|
case PixelOffsetMode::PixelOffsetModeHalf: return u"PixelOffsetModeHalf"_ustr;
|
|
}
|
|
return u""_ustr;
|
|
}
|
|
|
|
static OUString SmoothingModeToString(sal_uInt16 nSmoothMode)
|
|
{
|
|
switch (nSmoothMode)
|
|
{
|
|
case SmoothingMode::SmoothingModeDefault: return u"SmoothingModeDefault"_ustr;
|
|
case SmoothingMode::SmoothingModeHighSpeed: return u"SmoothModeHighSpeed"_ustr;
|
|
case SmoothingMode::SmoothingModeHighQuality: return u"SmoothingModeHighQuality"_ustr;
|
|
case SmoothingMode::SmoothingModeNone: return u"SmoothingModeNone"_ustr;
|
|
case SmoothingMode::SmoothingModeAntiAlias8x4: return u"SmoothingModeAntiAlias8x4"_ustr;
|
|
case SmoothingMode::SmoothingModeAntiAlias8x8: return u"SmoothingModeAntiAlias8x8"_ustr;
|
|
}
|
|
return u""_ustr;
|
|
}
|
|
|
|
static OUString TextRenderingHintToString(sal_uInt16 nHint)
|
|
{
|
|
switch (nHint)
|
|
{
|
|
case TextRenderingHint::TextRenderingHintSystemDefault: return u"TextRenderingHintSystemDefault"_ustr;
|
|
case TextRenderingHint::TextRenderingHintSingleBitPerPixelGridFit: return u"TextRenderingHintSingleBitPerPixelGridFit"_ustr;
|
|
case TextRenderingHint::TextRenderingHintSingleBitPerPixel: return u"TextRenderingHintSingleBitPerPixel"_ustr;
|
|
case TextRenderingHint::TextRenderingHintAntialiasGridFit: return u"TextRenderingHintAntialiasGridFit"_ustr;
|
|
case TextRenderingHint::TextRenderingHintAntialias: return u"TextRenderingHintAntialias"_ustr;
|
|
case TextRenderingHint::TextRenderingHintClearTypeGridFit: return u"TextRenderingHintClearTypeGridFit"_ustr;
|
|
}
|
|
return u""_ustr;
|
|
}
|
|
|
|
static OUString InterpolationModeToString(sal_uInt16 nMode)
|
|
{
|
|
switch (nMode)
|
|
{
|
|
case InterpolationMode::InterpolationModeDefault: return u"InterpolationModeDefault"_ustr;
|
|
case InterpolationMode::InterpolationModeLowQuality: return u"InterpolationModeLowQuality"_ustr;
|
|
case InterpolationMode::InterpolationModeHighQuality: return u"InterpolationModeHighQuality"_ustr;
|
|
case InterpolationMode::InterpolationModeBilinear: return u"InterpolationModeBilinear"_ustr;
|
|
case InterpolationMode::InterpolationModeBicubic: return u"InterpolationModeBicubic"_ustr;
|
|
case InterpolationMode::InterpolationModeNearestNeighbor: return u"InterpolationModeNearestNeighbor"_ustr;
|
|
case InterpolationMode::InterpolationModeHighQualityBilinear: return u"InterpolationModeHighQualityBilinear"_ustr;
|
|
case InterpolationMode::InterpolationModeHighQualityBicubic: return u"InterpolationModeHighQualityBicubic"_ustr;
|
|
}
|
|
return u""_ustr;
|
|
}
|
|
|
|
OUString UnitTypeToString(sal_uInt16 nType)
|
|
{
|
|
switch (nType)
|
|
{
|
|
case UnitTypeWorld: return u"UnitTypeWorld"_ustr;
|
|
case UnitTypeDisplay: return u"UnitTypeDisplay"_ustr;
|
|
case UnitTypePixel: return u"UnitTypePixel"_ustr;
|
|
case UnitTypePoint: return u"UnitTypePoint"_ustr;
|
|
case UnitTypeInch: return u"UnitTypeInch"_ustr;
|
|
case UnitTypeDocument: return u"UnitTypeDocument"_ustr;
|
|
case UnitTypeMillimeter: return u"UnitTypeMillimeter"_ustr;
|
|
}
|
|
return u""_ustr;
|
|
}
|
|
|
|
static bool IsBrush(sal_uInt16 flags)
|
|
{
|
|
return (!((flags >> 15) & 0x0001));
|
|
}
|
|
|
|
static OUString BrushIDToString(sal_uInt16 flags, sal_uInt32 brushid)
|
|
{
|
|
if (IsBrush(flags))
|
|
return "EmfPlusBrush ID: " + OUString::number(brushid);
|
|
else
|
|
return "ARGB: 0x" + OUString::number(brushid, 16);
|
|
}
|
|
|
|
EMFPObject::~EMFPObject()
|
|
{
|
|
}
|
|
|
|
double EmfPlusHelperData::unitToPixel(double n, sal_uInt32 aUnitType, Direction d)
|
|
{
|
|
switch (static_cast<UnitType>(aUnitType))
|
|
{
|
|
case UnitTypePixel:
|
|
return n;
|
|
|
|
case UnitTypePoint:
|
|
return o3tl::convert(n, o3tl::Length::pt, o3tl::Length::in) * DPI(d);
|
|
|
|
case UnitTypeInch:
|
|
return n * DPI(d);
|
|
|
|
case UnitTypeMillimeter:
|
|
return o3tl::convert(n, o3tl::Length::mm, o3tl::Length::in) * DPI(d);
|
|
|
|
case UnitTypeDocument:
|
|
return n * DPI(d) / 300.0;
|
|
|
|
case UnitTypeWorld:
|
|
case UnitTypeDisplay:
|
|
SAL_WARN("drawinglayer.emf", "EMF+\t Converting to World/Display.");
|
|
return n;
|
|
|
|
default:
|
|
SAL_WARN("drawinglayer.emf", "EMF+\tTODO Unimplemented support of Unit Type: 0x" << std::hex << aUnitType);
|
|
return n;
|
|
}
|
|
}
|
|
|
|
void EmfPlusHelperData::processObjectRecord(SvMemoryStream& rObjectStream, sal_uInt16 flags, sal_uInt32 dataSize, bool bUseWholeStream)
|
|
{
|
|
sal_uInt16 objecttype = flags & 0x7f00;
|
|
sal_uInt16 index = flags & 0xff;
|
|
SAL_INFO("drawinglayer.emf", "EMF+ Object: " << emfObjectToName(objecttype) << " (0x" << objecttype << ")");
|
|
SAL_INFO("drawinglayer.emf", "EMF+\tObject slot: " << index);
|
|
SAL_INFO("drawinglayer.emf", "EMF+\tFlags: " << (flags & 0xff00));
|
|
|
|
switch (objecttype)
|
|
{
|
|
case EmfPlusObjectTypeBrush:
|
|
{
|
|
EMFPBrush *brush = new EMFPBrush();
|
|
maEMFPObjects[index].reset(brush);
|
|
brush->Read(rObjectStream, *this, dataSize, bUseWholeStream);
|
|
break;
|
|
}
|
|
case EmfPlusObjectTypePen:
|
|
{
|
|
EMFPPen *pen = new EMFPPen();
|
|
maEMFPObjects[index].reset(pen);
|
|
pen->Read(rObjectStream, *this, dataSize, bUseWholeStream);
|
|
pen->penWidth = unitToPixel(pen->penWidth, pen->penUnit, Direction::horizontal);
|
|
break;
|
|
}
|
|
case EmfPlusObjectTypePath:
|
|
{
|
|
sal_uInt32 aVersion(0), aPathPointCount(0), aPathPointFlags(0);
|
|
|
|
rObjectStream.ReadUInt32(aVersion).ReadUInt32(aPathPointCount).ReadUInt32(aPathPointFlags);
|
|
SAL_INFO("drawinglayer.emf", "EMF+\t\tVersion: 0x" << std::hex << aVersion);
|
|
SAL_INFO("drawinglayer.emf", "EMF+\t\tNumber of points: " << std::dec << aPathPointCount);
|
|
SAL_INFO("drawinglayer.emf", "EMF+\t\tPath point flags: 0x" << std::hex << aPathPointFlags << std::dec);
|
|
EMFPPath *path = new EMFPPath(aPathPointCount);
|
|
maEMFPObjects[index].reset(path);
|
|
path->Read(rObjectStream, aPathPointFlags);
|
|
break;
|
|
}
|
|
case EmfPlusObjectTypeRegion:
|
|
{
|
|
EMFPRegion *region = new EMFPRegion();
|
|
maEMFPObjects[index].reset(region);
|
|
region->ReadRegion(rObjectStream, *this);
|
|
break;
|
|
}
|
|
case EmfPlusObjectTypeImage:
|
|
{
|
|
EMFPImage *image = new EMFPImage;
|
|
maEMFPObjects[index].reset(image);
|
|
image->type = 0;
|
|
image->width = 0;
|
|
image->height = 0;
|
|
image->stride = 0;
|
|
image->pixelFormat = 0;
|
|
image->Read(rObjectStream, dataSize, bUseWholeStream);
|
|
break;
|
|
}
|
|
case EmfPlusObjectTypeFont:
|
|
{
|
|
EMFPFont *font = new EMFPFont;
|
|
maEMFPObjects[index].reset(font);
|
|
font->emSize = 0;
|
|
font->sizeUnit = 0;
|
|
font->fontFlags = 0;
|
|
font->Read(rObjectStream);
|
|
// tdf#113624 Convert unit to Pixels
|
|
font->emSize = unitToPixel(font->emSize, font->sizeUnit, Direction::horizontal);
|
|
|
|
break;
|
|
}
|
|
case EmfPlusObjectTypeStringFormat:
|
|
{
|
|
EMFPStringFormat *stringFormat = new EMFPStringFormat();
|
|
maEMFPObjects[index].reset(stringFormat);
|
|
stringFormat->Read(rObjectStream);
|
|
break;
|
|
}
|
|
case EmfPlusObjectTypeImageAttributes:
|
|
{
|
|
EMFPImageAttributes *imageAttributes = new EMFPImageAttributes();
|
|
maEMFPObjects[index].reset(imageAttributes);
|
|
imageAttributes->Read(rObjectStream);
|
|
break;
|
|
}
|
|
case EmfPlusObjectTypeCustomLineCap:
|
|
{
|
|
SAL_WARN("drawinglayer.emf", "EMF+\t TODO Object type 'custom line cap' not yet implemented");
|
|
break;
|
|
}
|
|
default:
|
|
{
|
|
SAL_WARN("drawinglayer.emf", "EMF+\t TODO Object unhandled flags: 0x" << std::hex << (flags & 0xff00) << std::dec);
|
|
}
|
|
}
|
|
}
|
|
|
|
void EmfPlusHelperData::ReadPoint(SvStream& s, float& x, float& y, sal_uInt32 flags)
|
|
{
|
|
if (flags & 0x800)
|
|
{
|
|
// specifies a location in the coordinate space that is relative to
|
|
// the location specified by the previous element in the array. In the case of the first element in
|
|
// PointData, a previous location at coordinates (0,0) is assumed.
|
|
SAL_WARN("drawinglayer.emf", "EMF+\t\t TODO Relative coordinates bit detected. Implement parse EMFPlusPointR");
|
|
}
|
|
|
|
if (flags & 0x4000)
|
|
{
|
|
sal_Int16 ix(0), iy(0);
|
|
|
|
s.ReadInt16(ix).ReadInt16(iy);
|
|
|
|
x = ix;
|
|
y = iy;
|
|
}
|
|
else
|
|
{
|
|
s.ReadFloat(x).ReadFloat(y);
|
|
}
|
|
}
|
|
|
|
void EmfPlusHelperData::ReadRectangle(SvStream& s, float& x, float& y, float &width, float& height, bool bCompressed)
|
|
{
|
|
if (bCompressed)
|
|
{
|
|
sal_Int16 ix(0), iy(0), iw(0), ih(0);
|
|
|
|
s.ReadInt16(ix).ReadInt16(iy).ReadInt16(iw).ReadInt16(ih);
|
|
|
|
x = ix;
|
|
y = iy;
|
|
width = iw;
|
|
height = ih;
|
|
}
|
|
else
|
|
{
|
|
s.ReadFloat(x).ReadFloat(y).ReadFloat(width).ReadFloat(height);
|
|
}
|
|
}
|
|
|
|
bool EmfPlusHelperData::readXForm(SvStream& rIn, basegfx::B2DHomMatrix& rTarget)
|
|
{
|
|
rTarget.identity();
|
|
|
|
if (sizeof(float) != 4)
|
|
{
|
|
OSL_FAIL("EnhWMFReader::sizeof( float ) != 4");
|
|
return false;
|
|
}
|
|
else
|
|
{
|
|
float eM11(0.0);
|
|
float eM12(0.0);
|
|
float eM21(0.0);
|
|
float eM22(0.0);
|
|
float eDx(0.0);
|
|
float eDy(0.0);
|
|
rIn.ReadFloat(eM11).ReadFloat(eM12).ReadFloat(eM21).ReadFloat(eM22).ReadFloat(eDx).ReadFloat(eDy);
|
|
rTarget = basegfx::B2DHomMatrix(
|
|
eM11, eM21, eDx,
|
|
eM12, eM22, eDy);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void EmfPlusHelperData::mappingChanged()
|
|
{
|
|
if (mnPixX == 0 || mnPixY == 0)
|
|
{
|
|
SAL_WARN("drawinglayer.emf", "dimensions in pixels is 0");
|
|
return;
|
|
}
|
|
// Call when mnMmX/mnMmY/mnPixX/mnPixY/mnFrameLeft/mnFrameTop/maWorldTransform/ changes.
|
|
// Currently not used are mnHDPI/mnVDPI/mnFrameRight/mnFrameBottom. *If* these should
|
|
// be used in the future, this method will need to be called.
|
|
//
|
|
// Re-calculate maMapTransform to contain the complete former transformation so that
|
|
// it can be applied by a single matrix multiplication or be added to an encapsulated
|
|
// primitive later
|
|
//
|
|
// To evtl. correct and see where this came from, please compare with the implementations
|
|
// of EmfPlusHelperData::MapToDevice and EmfPlusHelperData::Map* in prev versions
|
|
maMapTransform = maWorldTransform;
|
|
maMapTransform.scale(mfPageScaleX, mfPageScaleY);
|
|
maMapTransform *= basegfx::utils::createScaleTranslateB2DHomMatrix(100.0 * mnMmX / mnPixX, 100.0 * mnMmY / mnPixY,
|
|
-double(mnFrameLeft), -double(mnFrameTop));
|
|
maMapTransform *= maBaseTransform;
|
|
|
|
// Used only for performance optimization, to do not calculate it every line draw
|
|
mdExtractedXScale = std::hypot(maMapTransform.a(), maMapTransform.b());
|
|
mdExtractedYScale = std::hypot(maMapTransform.c(), maMapTransform.d());
|
|
}
|
|
|
|
::basegfx::B2DPoint EmfPlusHelperData::Map(double ix, double iy) const
|
|
{
|
|
// map in one step using complete MapTransform (see mappingChanged)
|
|
return maMapTransform * ::basegfx::B2DPoint(ix, iy);
|
|
}
|
|
|
|
Color EmfPlusHelperData::EMFPGetBrushColorOrARGBColor(const sal_uInt16 flags, const sal_uInt32 brushIndexOrColor) const {
|
|
Color color;
|
|
if (flags & 0x8000) // we use a color
|
|
{
|
|
color = Color(ColorAlpha, (brushIndexOrColor >> 24), (brushIndexOrColor >> 16) & 0xff,
|
|
(brushIndexOrColor >> 8) & 0xff, brushIndexOrColor & 0xff);
|
|
}
|
|
else // we use a brush
|
|
{
|
|
const EMFPBrush* brush = dynamic_cast<EMFPBrush*>(maEMFPObjects[brushIndexOrColor & 0xff].get());
|
|
if (brush)
|
|
{
|
|
color = brush->GetColor();
|
|
if (brush->type != BrushTypeSolidColor)
|
|
SAL_WARN("drawinglayer.emf", "EMF+\t\t TODO Brush other than solid color is not supported");
|
|
}
|
|
}
|
|
return color;
|
|
}
|
|
|
|
void EmfPlusHelperData::GraphicStatePush(GraphicStateMap& map, sal_Int32 index)
|
|
{
|
|
GraphicStateMap::iterator iter = map.find( index );
|
|
|
|
if ( iter != map.end() )
|
|
{
|
|
map.erase( iter );
|
|
SAL_INFO("drawinglayer.emf", "EMF+\t\tStack index: " << index << " found and erased");
|
|
}
|
|
|
|
wmfemfhelper::PropertyHolder state = mrPropertyHolders.Current();
|
|
// tdf#112500 We need to save world transform somehow, during graphic state push
|
|
state.setTransformation(maWorldTransform);
|
|
map[ index ] = std::move(state);
|
|
}
|
|
|
|
void EmfPlusHelperData::GraphicStatePop(GraphicStateMap& map, sal_Int32 index)
|
|
{
|
|
GraphicStateMap::iterator iter = map.find(index);
|
|
|
|
if (iter != map.end())
|
|
{
|
|
wmfemfhelper::PropertyHolder state = iter->second;
|
|
|
|
maWorldTransform = state.getTransformation();
|
|
if (state.getClipPolyPolygonActive())
|
|
{
|
|
SAL_INFO("drawinglayer.emf",
|
|
"EMF+\t Restore clipping region to saved in index: " << index);
|
|
wmfemfhelper::HandleNewClipRegion(state.getClipPolyPolygon(), mrTargetHolders,
|
|
mrPropertyHolders);
|
|
}
|
|
else
|
|
{
|
|
SAL_INFO("drawinglayer.emf", "EMF+\t Disable clipping");
|
|
wmfemfhelper::HandleNewClipRegion(::basegfx::B2DPolyPolygon(), mrTargetHolders,
|
|
mrPropertyHolders);
|
|
}
|
|
mappingChanged();
|
|
SAL_INFO("drawinglayer.emf",
|
|
"EMF+\t\tStack index: " << index
|
|
<< " found, maWorldTransform: " << maWorldTransform);
|
|
}
|
|
}
|
|
|
|
drawinglayer::attribute::LineStartEndAttribute
|
|
EmfPlusHelperData::CreateLineEnd(const sal_Int32 aCap, const float aPenWidth) const
|
|
{
|
|
const double pw = mdExtractedYScale * aPenWidth;
|
|
if (aCap == LineCapTypeSquare)
|
|
{
|
|
basegfx::B2DPolygon aCapPolygon(
|
|
{ {-1.0, -1.0}, {1.0, -1.0}, {1.0, 1.0}, {-1.0, 1.0} });
|
|
aCapPolygon.setClosed(true);
|
|
return drawinglayer::attribute::LineStartEndAttribute(
|
|
pw, basegfx::B2DPolyPolygon(aCapPolygon), true);
|
|
}
|
|
else if (aCap == LineCapTypeRound)
|
|
{
|
|
basegfx::B2DPolygon aCapPolygon(
|
|
{ {-1.0, 1.0}, {1.0, 1.0}, {1.0, 0.0}, {0.9236, -0.3827},
|
|
{0.7071, -0.7071}, {0.3827, -0.9236}, {0.0, -1.0}, {-0.3827, -0.9236},
|
|
{-0.7071, -0.7071}, {-0.9236, -0.3827}, {-1.0, 0.0} });
|
|
aCapPolygon.setClosed(true);
|
|
return drawinglayer::attribute::LineStartEndAttribute(
|
|
pw, basegfx::B2DPolyPolygon(aCapPolygon), true);
|
|
}
|
|
else if (aCap == LineCapTypeTriangle)
|
|
{
|
|
basegfx::B2DPolygon aCapPolygon(
|
|
{ {-1.0, 1.0}, {1.0, 1.0}, {1.0, 0.0}, {0.0, -1.0}, {-1.0, 0.0} });
|
|
aCapPolygon.setClosed(true);
|
|
return drawinglayer::attribute::LineStartEndAttribute(
|
|
pw, basegfx::B2DPolyPolygon(aCapPolygon), true);
|
|
}
|
|
else if (aCap == LineCapTypeSquareAnchor)
|
|
{
|
|
basegfx::B2DPolygon aCapPolygon(
|
|
{ {-1.0, -1.0}, {1.0, -1.0}, {1.0, 1.0}, {-1.0, 1.0} });
|
|
aCapPolygon.setClosed(true);
|
|
return drawinglayer::attribute::LineStartEndAttribute(
|
|
1.5 * pw, basegfx::B2DPolyPolygon(aCapPolygon), true);
|
|
}
|
|
else if (aCap == LineCapTypeRoundAnchor)
|
|
{
|
|
const basegfx::B2DPolygon aCapPolygon
|
|
= ::basegfx::utils::createPolygonFromEllipse(::basegfx::B2DPoint(0.0, 0.0), 1.0, 1.0);
|
|
return drawinglayer::attribute::LineStartEndAttribute(
|
|
2.0 * pw, basegfx::B2DPolyPolygon(aCapPolygon), true);
|
|
}
|
|
else if (aCap == LineCapTypeDiamondAnchor)
|
|
{
|
|
basegfx::B2DPolygon aCapPolygon({ {0.0, -1.0}, {1.0, 0.0}, {0.5, 0.5},
|
|
{0.5, 1.0}, {-0.5, 1.0}, {-0.5, 0.5},
|
|
{-1.0, 0.0} });
|
|
aCapPolygon.setClosed(true);
|
|
return drawinglayer::attribute::LineStartEndAttribute(
|
|
2.0 * pw, basegfx::B2DPolyPolygon(aCapPolygon), true);
|
|
}
|
|
else if (aCap == LineCapTypeArrowAnchor)
|
|
{
|
|
basegfx::B2DPolygon aCapPolygon({ {0.0, -1.0}, {1.0, 1.0}, {-1.0, 1.0} });
|
|
aCapPolygon.setClosed(true);
|
|
return drawinglayer::attribute::LineStartEndAttribute(
|
|
2.0 * pw, basegfx::B2DPolyPolygon(aCapPolygon), true);
|
|
}
|
|
return drawinglayer::attribute::LineStartEndAttribute();
|
|
}
|
|
|
|
void EmfPlusHelperData::EMFPPlusDrawPolygon(const ::basegfx::B2DPolyPolygon& polygon,
|
|
sal_uInt32 penIndex)
|
|
{
|
|
const EMFPPen* pen = dynamic_cast<EMFPPen*>(maEMFPObjects[penIndex & 0xff].get());
|
|
SAL_WARN_IF(!pen, "drawinglayer.emf", "emf+ missing pen");
|
|
|
|
if (!(pen && polygon.count()))
|
|
return;
|
|
|
|
const double transformedPenWidth = mdExtractedYScale * pen->penWidth;
|
|
drawinglayer::attribute::LineAttribute lineAttribute(
|
|
pen->GetColor().getBColor(), transformedPenWidth, pen->maLineJoin,
|
|
css::drawing::LineCap_BUTT, //TODO implement PenDataDashedLineCap support here
|
|
pen->fMiterMinimumAngle);
|
|
|
|
drawinglayer::attribute::LineStartEndAttribute aStart;
|
|
if (pen->penDataFlags & EmfPlusPenDataStartCap)
|
|
{
|
|
if ((pen->penDataFlags & EmfPlusPenDataCustomStartCap) && pen->customStartCap
|
|
&& pen->customStartCap->polygon.count()
|
|
&& (pen->customStartCap->polygon.begin()->count() > 1))
|
|
aStart = drawinglayer::attribute::LineStartEndAttribute(
|
|
pen->customStartCap->polygon.getB2DRange().getRange().getX() * mdExtractedXScale
|
|
* pen->customStartCap->widthScale * pen->penWidth,
|
|
pen->customStartCap->polygon, false);
|
|
else
|
|
aStart = EmfPlusHelperData::CreateLineEnd(pen->startCap, pen->penWidth);
|
|
}
|
|
|
|
drawinglayer::attribute::LineStartEndAttribute aEnd;
|
|
if (pen->penDataFlags & EmfPlusPenDataEndCap)
|
|
{
|
|
if ((pen->penDataFlags & EmfPlusPenDataCustomEndCap) && pen->customEndCap
|
|
&& pen->customEndCap->polygon.count()
|
|
&& (pen->customEndCap->polygon.begin()->count() > 1))
|
|
aEnd = drawinglayer::attribute::LineStartEndAttribute(
|
|
pen->customEndCap->polygon.getB2DRange().getRange().getX() * mdExtractedXScale
|
|
* pen->customEndCap->widthScale * pen->penWidth,
|
|
pen->customEndCap->polygon, false);
|
|
else
|
|
aEnd = EmfPlusHelperData::CreateLineEnd(pen->endCap, pen->penWidth);
|
|
}
|
|
|
|
if (pen->GetColor().IsTransparent())
|
|
{
|
|
drawinglayer::primitive2d::Primitive2DContainer aContainer;
|
|
if (aStart.isDefault() && aEnd.isDefault())
|
|
aContainer.append(
|
|
new drawinglayer::primitive2d::PolyPolygonStrokePrimitive2D(
|
|
polygon, lineAttribute, pen->GetStrokeAttribute(mdExtractedXScale)));
|
|
else
|
|
{
|
|
aContainer.resize(polygon.count());
|
|
for (sal_uInt32 i = 0; i < polygon.count(); i++)
|
|
aContainer[i] =
|
|
new drawinglayer::primitive2d::PolygonStrokeArrowPrimitive2D(
|
|
polygon.getB2DPolygon(i), lineAttribute,
|
|
pen->GetStrokeAttribute(mdExtractedXScale), aStart, aEnd);
|
|
}
|
|
mrTargetHolders.Current().append(
|
|
new drawinglayer::primitive2d::UnifiedTransparencePrimitive2D(
|
|
std::move(aContainer), (255 - pen->GetColor().GetAlpha()) / 255.0));
|
|
}
|
|
else
|
|
{
|
|
if (aStart.isDefault() && aEnd.isDefault())
|
|
mrTargetHolders.Current().append(
|
|
new drawinglayer::primitive2d::PolyPolygonStrokePrimitive2D(
|
|
polygon, lineAttribute, pen->GetStrokeAttribute(mdExtractedXScale)));
|
|
else
|
|
for (sal_uInt32 i = 0; i < polygon.count(); i++)
|
|
{
|
|
mrTargetHolders.Current().append(
|
|
new drawinglayer::primitive2d::PolygonStrokeArrowPrimitive2D(
|
|
polygon.getB2DPolygon(i), lineAttribute,
|
|
pen->GetStrokeAttribute(mdExtractedXScale), aStart, aEnd));
|
|
}
|
|
}
|
|
mrPropertyHolders.Current().setLineColor(pen->GetColor().getBColor());
|
|
mrPropertyHolders.Current().setLineColorActive(true);
|
|
mrPropertyHolders.Current().setFillColorActive(false);
|
|
}
|
|
|
|
// Convert a polypolygon so that nonzero/winding fill semantics produce
|
|
// the same visual result when rendered with the even-odd fill rule used
|
|
// by the primitive2d fill primitives. A self-intersecting single
|
|
// polygon is resolved by solveCrossovers + stripNeutralPolygons and the
|
|
// resulting sub-loops are merged. Multi-polygon inputs use
|
|
// createNonzeroConform which removes inner duplicates of nested
|
|
// same-orientation polygons.
|
|
static ::basegfx::B2DPolyPolygon convertWindingToEvenOdd(
|
|
const ::basegfx::B2DPolyPolygon& rIn)
|
|
{
|
|
if (rIn.count() == 1)
|
|
{
|
|
::basegfx::B2DPolyPolygon aResolved
|
|
= ::basegfx::utils::solveCrossovers(rIn.getB2DPolygon(0));
|
|
aResolved = ::basegfx::utils::stripNeutralPolygons(aResolved);
|
|
if (aResolved.count() > 1)
|
|
{
|
|
::basegfx::B2DPolyPolygonVector aInput;
|
|
for (sal_uInt32 i = 0; i < aResolved.count(); ++i)
|
|
aInput.push_back(::basegfx::B2DPolyPolygon(aResolved.getB2DPolygon(i)));
|
|
return ::basegfx::utils::mergeToSinglePolyPolygon(aInput);
|
|
}
|
|
return aResolved;
|
|
}
|
|
return ::basegfx::utils::createNonzeroConform(rIn);
|
|
}
|
|
|
|
void EmfPlusHelperData::EMFPPlusFillPolygonSolidColor(const ::basegfx::B2DPolyPolygon& polygon, Color const& color)
|
|
{
|
|
if (color.GetAlpha() == 0)
|
|
return;
|
|
|
|
if (!color.IsTransparent())
|
|
{
|
|
// not transparent
|
|
mrTargetHolders.Current().append(
|
|
new drawinglayer::primitive2d::PolyPolygonColorPrimitive2D(
|
|
polygon,
|
|
color.getBColor()));
|
|
}
|
|
else
|
|
{
|
|
// transparent
|
|
mrTargetHolders.Current().append(
|
|
new drawinglayer::primitive2d::PolyPolygonRGBAPrimitive2D(
|
|
polygon,
|
|
color.getBColor(),
|
|
(255 - color.GetAlpha()) / 255.0));
|
|
}
|
|
}
|
|
|
|
void EmfPlusHelperData::EMFPPlusFillPolygon(const ::basegfx::B2DPolyPolygon& polygon, const bool isColor, const sal_uInt32 brushIndexOrColor)
|
|
{
|
|
if (!polygon.count())
|
|
return;
|
|
|
|
if (isColor) // use Color
|
|
{
|
|
SAL_INFO("drawinglayer.emf", "EMF+\t\t Fill polygon, ARGB color: 0x" << std::hex << brushIndexOrColor << std::dec);
|
|
|
|
// EMF Alpha (1 byte): An 8-bit unsigned integer that specifies the transparency of the background,
|
|
// ranging from 0 for completely transparent to 0xFF for completely opaque.
|
|
const Color color(ColorAlpha, (brushIndexOrColor >> 24), (brushIndexOrColor >> 16) & 0xff, (brushIndexOrColor >> 8) & 0xff, brushIndexOrColor & 0xff);
|
|
EMFPPlusFillPolygonSolidColor(polygon, color);
|
|
|
|
mrPropertyHolders.Current().setFillColor(color.getBColor());
|
|
mrPropertyHolders.Current().setFillColorActive(true);
|
|
mrPropertyHolders.Current().setLineColorActive(false);
|
|
}
|
|
else // use Brush
|
|
{
|
|
EMFPBrush* brush = dynamic_cast<EMFPBrush*>(maEMFPObjects[brushIndexOrColor & 0xff].get());
|
|
SAL_INFO("drawinglayer.emf", "EMF+\t\t Fill polygon, brush slot: " << brushIndexOrColor << " (brush type: " << (brush ? brush->GetType() : -1) << ")");
|
|
|
|
// give up in case something wrong happened
|
|
if( !brush )
|
|
return;
|
|
|
|
mrPropertyHolders.Current().setFillColorActive(false);
|
|
mrPropertyHolders.Current().setLineColorActive(false);
|
|
|
|
if (brush->type == BrushTypeSolidColor)
|
|
{
|
|
Color fillColor = brush->solidColor;
|
|
EMFPPlusFillPolygonSolidColor(polygon, fillColor);
|
|
}
|
|
else if (brush->type == BrushTypeHatchFill)
|
|
{
|
|
// EMF+ like hatching is currently not supported. These are just color blends which serve as an approximation for some of them
|
|
// for the others the hatch "background" color (secondColor in brush) is used.
|
|
|
|
bool isHatchBlend = true;
|
|
double blendFactor = 0.0;
|
|
|
|
switch (brush->hatchStyle)
|
|
{
|
|
case HatchStyle05Percent: blendFactor = 0.05; break;
|
|
case HatchStyle10Percent: blendFactor = 0.10; break;
|
|
case HatchStyle20Percent: blendFactor = 0.20; break;
|
|
case HatchStyle25Percent: blendFactor = 0.25; break;
|
|
case HatchStyle30Percent: blendFactor = 0.30; break;
|
|
case HatchStyle40Percent: blendFactor = 0.40; break;
|
|
case HatchStyle50Percent: blendFactor = 0.50; break;
|
|
case HatchStyle60Percent: blendFactor = 0.60; break;
|
|
case HatchStyle70Percent: blendFactor = 0.70; break;
|
|
case HatchStyle75Percent: blendFactor = 0.75; break;
|
|
case HatchStyle80Percent: blendFactor = 0.80; break;
|
|
case HatchStyle90Percent: blendFactor = 0.90; break;
|
|
default:
|
|
isHatchBlend = false;
|
|
break;
|
|
}
|
|
Color fillColor;
|
|
if (isHatchBlend)
|
|
{
|
|
fillColor = brush->solidColor;
|
|
fillColor.Merge(brush->secondColor, static_cast<sal_uInt8>(255 * blendFactor));
|
|
}
|
|
else
|
|
{
|
|
fillColor = brush->secondColor;
|
|
}
|
|
// temporal solution: create a solid colored polygon
|
|
// TODO create a 'real' hatching primitive
|
|
mrTargetHolders.Current().append(
|
|
new drawinglayer::primitive2d::PolyPolygonColorPrimitive2D(
|
|
polygon,
|
|
fillColor.getBColor()));
|
|
}
|
|
else if (brush->type == BrushTypeTextureFill && brush->textureImage)
|
|
{
|
|
if (brush->textureImage->type != ImageDataTypeBitmap)
|
|
{
|
|
SAL_WARN("drawinglayer.emf", "EMF+\tTextureFill: Metafiles type is not supported");
|
|
return;
|
|
}
|
|
|
|
const Size aSizePx = brush->textureImage->graphic.GetSizePixel();
|
|
if (aSizePx.Width() <= 0 || aSizePx.Height() <= 0)
|
|
return;
|
|
|
|
basegfx::B2DHomMatrix aBrushMatrix;
|
|
if (brush->additionalFlags & 0x02) // 0x02 = BrushDataTransform flag
|
|
aBrushMatrix = brush->brush_transformation;
|
|
|
|
// Create a scaling matrix to map the normalized 1x1 range
|
|
// to the actual pixel dimensions of the texture.
|
|
|
|
const basegfx::B2DHomMatrix aTextureScale(
|
|
/* Row 0, Column 0 */ aSizePx.Width(),
|
|
/* Row 0, Column 1 */ 0.0,
|
|
/* Row 0, Column 2 */ 0.0,
|
|
/* Row 1, Column 0 */ 0.0,
|
|
/* Row 1, Column 1 */ aSizePx.Height(),
|
|
/* Row 1, Column 2 */ 0.0);
|
|
|
|
const basegfx::B2DHomMatrix aTotalMatrix = maMapTransform * aBrushMatrix * aTextureScale;
|
|
|
|
// Invert the matrix to transform the fill polygon from document space
|
|
// back into the local normalized texture space (0.0 to 1.0).
|
|
basegfx::B2DHomMatrix aInvTotalMatrix = aTotalMatrix;
|
|
if (!aInvTotalMatrix.invert())
|
|
{
|
|
SAL_WARN("drawinglayer.emf", "EMF+\tTextureFill: Failed to invert transformation matrix");
|
|
return;
|
|
}
|
|
basegfx::B2DPolyPolygon aLocalPolygon = polygon;
|
|
aLocalPolygon.transform(aInvTotalMatrix);
|
|
|
|
const bool bIsTiled = (brush->wrapMode != WrapModeClamp);
|
|
|
|
const drawinglayer::attribute::FillGraphicAttribute aFillAttribute(
|
|
brush->textureImage->graphic,
|
|
basegfx::B2DRange(0.0, 0.0, 1.0, 1.0),
|
|
bIsTiled);
|
|
|
|
drawinglayer::primitive2d::Primitive2DReference xFillPrimitive =
|
|
new drawinglayer::primitive2d::PolyPolygonGraphicPrimitive2D(
|
|
aLocalPolygon,
|
|
basegfx::B2DRange(0.0, 0.0, 1.0, 1.0),
|
|
aFillAttribute);
|
|
|
|
mrTargetHolders.Current().append(
|
|
new drawinglayer::primitive2d::TransformPrimitive2D(
|
|
aTotalMatrix,
|
|
drawinglayer::primitive2d::Primitive2DContainer({ xFillPrimitive })));
|
|
}
|
|
else if (brush->type == BrushTypePathGradient || brush->type == BrushTypeLinearGradient)
|
|
{
|
|
if (brush->type == BrushTypePathGradient && !(brush->additionalFlags & 0x1))
|
|
{
|
|
SAL_WARN("drawinglayer.emf", "EMF+\t TODO Implement displaying BrushTypePathGradient with Boundary: ");
|
|
}
|
|
::basegfx::B2DHomMatrix aTextureTransformation;
|
|
|
|
if (brush->hasTransformation) {
|
|
aTextureTransformation = brush->brush_transformation;
|
|
|
|
// adjust aTextureTransformation for our world space:
|
|
// -> revert the mapping -> apply the transformation -> map back
|
|
basegfx::B2DHomMatrix aInvertedMapTrasform(maMapTransform);
|
|
aInvertedMapTrasform.invert();
|
|
aTextureTransformation = maMapTransform * aTextureTransformation * aInvertedMapTrasform;
|
|
}
|
|
|
|
// select the stored colors
|
|
const basegfx::BColor aStartColor = brush->solidColor.getBColor();
|
|
const basegfx::BColor aEndColor = brush->secondColor.getBColor();
|
|
drawinglayer::primitive2d::SvgGradientEntryVector aVector;
|
|
|
|
if (brush->blendPositions)
|
|
{
|
|
SAL_INFO("drawinglayer.emf", "EMF+\t\tUse blend");
|
|
|
|
// store the blendpoints in the vector
|
|
for (sal_uInt32 i = 0; i < brush->blendPoints; i++)
|
|
{
|
|
const double aBlendPoint = brush->blendPositions[i];
|
|
basegfx::BColor aColor;
|
|
aColor.setGreen(aStartColor.getGreen() + brush->blendFactors[i] * (aEndColor.getGreen() - aStartColor.getGreen()));
|
|
aColor.setBlue (aStartColor.getBlue() + brush->blendFactors[i] * (aEndColor.getBlue() - aStartColor.getBlue()));
|
|
aColor.setRed (aStartColor.getRed() + brush->blendFactors[i] * (aEndColor.getRed() - aStartColor.getRed()));
|
|
const double aAlpha = brush->solidColor.GetAlpha() + brush->blendFactors[i] * (brush->secondColor.GetAlpha() - brush->solidColor.GetAlpha());
|
|
aVector.emplace_back(aBlendPoint, aColor, aAlpha / 255.0);
|
|
}
|
|
}
|
|
else if (brush->colorblendPositions)
|
|
{
|
|
SAL_INFO("drawinglayer.emf", "EMF+\t\tUse color blend");
|
|
|
|
// store the colorBlends in the vector
|
|
for (sal_uInt32 i = 0; i < brush->colorblendPoints; i++)
|
|
{
|
|
const double aBlendPoint = brush->colorblendPositions[i];
|
|
const basegfx::BColor aColor = brush->colorblendColors[i].getBColor();
|
|
aVector.emplace_back(aBlendPoint, aColor, brush->colorblendColors[i].GetAlpha() / 255.0);
|
|
}
|
|
}
|
|
else // ok, no extra points: just start and end
|
|
{
|
|
aVector.emplace_back(0.0, aStartColor, brush->solidColor.GetAlpha() / 255.0);
|
|
aVector.emplace_back(1.0, aEndColor, brush->secondColor.GetAlpha() / 255.0);
|
|
}
|
|
|
|
// get the polygon range to be able to map the start/end/center point correctly
|
|
// therefore, create a mapping and invert it
|
|
basegfx::B2DRange aPolygonRange= polygon.getB2DRange();
|
|
basegfx::B2DHomMatrix aPolygonTransformation = basegfx::utils::createScaleTranslateB2DHomMatrix(
|
|
aPolygonRange.getWidth(),aPolygonRange.getHeight(),
|
|
aPolygonRange.getMinX(), aPolygonRange.getMinY());
|
|
aPolygonTransformation.invert();
|
|
|
|
if (brush->type == BrushTypeLinearGradient)
|
|
{
|
|
// support for public enum EmfPlusWrapMode
|
|
basegfx::B2DPoint aStartPoint = Map(brush->firstPointX, 0.0);
|
|
aStartPoint = aPolygonTransformation * aStartPoint;
|
|
basegfx::B2DPoint aEndPoint = Map(brush->firstPointX + brush->aWidth, 0.0);
|
|
aEndPoint = aPolygonTransformation * aEndPoint;
|
|
|
|
// support for public enum EmfPlusWrapMode
|
|
drawinglayer::primitive2d::SpreadMethod aSpreadMethod(drawinglayer::primitive2d::SpreadMethod::Pad);
|
|
switch(brush->wrapMode)
|
|
{
|
|
case WrapModeTile:
|
|
case WrapModeTileFlipY:
|
|
{
|
|
aSpreadMethod = drawinglayer::primitive2d::SpreadMethod::Repeat;
|
|
break;
|
|
}
|
|
case WrapModeTileFlipX:
|
|
case WrapModeTileFlipXY:
|
|
{
|
|
aSpreadMethod = drawinglayer::primitive2d::SpreadMethod::Reflect;
|
|
break;
|
|
}
|
|
default:
|
|
break;
|
|
}
|
|
|
|
// create the same one used for SVG
|
|
mrTargetHolders.Current().append(
|
|
new drawinglayer::primitive2d::SvgLinearGradientPrimitive2D(
|
|
aTextureTransformation,
|
|
polygon,
|
|
std::move(aVector),
|
|
aStartPoint,
|
|
aEndPoint,
|
|
false, // do not use UnitCoordinates
|
|
aSpreadMethod));
|
|
}
|
|
else // BrushTypePathGradient
|
|
{
|
|
// Render the gradient pattern into a small bitmap and use
|
|
// it as a (potentially tiled) texture fill. SvgRadialGradient
|
|
// alone cannot reproduce path-gradient tiling because the
|
|
// brush coordinate space and the fill-polygon space need
|
|
// not overlap.
|
|
if (!brush->path)
|
|
return;
|
|
|
|
basegfx::B2DRange aBrushBounds(
|
|
brush->path->GetPolygon(*this, false).getB2DRange());
|
|
if (aBrushBounds.getWidth() <= 0 || aBrushBounds.getHeight() <= 0)
|
|
return;
|
|
|
|
constexpr sal_Int32 nMaxDim = 256;
|
|
sal_Int32 nBmpW, nBmpH;
|
|
if (aBrushBounds.getWidth() >= aBrushBounds.getHeight())
|
|
{
|
|
nBmpW = nMaxDim;
|
|
nBmpH = std::max<sal_Int32>(
|
|
1, std::lround(nMaxDim * aBrushBounds.getHeight()
|
|
/ aBrushBounds.getWidth()));
|
|
}
|
|
else
|
|
{
|
|
nBmpH = nMaxDim;
|
|
nBmpW = std::max<sal_Int32>(
|
|
1, std::lround(nMaxDim * aBrushBounds.getWidth()
|
|
/ aBrushBounds.getHeight()));
|
|
}
|
|
|
|
// Path-gradient sweep: GDI+ partitions the brush
|
|
// boundary into triangles formed by (centre, V_i,
|
|
// V_{i+1}) and Gouraud-shades each one with vertex
|
|
// colours (centerColor, surround[i],
|
|
// surround[(i+1) % nSurround]). Inside a triangle the
|
|
// pixel colour is the barycentric blend of the three
|
|
// vertex colours; this reproduces the colourful per-
|
|
// segment rendering that GDI+ produces (red/green/blue
|
|
// points on a star with multiple surround colours, and
|
|
// smooth radial gradient on a uniform-surround brush).
|
|
basegfx::B2DPolygon aSweepPolygon
|
|
= brush->path->GetPolygon(*this, false).getB2DPolygon(0);
|
|
// Flatten Bezier control points into straight-line
|
|
// segments so the triangulation has a fine-enough
|
|
// boundary. Without this, a 4-cardinal-vertex Bezier
|
|
// ellipse becomes 4 triangles meeting at the centre
|
|
// (a diamond), which is visibly wrong.
|
|
if (aSweepPolygon.areControlPointsUsed())
|
|
aSweepPolygon = basegfx::utils::adaptiveSubdivideByDistance(
|
|
aSweepPolygon,
|
|
std::max(aBrushBounds.getWidth(),
|
|
aBrushBounds.getHeight()) / 512.0);
|
|
while (aSweepPolygon.count() >= 2
|
|
&& aSweepPolygon.getB2DPoint(0)
|
|
== aSweepPolygon.getB2DPoint(aSweepPolygon.count() - 1))
|
|
aSweepPolygon.remove(aSweepPolygon.count() - 1);
|
|
|
|
const sal_uInt32 nVerts = aSweepPolygon.count();
|
|
const basegfx::B2DPoint aCentre(
|
|
brush->firstPointX, brush->firstPointY);
|
|
|
|
// Per-vertex surround colour (one per boundary vertex).
|
|
// surroundColors[i] is a sal_uInt8-channel Color; convert
|
|
// to BColor (0..1 doubles) once.
|
|
std::vector<basegfx::BColor> aVertColor(nVerts);
|
|
std::vector<double> aVertAlpha(nVerts);
|
|
{
|
|
const sal_uInt32 nSurr
|
|
= brush->surroundColorsNumber > 0
|
|
? brush->surroundColorsNumber
|
|
: 1;
|
|
for (sal_uInt32 i = 0; i < nVerts; ++i)
|
|
{
|
|
const Color& c
|
|
= brush->surroundColorsNumber > 0
|
|
? brush->surroundColors[i % nSurr]
|
|
: brush->secondColor;
|
|
aVertColor[i] = c.getBColor();
|
|
aVertAlpha[i] = c.GetAlpha() / 255.0;
|
|
}
|
|
}
|
|
const basegfx::BColor aCentreColor
|
|
= brush->solidColor.getBColor();
|
|
const double fCentreAlpha
|
|
= brush->solidColor.GetAlpha() / 255.0;
|
|
|
|
// Optional centre->edge transition curves carried by the
|
|
// brush. BlendFactors remap the gradient parameter t
|
|
// (0 at centre, 1 at edge) before the centre/edge mix.
|
|
// PresetColors (colorblend) replace the centre/edge mix
|
|
// entirely with a sampled multi-stop colour curve. The
|
|
// two are mutually exclusive in well-formed EMF+ data.
|
|
struct FactorStop { double pos; double factor; };
|
|
std::vector<FactorStop> aFactorCurve;
|
|
if (brush->blendPositions && brush->blendPoints > 0)
|
|
{
|
|
aFactorCurve.reserve(brush->blendPoints);
|
|
for (sal_uInt32 i = 0; i < brush->blendPoints; ++i)
|
|
aFactorCurve.push_back({brush->blendPositions[i],
|
|
brush->blendFactors[i]});
|
|
std::sort(aFactorCurve.begin(), aFactorCurve.end(),
|
|
[](const FactorStop& a, const FactorStop& b)
|
|
{ return a.pos < b.pos; });
|
|
}
|
|
|
|
struct ColorStop { double pos; basegfx::BColor color; double alpha; };
|
|
std::vector<ColorStop> aColorCurve;
|
|
if (brush->colorblendPositions && brush->colorblendPoints > 0)
|
|
{
|
|
aColorCurve.reserve(brush->colorblendPoints);
|
|
for (sal_uInt32 i = 0; i < brush->colorblendPoints; ++i)
|
|
aColorCurve.push_back(
|
|
{brush->colorblendPositions[i],
|
|
brush->colorblendColors[i].getBColor(),
|
|
brush->colorblendColors[i].GetAlpha() / 255.0});
|
|
std::sort(aColorCurve.begin(), aColorCurve.end(),
|
|
[](const ColorStop& a, const ColorStop& b)
|
|
{ return a.pos < b.pos; });
|
|
}
|
|
|
|
auto sampleFactor = [&aFactorCurve](double t) -> double
|
|
{
|
|
if (aFactorCurve.empty()) return t;
|
|
if (t <= aFactorCurve.front().pos)
|
|
return aFactorCurve.front().factor;
|
|
if (t >= aFactorCurve.back().pos)
|
|
return aFactorCurve.back().factor;
|
|
for (size_t i = 0; i + 1 < aFactorCurve.size(); ++i)
|
|
{
|
|
if (t <= aFactorCurve[i + 1].pos)
|
|
{
|
|
const double span
|
|
= aFactorCurve[i + 1].pos - aFactorCurve[i].pos;
|
|
const double f
|
|
= span > 0.0 ? (t - aFactorCurve[i].pos) / span : 0.0;
|
|
return aFactorCurve[i].factor
|
|
+ f * (aFactorCurve[i + 1].factor
|
|
- aFactorCurve[i].factor);
|
|
}
|
|
}
|
|
return aFactorCurve.back().factor;
|
|
};
|
|
|
|
auto sampleColor = [&aColorCurve](double t)
|
|
-> std::pair<basegfx::BColor, double>
|
|
{
|
|
if (aColorCurve.empty()) return {basegfx::BColor(), 1.0};
|
|
if (t <= aColorCurve.front().pos)
|
|
return {aColorCurve.front().color,
|
|
aColorCurve.front().alpha};
|
|
if (t >= aColorCurve.back().pos)
|
|
return {aColorCurve.back().color,
|
|
aColorCurve.back().alpha};
|
|
for (size_t i = 0; i + 1 < aColorCurve.size(); ++i)
|
|
{
|
|
if (t <= aColorCurve[i + 1].pos)
|
|
{
|
|
const double span
|
|
= aColorCurve[i + 1].pos - aColorCurve[i].pos;
|
|
const double f
|
|
= span > 0.0 ? (t - aColorCurve[i].pos) / span : 0.0;
|
|
basegfx::BColor c;
|
|
c.setRed(aColorCurve[i].color.getRed()
|
|
+ f * (aColorCurve[i + 1].color.getRed()
|
|
- aColorCurve[i].color.getRed()));
|
|
c.setGreen(aColorCurve[i].color.getGreen()
|
|
+ f * (aColorCurve[i + 1].color.getGreen()
|
|
- aColorCurve[i].color.getGreen()));
|
|
c.setBlue(aColorCurve[i].color.getBlue()
|
|
+ f * (aColorCurve[i + 1].color.getBlue()
|
|
- aColorCurve[i].color.getBlue()));
|
|
return {c, aColorCurve[i].alpha
|
|
+ f * (aColorCurve[i + 1].alpha
|
|
- aColorCurve[i].alpha)};
|
|
}
|
|
}
|
|
return {aColorCurve.back().color,
|
|
aColorCurve.back().alpha};
|
|
};
|
|
|
|
auto signedCross = [](const basegfx::B2DPoint& a,
|
|
const basegfx::B2DPoint& b,
|
|
const basegfx::B2DPoint& c) -> double
|
|
{
|
|
return (b.getX() - a.getX()) * (c.getY() - a.getY())
|
|
- (b.getY() - a.getY()) * (c.getX() - a.getX());
|
|
};
|
|
|
|
// Compose the final pixel colour given the active
|
|
// boundary segment index i, the segment-edge weights
|
|
// w0/w1 and the centre-edge parameter tRaw. Mixes the
|
|
// per-segment surround colours (Gouraud) and applies
|
|
// the brush's blend curve when present.
|
|
auto composeColor
|
|
= [&aVertColor, &aVertAlpha, &aCentreColor, &fCentreAlpha,
|
|
&aColorCurve, &sampleColor, &sampleFactor, nVerts](
|
|
sal_uInt32 i, double w0, double w1, double tRaw,
|
|
basegfx::BColor& outColor, double& outAlpha)
|
|
{
|
|
basegfx::BColor aEdgeColor;
|
|
double fEdgeAlpha = 0.0;
|
|
const double fEdgeSum = w0 + w1;
|
|
if (fEdgeSum > 1e-12)
|
|
{
|
|
const double f0 = w0 / fEdgeSum;
|
|
const double f1 = w1 / fEdgeSum;
|
|
aEdgeColor.setRed(
|
|
f0 * aVertColor[i].getRed()
|
|
+ f1 * aVertColor[(i + 1) % nVerts].getRed());
|
|
aEdgeColor.setGreen(
|
|
f0 * aVertColor[i].getGreen()
|
|
+ f1 * aVertColor[(i + 1) % nVerts].getGreen());
|
|
aEdgeColor.setBlue(
|
|
f0 * aVertColor[i].getBlue()
|
|
+ f1 * aVertColor[(i + 1) % nVerts].getBlue());
|
|
fEdgeAlpha
|
|
= f0 * aVertAlpha[i]
|
|
+ f1 * aVertAlpha[(i + 1) % nVerts];
|
|
}
|
|
else
|
|
{
|
|
aEdgeColor = aVertColor[i];
|
|
fEdgeAlpha = aVertAlpha[i];
|
|
}
|
|
if (!aColorCurve.empty())
|
|
{
|
|
// PresetColors override the centre/edge mix.
|
|
auto [presetColor, presetAlpha] = sampleColor(tRaw);
|
|
outColor = presetColor;
|
|
outAlpha = presetAlpha;
|
|
}
|
|
else
|
|
{
|
|
// BlendFactors remap tRaw before mixing centre
|
|
// and (per-segment Gouraud) edge colour. With
|
|
// an empty factor curve sampleFactor returns t
|
|
// unchanged so the mix is linear.
|
|
const double tCurve = sampleFactor(tRaw);
|
|
outColor.setRed(
|
|
(1.0 - tCurve) * aCentreColor.getRed()
|
|
+ tCurve * aEdgeColor.getRed());
|
|
outColor.setGreen(
|
|
(1.0 - tCurve) * aCentreColor.getGreen()
|
|
+ tCurve * aEdgeColor.getGreen());
|
|
outColor.setBlue(
|
|
(1.0 - tCurve) * aCentreColor.getBlue()
|
|
+ tCurve * aEdgeColor.getBlue());
|
|
outAlpha = (1.0 - tCurve) * fCentreAlpha
|
|
+ tCurve * fEdgeAlpha;
|
|
}
|
|
};
|
|
|
|
Bitmap aBmp(Size(nBmpW, nBmpH), vcl::PixelFormat::N32_BPP);
|
|
AlphaMask aAlpha(Size(nBmpW, nBmpH));
|
|
aBmp.Erase(COL_BLACK);
|
|
aAlpha.Erase(255);
|
|
{
|
|
BitmapScopedWriteAccess pBmpWrite(aBmp);
|
|
BitmapScopedWriteAccess pAlphaWrite(aAlpha);
|
|
if (!pBmpWrite || !pAlphaWrite)
|
|
return;
|
|
|
|
// Track which pixels Pass 1 has painted so the
|
|
// Clamp-mode Pass 2 can find the rest. Alpha == 0
|
|
// alone is not a reliable "uncovered" marker
|
|
// because a translucent surround colour can
|
|
// legitimately produce alpha == 0 inside a fan
|
|
// triangle.
|
|
std::vector<bool> aCovered(
|
|
static_cast<size_t>(nBmpW) * nBmpH, false);
|
|
|
|
auto writePixel
|
|
= [&pBmpWrite, &pAlphaWrite, &aCovered, nBmpW](
|
|
sal_Int32 x, sal_Int32 y,
|
|
const basegfx::BColor& c, double a)
|
|
{
|
|
pBmpWrite->SetPixel(y, x, BitmapColor(
|
|
static_cast<sal_uInt8>(std::lround(std::clamp(c.getRed(), 0.0, 1.0) * 255)),
|
|
static_cast<sal_uInt8>(std::lround(std::clamp(c.getGreen(), 0.0, 1.0) * 255)),
|
|
static_cast<sal_uInt8>(std::lround(std::clamp(c.getBlue(), 0.0, 1.0) * 255))));
|
|
pAlphaWrite->SetPixelIndex(y, x, static_cast<sal_uInt8>(
|
|
std::lround(std::clamp(a, 0.0, 1.0) * 255)));
|
|
aCovered[static_cast<size_t>(y) * nBmpW + x] = true;
|
|
};
|
|
|
|
// Pixel <-> brush-coord helpers. A pixel at integer
|
|
// (x,y) has its centre at world coord
|
|
// bb.minX + (x + 0.5) / nBmpW * bb.width
|
|
// (and similarly for y).
|
|
auto pixelToWorld
|
|
= [&aBrushBounds, nBmpW, nBmpH](
|
|
sal_Int32 x, sal_Int32 y) -> basegfx::B2DPoint
|
|
{
|
|
return basegfx::B2DPoint(
|
|
aBrushBounds.getMinX()
|
|
+ (x + 0.5) / nBmpW * aBrushBounds.getWidth(),
|
|
aBrushBounds.getMinY()
|
|
+ (y + 0.5) / nBmpH * aBrushBounds.getHeight());
|
|
};
|
|
auto worldToPixelX
|
|
= [&aBrushBounds, nBmpW](double wx) -> double
|
|
{
|
|
return (wx - aBrushBounds.getMinX()) * nBmpW
|
|
/ aBrushBounds.getWidth() - 0.5;
|
|
};
|
|
auto worldToPixelY
|
|
= [&aBrushBounds, nBmpH](double wy) -> double
|
|
{
|
|
return (wy - aBrushBounds.getMinY()) * nBmpH
|
|
/ aBrushBounds.getHeight() - 0.5;
|
|
};
|
|
|
|
// Pass 1: rasterize each fan triangle (centre,
|
|
// V_i, V_{i+1}) by iterating only the triangle's
|
|
// pixel-space AABB. Cost is O(sum of triangle
|
|
// areas), not O(bitmap area * triangle count).
|
|
// Structurally this is what cairo's mesh-pattern
|
|
// rasterizer does for PDF type-7 shadings: each
|
|
// patch paints into its own pixel range, rather
|
|
// than every pixel testing every patch.
|
|
for (sal_uInt32 i = 0; i < nVerts; ++i)
|
|
{
|
|
const basegfx::B2DPoint aV0
|
|
= aSweepPolygon.getB2DPoint(i);
|
|
const basegfx::B2DPoint aV1
|
|
= aSweepPolygon.getB2DPoint((i + 1) % nVerts);
|
|
// Total triangle area (signed). Sign of
|
|
// fTotal carries the polygon winding; a
|
|
// point is inside the triangle when all
|
|
// three sub-areas have that same sign.
|
|
const double fTotal
|
|
= signedCross(aCentre, aV0, aV1);
|
|
if (std::abs(fTotal) < 1e-12)
|
|
continue;
|
|
const double s = fTotal > 0.0 ? 1.0 : -1.0;
|
|
const double fInvAbsTotal = 1.0 / std::abs(fTotal);
|
|
|
|
const double fMinX = std::min({aCentre.getX(), aV0.getX(), aV1.getX()});
|
|
const double fMaxX = std::max({aCentre.getX(), aV0.getX(), aV1.getX()});
|
|
const double fMinY = std::min({aCentre.getY(), aV0.getY(), aV1.getY()});
|
|
const double fMaxY = std::max({aCentre.getY(), aV0.getY(), aV1.getY()});
|
|
const sal_Int32 nXMin = std::clamp<sal_Int32>(
|
|
static_cast<sal_Int32>(std::floor(worldToPixelX(fMinX))),
|
|
0, nBmpW - 1);
|
|
const sal_Int32 nXMax = std::clamp<sal_Int32>(
|
|
static_cast<sal_Int32>(std::ceil(worldToPixelX(fMaxX))),
|
|
0, nBmpW - 1);
|
|
const sal_Int32 nYMin = std::clamp<sal_Int32>(
|
|
static_cast<sal_Int32>(std::floor(worldToPixelY(fMinY))),
|
|
0, nBmpH - 1);
|
|
const sal_Int32 nYMax = std::clamp<sal_Int32>(
|
|
static_cast<sal_Int32>(std::ceil(worldToPixelY(fMaxY))),
|
|
0, nBmpH - 1);
|
|
|
|
for (sal_Int32 y = nYMin; y <= nYMax; ++y)
|
|
{
|
|
for (sal_Int32 x = nXMin; x <= nXMax; ++x)
|
|
{
|
|
const basegfx::B2DPoint aP = pixelToWorld(x, y);
|
|
// Barycentric weights via signed
|
|
// sub-triangle areas. wC = area(P,
|
|
// V0, V1) / area(C, V0, V1) etc.
|
|
const double wC
|
|
= s * signedCross(aP, aV0, aV1) * fInvAbsTotal;
|
|
const double w0
|
|
= s * signedCross(aCentre, aP, aV1) * fInvAbsTotal;
|
|
const double w1
|
|
= s * signedCross(aCentre, aV0, aP) * fInvAbsTotal;
|
|
constexpr double EPS = -1e-9;
|
|
if (wC < EPS || w0 < EPS || w1 < EPS)
|
|
continue;
|
|
// Centre/edge parameter (0 at centre,
|
|
// 1 at boundary segment).
|
|
const double tRaw
|
|
= std::clamp(1.0 - wC, 0.0, 1.0);
|
|
basegfx::BColor aColor;
|
|
double fAlpha = 0.0;
|
|
composeColor(i, w0, w1, tRaw, aColor, fAlpha);
|
|
writePixel(x, y, aColor, fAlpha);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Pass 2 (Clamp wrap mode): for pixels that fell
|
|
// outside every fan triangle, fill with the
|
|
// closest triangle's edge colour. This
|
|
// eliminates sub-pixel transparent jaggies along
|
|
// smooth Bezier fill polygons that extend
|
|
// slightly past the flattened boundary.
|
|
//
|
|
// For other wrap modes we leave uncovered pixels
|
|
// transparent so that adjacent tiles in the fill
|
|
// polygon stay visually separate.
|
|
if (brush->wrapMode == WrapModeClamp)
|
|
{
|
|
for (sal_Int32 y = 0; y < nBmpH; ++y)
|
|
{
|
|
for (sal_Int32 x = 0; x < nBmpW; ++x)
|
|
{
|
|
if (aCovered[static_cast<size_t>(y) * nBmpW + x])
|
|
continue;
|
|
const basegfx::B2DPoint aP = pixelToWorld(x, y);
|
|
sal_uInt32 nClosestI = 0;
|
|
double fClosestW0 = 0.0;
|
|
double fClosestW1 = 0.0;
|
|
double fMaxNegMargin
|
|
= std::numeric_limits<double>::lowest();
|
|
for (sal_uInt32 i = 0; i < nVerts; ++i)
|
|
{
|
|
const basegfx::B2DPoint aV0
|
|
= aSweepPolygon.getB2DPoint(i);
|
|
const basegfx::B2DPoint aV1
|
|
= aSweepPolygon.getB2DPoint((i + 1) % nVerts);
|
|
const double fTotal
|
|
= signedCross(aCentre, aV0, aV1);
|
|
if (std::abs(fTotal) < 1e-12)
|
|
continue;
|
|
const double s
|
|
= fTotal > 0.0 ? 1.0 : -1.0;
|
|
const double fInvAbsTotal
|
|
= 1.0 / std::abs(fTotal);
|
|
const double wC
|
|
= s * signedCross(aP, aV0, aV1) * fInvAbsTotal;
|
|
const double w0
|
|
= s * signedCross(aCentre, aP, aV1) * fInvAbsTotal;
|
|
const double w1
|
|
= s * signedCross(aCentre, aV0, aP) * fInvAbsTotal;
|
|
const double fMin
|
|
= std::min({wC, w0, w1});
|
|
if (fMin > fMaxNegMargin)
|
|
{
|
|
fMaxNegMargin = fMin;
|
|
nClosestI = i;
|
|
fClosestW0 = w0;
|
|
fClosestW1 = w1;
|
|
}
|
|
}
|
|
// The pixel is outside its closest
|
|
// triangle (wC < 0), so the centre/
|
|
// edge parameter clamps to 1 and the
|
|
// colour is the pure edge mix.
|
|
basegfx::BColor aColor;
|
|
double fAlpha = 0.0;
|
|
composeColor(nClosestI, fClosestW0,
|
|
fClosestW1, 1.0,
|
|
aColor, fAlpha);
|
|
writePixel(x, y, aColor, fAlpha);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
Graphic aGraphic(Bitmap(aBmp, aAlpha));
|
|
|
|
// Map bitmap [0,1] to the brush's bounds in raw EMF
|
|
// coords (with translation so the bitmap is anchored
|
|
// where the brush actually lives, not at world origin).
|
|
// maMapTransform then converts to world space, where
|
|
// the fill polygon also lives - so the bitmap content
|
|
// aligns with the fill polygon.
|
|
const basegfx::B2DHomMatrix aBitmapToBrushSpace(
|
|
aBrushBounds.getWidth(), 0.0, aBrushBounds.getMinX(),
|
|
0.0, aBrushBounds.getHeight(), aBrushBounds.getMinY());
|
|
|
|
basegfx::B2DHomMatrix aBrushMatrix;
|
|
if (brush->hasTransformation)
|
|
aBrushMatrix = brush->brush_transformation;
|
|
|
|
const basegfx::B2DHomMatrix aTotalMatrix
|
|
= maMapTransform * aBrushMatrix * aBitmapToBrushSpace;
|
|
|
|
basegfx::B2DHomMatrix aInvTotalMatrix = aTotalMatrix;
|
|
if (!aInvTotalMatrix.invert())
|
|
return;
|
|
basegfx::B2DPolyPolygon aLocalPolygon = polygon;
|
|
aLocalPolygon.transform(aInvTotalMatrix);
|
|
|
|
const bool bIsTiled = (brush->wrapMode != WrapModeClamp);
|
|
|
|
const drawinglayer::attribute::FillGraphicAttribute aFillAttribute(
|
|
aGraphic, basegfx::B2DRange(0.0, 0.0, 1.0, 1.0), bIsTiled);
|
|
|
|
drawinglayer::primitive2d::Primitive2DReference xFillPrimitive
|
|
= new drawinglayer::primitive2d::PolyPolygonGraphicPrimitive2D(
|
|
aLocalPolygon,
|
|
basegfx::B2DRange(0.0, 0.0, 1.0, 1.0),
|
|
aFillAttribute);
|
|
|
|
mrTargetHolders.Current().append(
|
|
new drawinglayer::primitive2d::TransformPrimitive2D(
|
|
aTotalMatrix,
|
|
drawinglayer::primitive2d::Primitive2DContainer({ xFillPrimitive })));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
const sal_uInt16 LOWERGAMMA = 1000;
|
|
const sal_uInt16 UPPERGAMMA = 2200;
|
|
|
|
EmfPlusHelperData::EmfPlusHelperData(
|
|
SvMemoryStream& rMS,
|
|
wmfemfhelper::TargetHolders& rTargetHolders,
|
|
wmfemfhelper::PropertyHolders& rPropertyHolders)
|
|
: mfPageScaleX(1.0),
|
|
mfPageScaleY(1.0),
|
|
mnOriginX(0),
|
|
mnOriginY(0),
|
|
mnHDPI(0),
|
|
mnVDPI(0),
|
|
mbSetTextContrast(false),
|
|
mnTextContrast(0),
|
|
mnFrameLeft(0),
|
|
mnFrameTop(0),
|
|
mnFrameRight(0),
|
|
mnFrameBottom(0),
|
|
mnPixX(0),
|
|
mnPixY(0),
|
|
mnMmX(0),
|
|
mnMmY(0),
|
|
mbMultipart(false),
|
|
mMFlags(0),
|
|
mdExtractedXScale(1.0),
|
|
mdExtractedYScale(1.0),
|
|
mrTargetHolders(rTargetHolders),
|
|
mrPropertyHolders(rPropertyHolders),
|
|
bIsGetDCProcessing(false)
|
|
{
|
|
rMS.ReadInt32(mnFrameLeft).ReadInt32(mnFrameTop).ReadInt32(mnFrameRight).ReadInt32(mnFrameBottom);
|
|
SAL_INFO("drawinglayer.emf", "EMF+ picture frame: " << mnFrameLeft << "," << mnFrameTop << " - " << mnFrameRight << "," << mnFrameBottom);
|
|
rMS.ReadInt32(mnPixX).ReadInt32(mnPixY).ReadInt32(mnMmX).ReadInt32(mnMmY);
|
|
SAL_INFO("drawinglayer.emf", "EMF+ ref device pixel size: " << mnPixX << "x" << mnPixY << " mm size: " << mnMmX << "x" << mnMmY);
|
|
readXForm(rMS, maBaseTransform);
|
|
SAL_INFO("drawinglayer.emf", "EMF+ base transform: " << maBaseTransform);
|
|
mappingChanged();
|
|
}
|
|
|
|
EmfPlusHelperData::~EmfPlusHelperData()
|
|
{
|
|
}
|
|
|
|
::basegfx::B2DPolyPolygon EmfPlusHelperData::combineClip(::basegfx::B2DPolyPolygon const & leftPolygon, int combineMode, ::basegfx::B2DPolyPolygon const & rightPolygon)
|
|
{
|
|
basegfx::B2DPolyPolygon aClippedPolyPolygon;
|
|
switch (combineMode)
|
|
{
|
|
case EmfPlusCombineModeReplace:
|
|
{
|
|
aClippedPolyPolygon = rightPolygon;
|
|
break;
|
|
}
|
|
case EmfPlusCombineModeIntersect:
|
|
{
|
|
aClippedPolyPolygon = basegfx::utils::clipPolyPolygonOnPolyPolygon(
|
|
leftPolygon, rightPolygon, true, false);
|
|
break;
|
|
}
|
|
case EmfPlusCombineModeUnion:
|
|
{
|
|
aClippedPolyPolygon = ::basegfx::utils::solvePolygonOperationOr(leftPolygon, rightPolygon);
|
|
break;
|
|
}
|
|
case EmfPlusCombineModeXOR:
|
|
{
|
|
aClippedPolyPolygon = ::basegfx::utils::solvePolygonOperationXor(leftPolygon, rightPolygon);
|
|
break;
|
|
}
|
|
case EmfPlusCombineModeExclude:
|
|
{
|
|
// Replaces the existing region with the part of itself that is not in the new region.
|
|
aClippedPolyPolygon = ::basegfx::utils::solvePolygonOperationDiff(leftPolygon, rightPolygon);
|
|
break;
|
|
}
|
|
case EmfPlusCombineModeComplement:
|
|
{
|
|
// Replaces the existing region with the part of the new region that is not in the existing region.
|
|
aClippedPolyPolygon = ::basegfx::utils::solvePolygonOperationDiff(rightPolygon, leftPolygon);
|
|
break;
|
|
}
|
|
}
|
|
return aClippedPolyPolygon;
|
|
}
|
|
|
|
void EmfPlusHelperData::processEmfPlusData(
|
|
SvMemoryStream& rMS,
|
|
const drawinglayer::geometry::ViewInformation2D& /*rViewInformation*/)
|
|
{
|
|
sal_uInt64 length = rMS.GetSize();
|
|
|
|
if (length < 12)
|
|
SAL_WARN("drawinglayer.emf", "length is less than required header size");
|
|
|
|
// 12 is minimal valid EMF+ record size; remaining bytes are padding
|
|
while (length >= 12)
|
|
{
|
|
sal_uInt16 type(0), flags(0);
|
|
sal_uInt32 size(0), dataSize(0);
|
|
|
|
rMS.ReadUInt16(type).ReadUInt16(flags).ReadUInt32(size).ReadUInt32(dataSize);
|
|
|
|
if (size < 12)
|
|
{
|
|
SAL_WARN("drawinglayer.emf", "Size field is less than 12 bytes");
|
|
break;
|
|
}
|
|
else if (size > length)
|
|
{
|
|
SAL_WARN("drawinglayer.emf", "Size field is greater than bytes left");
|
|
break;
|
|
}
|
|
|
|
sal_uInt64 next = rMS.Tell() + (size - 12);
|
|
|
|
if (dataSize > (size - 12))
|
|
{
|
|
SAL_WARN("drawinglayer.emf", "DataSize field is greater than Size-12");
|
|
break;
|
|
}
|
|
|
|
SAL_INFO("drawinglayer.emf", "EMF+ " << emfTypeToName(type) << " (0x" << std::hex << type << ")" << std::dec);
|
|
SAL_INFO("drawinglayer.emf", "EMF+\t record size: " << size);
|
|
SAL_INFO("drawinglayer.emf", "EMF+\t flags: 0x" << std::hex << flags << std::dec);
|
|
SAL_INFO("drawinglayer.emf", "EMF+\t data size: " << dataSize);
|
|
|
|
if (bIsGetDCProcessing)
|
|
{
|
|
if (aGetDCState.getClipPolyPolygonActive())
|
|
{
|
|
SAL_INFO("drawinglayer.emf", "EMF+\t Restore region to GetDC saved");
|
|
wmfemfhelper::HandleNewClipRegion(aGetDCState.getClipPolyPolygon(), mrTargetHolders,
|
|
mrPropertyHolders);
|
|
}
|
|
else
|
|
{
|
|
SAL_INFO("drawinglayer.emf", "EMF+\t Disable clipping");
|
|
wmfemfhelper::HandleNewClipRegion(::basegfx::B2DPolyPolygon(), mrTargetHolders,
|
|
mrPropertyHolders);
|
|
}
|
|
bIsGetDCProcessing = false;
|
|
}
|
|
if (type == EmfPlusRecordTypeObject && ((mbMultipart && (flags & 0x7fff) == (mMFlags & 0x7fff)) || (flags & 0x8000)))
|
|
{
|
|
if (dataSize < 4)
|
|
{
|
|
SAL_WARN("drawinglayer.emf", "DataSize field has no room for TotalObjectSize");
|
|
break;
|
|
}
|
|
|
|
if (!mbMultipart)
|
|
{
|
|
mbMultipart = true;
|
|
mMFlags = flags;
|
|
mMStream.Seek(0);
|
|
}
|
|
|
|
// 1st 4 bytes are TotalObjectSize
|
|
mMStream.WriteBytes(static_cast<const char *>(rMS.GetData()) + rMS.Tell() + 4, dataSize - 4);
|
|
SAL_INFO("drawinglayer.emf", "EMF+ read next object part size: " << size << " type: " << type << " flags: " << flags << " data size: " << dataSize);
|
|
}
|
|
else
|
|
{
|
|
if (mbMultipart)
|
|
{
|
|
SAL_INFO("drawinglayer.emf", "EMF+ multipart record flags: " << mMFlags);
|
|
mMStream.Seek(0);
|
|
processObjectRecord(mMStream, mMFlags, 0, true);
|
|
}
|
|
|
|
mbMultipart = false;
|
|
}
|
|
|
|
if (type != EmfPlusRecordTypeObject || !(flags & 0x8000))
|
|
{
|
|
switch (type)
|
|
{
|
|
case EmfPlusRecordTypeHeader:
|
|
{
|
|
sal_uInt32 version(0), emfPlusFlags(0);
|
|
SAL_INFO("drawinglayer.emf", "EMF+\tDual: " << ((flags & 1) ? "true" : "false"));
|
|
|
|
rMS.ReadUInt32(version).ReadUInt32(emfPlusFlags).ReadUInt32(mnHDPI).ReadUInt32(mnVDPI);
|
|
SAL_INFO("drawinglayer.emf", "EMF+\tVersion: 0x" << std::hex << version);
|
|
SAL_INFO("drawinglayer.emf", "EMF+\tEmf+ Flags: 0x" << emfPlusFlags << std::dec);
|
|
SAL_INFO("drawinglayer.emf", "EMF+\tMetafile was recorded with a reference device context for " << ((emfPlusFlags & 1) ? "video display" : "printer"));
|
|
SAL_INFO("drawinglayer.emf", "EMF+\tHorizontal DPI: " << mnHDPI);
|
|
SAL_INFO("drawinglayer.emf", "EMF+\tVertical DPI: " << mnVDPI);
|
|
break;
|
|
}
|
|
case EmfPlusRecordTypeEndOfFile:
|
|
{
|
|
break;
|
|
}
|
|
case EmfPlusRecordTypeComment:
|
|
{
|
|
#if OSL_DEBUG_LEVEL > 1
|
|
unsigned char data;
|
|
OUString hexdata;
|
|
|
|
SAL_INFO("drawinglayer.emf", "EMF+\tDatasize: 0x" << std::hex << dataSize << std::dec);
|
|
|
|
for (sal_uInt32 i=0; i<dataSize; i++)
|
|
{
|
|
rMS.ReadUChar(data);
|
|
|
|
if (i % 16 == 0)
|
|
hexdata += "\n";
|
|
|
|
OUString padding;
|
|
if ((data & 0xF0) == 0)
|
|
padding = "0";
|
|
|
|
hexdata += "0x" + padding + OUString::number(data, 16) + " ";
|
|
}
|
|
|
|
SAL_INFO("drawinglayer.emf", "EMF+\t" << hexdata);
|
|
#endif
|
|
break;
|
|
}
|
|
case EmfPlusRecordTypeGetDC:
|
|
{
|
|
bIsGetDCProcessing = true;
|
|
aGetDCState = mrPropertyHolders.Current();
|
|
SAL_INFO("drawinglayer.emf", "EMF+\tAlready used in svtools wmf/emf filter parser");
|
|
break;
|
|
}
|
|
case EmfPlusRecordTypeObject:
|
|
{
|
|
processObjectRecord(rMS, flags, dataSize);
|
|
break;
|
|
}
|
|
case EmfPlusRecordTypeFillPie:
|
|
case EmfPlusRecordTypeDrawPie:
|
|
case EmfPlusRecordTypeDrawArc:
|
|
{
|
|
float startAngle(0), sweepAngle(0);
|
|
|
|
// Silent MSVC warning C4701: potentially uninitialized local variable 'brushIndexOrColor' used
|
|
sal_uInt32 brushIndexOrColor = 999;
|
|
|
|
if (type == EmfPlusRecordTypeFillPie)
|
|
{
|
|
rMS.ReadUInt32(brushIndexOrColor);
|
|
SAL_INFO("drawinglayer.emf", "EMF+\t FillPie colorOrIndex: " << brushIndexOrColor);
|
|
}
|
|
else if (type == EmfPlusRecordTypeDrawPie)
|
|
{
|
|
SAL_INFO("drawinglayer.emf", "EMF+\t DrawPie");
|
|
}
|
|
else
|
|
{
|
|
SAL_INFO("drawinglayer.emf", "EMF+\t DrawArc");
|
|
}
|
|
|
|
rMS.ReadFloat(startAngle).ReadFloat(sweepAngle);
|
|
float dx(0), dy(0), dw(0), dh(0);
|
|
ReadRectangle(rMS, dx, dy, dw, dh, bool(flags & 0x4000));
|
|
SAL_INFO("drawinglayer.emf", "EMF+\t RectData: " << dx << "," << dy << " " << dw << "x" << dh);
|
|
startAngle = basegfx::deg2rad(startAngle);
|
|
sweepAngle = basegfx::deg2rad(sweepAngle);
|
|
float endAngle = startAngle + sweepAngle;
|
|
startAngle = fmodf(startAngle, static_cast<float>(M_PI * 2));
|
|
|
|
if (startAngle < 0.0)
|
|
{
|
|
startAngle += static_cast<float>(M_PI * 2.0);
|
|
}
|
|
endAngle = fmodf(endAngle, static_cast<float>(M_PI * 2.0));
|
|
|
|
if (endAngle < 0.0)
|
|
{
|
|
endAngle += static_cast<float>(M_PI * 2.0);
|
|
}
|
|
if (sweepAngle < 0)
|
|
{
|
|
std::swap(endAngle, startAngle);
|
|
}
|
|
|
|
SAL_INFO("drawinglayer.emf", "EMF+\t Adjusted angles: start " <<
|
|
basegfx::rad2deg(startAngle) << ", end: " << basegfx::rad2deg(endAngle) <<
|
|
" startAngle: " << startAngle << " sweepAngle: " << sweepAngle);
|
|
const ::basegfx::B2DPoint centerPoint(dx + 0.5 * dw, dy + 0.5 * dh);
|
|
::basegfx::B2DPolygon polygon(
|
|
::basegfx::utils::createPolygonFromEllipseSegment(centerPoint,
|
|
0.5 * dw, 0.5 * dh,
|
|
startAngle, endAngle));
|
|
if (type != EmfPlusRecordTypeDrawArc)
|
|
{
|
|
polygon.append(centerPoint);
|
|
polygon.setClosed(true);
|
|
}
|
|
::basegfx::B2DPolyPolygon polyPolygon(polygon);
|
|
polyPolygon.transform(maMapTransform);
|
|
if (type == EmfPlusRecordTypeFillPie)
|
|
EMFPPlusFillPolygon(polyPolygon, flags & 0x8000, brushIndexOrColor);
|
|
else
|
|
EMFPPlusDrawPolygon(polyPolygon, flags & 0xff);
|
|
}
|
|
break;
|
|
case EmfPlusRecordTypeFillPath:
|
|
{
|
|
sal_uInt32 index = flags & 0xff;
|
|
sal_uInt32 brushIndexOrColor(0);
|
|
rMS.ReadUInt32(brushIndexOrColor);
|
|
SAL_INFO("drawinglayer.emf", "EMF+ FillPath slot: " << index);
|
|
|
|
EMFPPath* path = dynamic_cast<EMFPPath*>(maEMFPObjects[index].get());
|
|
if (path)
|
|
EMFPPlusFillPolygon(path->GetPolygon(*this), flags & 0x8000, brushIndexOrColor);
|
|
else
|
|
SAL_WARN("drawinglayer.emf", "EMF+\tEmfPlusRecordTypeFillPath missing path");
|
|
}
|
|
break;
|
|
case EmfPlusRecordTypeFillRegion:
|
|
{
|
|
sal_uInt32 index = flags & 0xff;
|
|
sal_uInt32 brushIndexOrColor(0);
|
|
rMS.ReadUInt32(brushIndexOrColor);
|
|
SAL_INFO("drawinglayer.emf", "EMF+\t FillRegion slot: " << index);
|
|
|
|
EMFPRegion* region = dynamic_cast<EMFPRegion*>(maEMFPObjects[flags & 0xff].get());
|
|
if (region)
|
|
EMFPPlusFillPolygon(region->regionPolyPolygon, flags & 0x8000, brushIndexOrColor);
|
|
else
|
|
SAL_WARN("drawinglayer.emf", "EMF+\tEmfPlusRecordTypeFillRegion missing region");
|
|
}
|
|
break;
|
|
case EmfPlusRecordTypeDrawEllipse:
|
|
case EmfPlusRecordTypeFillEllipse:
|
|
{
|
|
// Intentionally very bogus initial value to avoid MSVC complaining about potentially uninitialized local
|
|
// variable. As long as the code stays as intended, this variable will be assigned a (real) value in the case
|
|
// when it is later used.
|
|
sal_uInt32 brushIndexOrColor = 1234567;
|
|
|
|
if (type == EmfPlusRecordTypeFillEllipse)
|
|
{
|
|
rMS.ReadUInt32(brushIndexOrColor);
|
|
}
|
|
|
|
SAL_INFO("drawinglayer.emf", "EMF+\t " << (type == EmfPlusRecordTypeFillEllipse ? "Fill" : "Draw") << "Ellipse slot: " << (flags & 0xff));
|
|
float dx(0), dy(0), dw(0), dh(0);
|
|
ReadRectangle(rMS, dx, dy, dw, dh, bool(flags & 0x4000));
|
|
SAL_INFO("drawinglayer.emf", "EMF+\t RectData: " << dx << "," << dy << " " << dw << "x" << dh);
|
|
::basegfx::B2DPolyPolygon polyPolygon(
|
|
::basegfx::utils::createPolygonFromEllipse(::basegfx::B2DPoint(dx + 0.5 * dw, dy + 0.5 * dh),
|
|
0.5 * dw, 0.5 * dh));
|
|
polyPolygon.transform(maMapTransform);
|
|
if (type == EmfPlusRecordTypeFillEllipse)
|
|
EMFPPlusFillPolygon(polyPolygon, flags & 0x8000, brushIndexOrColor);
|
|
else
|
|
EMFPPlusDrawPolygon(polyPolygon, flags & 0xff);
|
|
}
|
|
break;
|
|
case EmfPlusRecordTypeFillRects:
|
|
case EmfPlusRecordTypeDrawRects:
|
|
{
|
|
// Silent MSVC warning C4701: potentially uninitialized local variable 'brushIndexOrColor' used
|
|
sal_uInt32 brushIndexOrColor = 999;
|
|
::basegfx::B2DPolyPolygon polyPolygon;
|
|
sal_uInt32 rectangles(0);
|
|
float x(0), y(0), width(0), height(0);
|
|
const bool isColor = (flags & 0x8000);
|
|
::basegfx::B2DPolygon polygon;
|
|
|
|
if (EmfPlusRecordTypeFillRects == type)
|
|
{
|
|
SAL_INFO("drawinglayer.emf", "EMF+\t FillRects");
|
|
rMS.ReadUInt32(brushIndexOrColor);
|
|
SAL_INFO("drawinglayer.emf", "EMF+\t" << (isColor ? "color" : "brush index") << ": 0x" << std::hex << brushIndexOrColor << std::dec);
|
|
}
|
|
else
|
|
{
|
|
SAL_INFO("drawinglayer.emf", "EMF+\t DrawRects");
|
|
}
|
|
|
|
rMS.ReadUInt32(rectangles);
|
|
// The record that is left says how many rectangles there can really be,
|
|
// whatever count the file gave.
|
|
const sal_uInt32 nBytesPerRectangle = (flags & 0x4000) ? 8 : 16;
|
|
const sal_uInt64 nRecordLeft
|
|
= rMS.Tell() < next ? std::min(next - rMS.Tell(), rMS.remainingSize())
|
|
: 0;
|
|
const sal_uInt64 nMaxRectangles = nRecordLeft / nBytesPerRectangle;
|
|
if (rectangles > nMaxRectangles)
|
|
{
|
|
SAL_WARN("drawinglayer.emf", "EMF+\twants " << rectangles
|
|
<< " rectangles, the record holds " << nMaxRectangles);
|
|
rectangles = static_cast<sal_uInt32>(nMaxRectangles);
|
|
}
|
|
for (sal_uInt32 i = 0; i < rectangles; i++)
|
|
{
|
|
ReadRectangle(rMS, x, y, width, height, bool(flags & 0x4000));
|
|
polygon.clear();
|
|
polygon.append(Map(x, y));
|
|
polygon.append(Map(x + width, y));
|
|
polygon.append(Map(x + width, y + height));
|
|
polygon.append(Map(x, y + height));
|
|
polygon.setClosed(true);
|
|
|
|
SAL_INFO("drawinglayer.emf", "EMF+\t\t rectangle: " << x << ", "<< y << " " << width << "x" << height);
|
|
|
|
if (type == EmfPlusRecordTypeFillRects)
|
|
{
|
|
// Per MS-EMFPLUS each rect fills its
|
|
// interior independently. Emitting them
|
|
// as one polypolygon would cancel
|
|
// pairwise overlaps under drawinglayer's
|
|
// even-odd fill rule, leaving hollow
|
|
// stripes where overlapping squares meet.
|
|
EMFPPlusFillPolygon(::basegfx::B2DPolyPolygon(polygon),
|
|
isColor, brushIndexOrColor);
|
|
}
|
|
else
|
|
{
|
|
polyPolygon.append(polygon);
|
|
}
|
|
}
|
|
if (type == EmfPlusRecordTypeDrawRects)
|
|
EMFPPlusDrawPolygon(polyPolygon, flags & 0xff);
|
|
break;
|
|
}
|
|
case EmfPlusRecordTypeFillPolygon:
|
|
{
|
|
sal_uInt32 brushIndexOrColor(0), points(0);
|
|
|
|
rMS.ReadUInt32(brushIndexOrColor);
|
|
rMS.ReadUInt32(points);
|
|
SAL_INFO("drawinglayer.emf", "EMF+\t Points: " << points);
|
|
SAL_INFO("drawinglayer.emf", "EMF+\t " << ((flags & 0x8000) ? "Color" : "Brush index") << " : 0x" << std::hex << brushIndexOrColor << std::dec);
|
|
|
|
EMFPPath path(points, false/*bHasPointTypes*/);
|
|
path.Read(rMS, flags);
|
|
|
|
EMFPPlusFillPolygon(path.GetPolygon(*this), flags & 0x8000, brushIndexOrColor);
|
|
break;
|
|
}
|
|
case EmfPlusRecordTypeDrawLines:
|
|
{
|
|
sal_uInt32 points(0);
|
|
rMS.ReadUInt32(points);
|
|
SAL_INFO("drawinglayer.emf", "EMF+\t Points: " << points);
|
|
EMFPPath path(points, false/*bHasPointTypes*/);
|
|
path.Read(rMS, flags);
|
|
|
|
// 0x2000 bit indicates whether to draw an extra line between the last point
|
|
// and the first point, to close the shape.
|
|
EMFPPlusDrawPolygon(path.GetPolygon(*this, true, (flags & 0x2000)), flags);
|
|
|
|
break;
|
|
}
|
|
case EmfPlusRecordTypeDrawPath:
|
|
{
|
|
sal_uInt32 penIndex(0);
|
|
rMS.ReadUInt32(penIndex);
|
|
SAL_INFO("drawinglayer.emf", "EMF+\t Pen: " << penIndex);
|
|
|
|
EMFPPath* path = dynamic_cast<EMFPPath*>(maEMFPObjects[flags & 0xff].get());
|
|
if (path)
|
|
EMFPPlusDrawPolygon(path->GetPolygon(*this), penIndex);
|
|
else
|
|
SAL_WARN("drawinglayer.emf", "\t\tEmfPlusRecordTypeDrawPath missing path");
|
|
|
|
break;
|
|
}
|
|
case EmfPlusRecordTypeDrawBeziers:
|
|
{
|
|
sal_uInt32 aCount(0);
|
|
float x1(0), y1(0), x2(0), y2(0), x3(0), y3(0), x4(0), y4(0);
|
|
::basegfx::B2DPolygon aPolygon;
|
|
rMS.ReadUInt32(aCount);
|
|
SAL_INFO("drawinglayer.emf", "EMF+\t DrawBeziers slot: " << (flags & 0xff));
|
|
SAL_INFO("drawinglayer.emf", "EMF+\t Number of points: " << aCount);
|
|
SAL_WARN_IF((aCount - 1) % 3 != 0, "drawinglayer.emf",
|
|
"EMF+\t Bezier Draw not support number of points other than 4, 7, "
|
|
"10, 13, 16...");
|
|
|
|
const sal_uInt32 nBytesPerPoint = (flags & 0x4000) ? 4 : 8;
|
|
const sal_uInt64 nRecordLeft = rMS.Tell() < next ? next - rMS.Tell() : 0;
|
|
const sal_uInt64 nMaxPoints = nRecordLeft / nBytesPerPoint;
|
|
if (aCount > nMaxPoints)
|
|
{
|
|
SAL_WARN("drawinglayer.emf", "EMF+\twants " << aCount
|
|
<< " points, the record holds " << nMaxPoints);
|
|
aCount = static_cast<sal_uInt32>(nMaxPoints);
|
|
}
|
|
|
|
if (aCount < 4)
|
|
{
|
|
SAL_WARN("drawinglayer.emf", "EMF+\t Bezier Draw does not support less "
|
|
"than 4 points. Number of points: "
|
|
<< aCount);
|
|
break;
|
|
}
|
|
|
|
ReadPoint(rMS, x1, y1, flags);
|
|
// We need to add first starting point
|
|
aPolygon.append(Map(x1, y1));
|
|
SAL_INFO("drawinglayer.emf",
|
|
"EMF+\t Bezier starting point: " << x1 << "," << y1);
|
|
for (sal_uInt32 i = 4; i <= aCount; i += 3)
|
|
{
|
|
ReadPoint(rMS, x2, y2, flags);
|
|
ReadPoint(rMS, x3, y3, flags);
|
|
ReadPoint(rMS, x4, y4, flags);
|
|
|
|
SAL_INFO("drawinglayer.emf",
|
|
"EMF+\t Bezier points: " << x2 << "," << y2 << " " << x3 << ","
|
|
<< y3 << " " << x4 << "," << y4);
|
|
aPolygon.appendBezierSegment(Map(x2, y2), Map(x3, y3), Map(x4, y4));
|
|
}
|
|
EMFPPlusDrawPolygon(::basegfx::B2DPolyPolygon(aPolygon), flags & 0xff);
|
|
break;
|
|
}
|
|
case EmfPlusRecordTypeDrawCurve:
|
|
{
|
|
sal_uInt32 aOffset(0), aNumSegments(0), points(0);
|
|
float aTension(0);
|
|
rMS.ReadFloat(aTension);
|
|
rMS.ReadUInt32(aOffset);
|
|
rMS.ReadUInt32(aNumSegments);
|
|
rMS.ReadUInt32(points);
|
|
SAL_WARN("drawinglayer.emf",
|
|
"EMF+\t Tension: " << aTension << " Offset: " << aOffset
|
|
<< " NumSegments: " << aNumSegments
|
|
<< " Points: " << points);
|
|
|
|
EMFPPath path(points, false/*bHasPointTypes*/);
|
|
path.Read(rMS, flags);
|
|
|
|
if (points >= 2)
|
|
EMFPPlusDrawPolygon(
|
|
path.GetCardinalSpline(*this, aTension, aOffset, aNumSegments),
|
|
flags & 0xff);
|
|
else
|
|
SAL_WARN("drawinglayer.emf", "Not enough number of points");
|
|
break;
|
|
}
|
|
case EmfPlusRecordTypeDrawClosedCurve:
|
|
case EmfPlusRecordTypeFillClosedCurve:
|
|
{
|
|
// Silent MSVC warning C4701: potentially uninitialized local variable 'brushIndexOrColor' used
|
|
sal_uInt32 brushIndexOrColor = 999, points;
|
|
float aTension(0);
|
|
if (type == EmfPlusRecordTypeFillClosedCurve)
|
|
{
|
|
rMS.ReadUInt32(brushIndexOrColor);
|
|
SAL_INFO(
|
|
"drawinglayer.emf",
|
|
"EMF+\t Fill Mode: " << (flags & 0x2000 ? "Winding" : "Alternate"));
|
|
}
|
|
rMS.ReadFloat(aTension);
|
|
rMS.ReadUInt32(points);
|
|
SAL_WARN("drawinglayer.emf",
|
|
"EMF+\t Tension: " << aTension << " Points: " << points);
|
|
SAL_INFO("drawinglayer.emf",
|
|
"EMF+\t " << (flags & 0x8000 ? "Color" : "Brush index") << " : 0x"
|
|
<< std::hex << brushIndexOrColor << std::dec);
|
|
if (points < 3)
|
|
{
|
|
SAL_WARN("drawinglayer.emf", "Not enough number of points");
|
|
break;
|
|
}
|
|
EMFPPath path(points, false/*bHasPointTypes*/);
|
|
path.Read(rMS, flags);
|
|
if (type == EmfPlusRecordTypeFillClosedCurve)
|
|
{
|
|
::basegfx::B2DPolyPolygon aPoly
|
|
= path.GetClosedCardinalSpline(*this, aTension);
|
|
if (flags & 0x2000)
|
|
aPoly = convertWindingToEvenOdd(aPoly);
|
|
EMFPPlusFillPolygon(aPoly, flags & 0x8000, brushIndexOrColor);
|
|
}
|
|
else
|
|
EMFPPlusDrawPolygon(path.GetClosedCardinalSpline(*this, aTension),
|
|
flags & 0xff);
|
|
break;
|
|
}
|
|
case EmfPlusRecordTypeDrawImage:
|
|
case EmfPlusRecordTypeDrawImagePoints:
|
|
{
|
|
sal_uInt32 imageAttributesId(0);
|
|
sal_Int32 sourceUnit(0);
|
|
rMS.ReadUInt32(imageAttributesId).ReadInt32(sourceUnit);
|
|
SAL_INFO("drawinglayer.emf",
|
|
"EMF+\t " << (type == EmfPlusRecordTypeDrawImage ? "DrawImage"
|
|
: "DrawImagePoints")
|
|
<< " image attributes Id: " << imageAttributesId
|
|
<< " source unit: " << sourceUnit);
|
|
SAL_INFO("drawinglayer.emf", "EMF+\t TODO: use image attributes");
|
|
|
|
// Source unit of measurement type must be 1 pixel
|
|
if (EMFPImage* image = sourceUnit == UnitTypePixel ?
|
|
dynamic_cast<EMFPImage*>(maEMFPObjects[flags & 0xff].get()) :
|
|
nullptr)
|
|
{
|
|
float sx(0), sy(0), sw(0), sh(0);
|
|
ReadRectangle(rMS, sx, sy, sw, sh);
|
|
|
|
::tools::Rectangle aSource(Point(sx, sy), Size(sw + 1, sh + 1));
|
|
SAL_INFO("drawinglayer.emf",
|
|
"EMF+\t "
|
|
<< (type == EmfPlusRecordTypeDrawImage ? "DrawImage"
|
|
: "DrawImagePoints")
|
|
<< " source rectangle: " << sx << "," << sy << " " << sw << "x"
|
|
<< sh);
|
|
|
|
float dx(0.), dy(0.), dw(0.), dh(0.);
|
|
double fShearX = 0.0;
|
|
double fShearY = 0.0;
|
|
if (type == EmfPlusRecordTypeDrawImagePoints)
|
|
{
|
|
sal_uInt32 aCount(0);
|
|
rMS.ReadUInt32(aCount);
|
|
|
|
// Number of points used by DrawImagePoints. Exactly 3 points must be specified.
|
|
if (aCount != 3)
|
|
{
|
|
SAL_WARN("drawinglayer.emf", "EMF+\t Wrong EMF+ file. Expected "
|
|
"3 points, received: "
|
|
<< aCount);
|
|
break;
|
|
}
|
|
float x1(0), y1(0), x2(0), y2(0), x3(0), y3(0);
|
|
|
|
ReadPoint(rMS, x1, y1, flags); // upper-left point
|
|
ReadPoint(rMS, x2, y2, flags); // upper-right
|
|
ReadPoint(rMS, x3, y3, flags); // lower-left
|
|
|
|
dx = x1;
|
|
dy = y1;
|
|
dw = x2 - x1;
|
|
dh = y3 - y1;
|
|
fShearX = x3 - x1;
|
|
fShearY = y2 - y1;
|
|
}
|
|
else if (type == EmfPlusRecordTypeDrawImage)
|
|
ReadRectangle(rMS, dx, dy, dw, dh, bool(flags & 0x4000));
|
|
|
|
SAL_INFO("drawinglayer.emf",
|
|
"EMF+\t Destination rectangle: " << dx << "," << dy << " " << dw << "x" << dh);
|
|
Size aSize;
|
|
if (image->type == ImageDataTypeBitmap)
|
|
{
|
|
aSize = image->graphic.GetBitmap().GetSizePixel();
|
|
SAL_INFO("drawinglayer.emf", "EMF+\t Bitmap size: " << aSize.Width()
|
|
<< "x"
|
|
<< aSize.Height());
|
|
if (sx < 0)
|
|
{
|
|
// If src position is negative then we need shift image to right
|
|
dx = dx + ((-sx) / sw) * dw;
|
|
if (sx + sw <= aSize.Width())
|
|
dw = ((sw + sx) / sw) * dw;
|
|
else
|
|
dw = (aSize.Width() / sw) * dw;
|
|
}
|
|
else if (sx + sw > aSize.Width())
|
|
// If the src image is smaller that what we want to cut, then we need to scale down
|
|
dw = ((aSize.Width() - sx) / sw) * dw;
|
|
|
|
if (sy < 0)
|
|
{
|
|
dy = dy + ((-sy) / sh) * dh;
|
|
if (sy + sh <= aSize.Height())
|
|
dh = ((sh + sy) / sh) * dh;
|
|
else
|
|
dh = (aSize.Height() / sh) * dh;
|
|
}
|
|
else if (sy + sh > aSize.Height())
|
|
dh = ((aSize.Height() - sy) / sh) * dh;
|
|
}
|
|
|
|
const basegfx::B2DHomMatrix aDestMatrix =
|
|
maMapTransform * basegfx::B2DHomMatrix(
|
|
/* Row 0, Column 0 */ dw,
|
|
/* Row 0, Column 1 */ fShearX,
|
|
/* Row 0, Column 2 */ dx,
|
|
/* Row 1, Column 0 */ fShearY,
|
|
/* Row 1, Column 1 */ dh,
|
|
/* Row 1, Column 2 */ dy);
|
|
|
|
if (image->type == ImageDataTypeBitmap)
|
|
{
|
|
Bitmap aBmp(image->graphic.GetBitmap());
|
|
aBmp.Crop(aSource);
|
|
aSize = aBmp.GetSizePixel();
|
|
if (aSize.Width() > 0 && aSize.Height() > 0)
|
|
{
|
|
mrTargetHolders.Current().append(
|
|
new drawinglayer::primitive2d::BitmapPrimitive2D(
|
|
aBmp, aDestMatrix));
|
|
}
|
|
else
|
|
SAL_WARN("drawinglayer.emf", "EMF+\t warning: empty bitmap");
|
|
}
|
|
else if (image->type == ImageDataTypeMetafile)
|
|
{
|
|
SAL_INFO("drawinglayer.emf",
|
|
"\t\t Metafile Dimensions: " << image->x << "," << image->y
|
|
<< " [" << image->width << "x"
|
|
<< image->height);
|
|
|
|
const GDIMetaFile aGDI(image->graphic.GetGDIMetaFile());
|
|
basegfx::B2DHomMatrix aFinalMatrix = aDestMatrix;
|
|
if (image->width > 0 && image->height > 0 && sw > 0. && sh > 0.)
|
|
{
|
|
const double fRelSrcX = sx - static_cast<double>(image->x);
|
|
const double fRelSrcY = sy - static_cast<double>(image->y);
|
|
const basegfx::B2DHomMatrix aNormalizeMatrix(
|
|
/* Row 0, Column 0 */ static_cast<double>(image->width) / sw,
|
|
/* Row 0, Column 1 */ 0.0,
|
|
/* Row 0, Column 2 */ -fRelSrcX / sw,
|
|
/* Row 1, Column 0 */ 0.0,
|
|
/* Row 1, Column 1 */ static_cast<double>(image->height) / sh,
|
|
/* Row 1, Column 2 */ -fRelSrcY / sh);
|
|
|
|
aFinalMatrix = aDestMatrix * aNormalizeMatrix;
|
|
}
|
|
|
|
basegfx::B2DPolygon aClipPoly = basegfx::utils::createUnitPolygon();
|
|
aClipPoly.transform(aDestMatrix);
|
|
|
|
mrTargetHolders.Current().append(
|
|
new drawinglayer::primitive2d::MaskPrimitive2D(
|
|
basegfx::B2DPolyPolygon(aClipPoly),
|
|
drawinglayer::primitive2d::Primitive2DContainer {
|
|
new drawinglayer::primitive2d::MetafilePrimitive2D(aFinalMatrix, aGDI)
|
|
}
|
|
)
|
|
);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
SAL_WARN("drawinglayer.emf",
|
|
"EMF+\tDrawImage(Points) Wrong EMF+ file. Only Unit Type Pixel is "
|
|
"support by EMF+ specification for DrawImage(Points)");
|
|
}
|
|
break;
|
|
}
|
|
case EmfPlusRecordTypeDrawString:
|
|
{
|
|
sal_uInt32 brushId(0), formatId(0), stringLength(0);
|
|
rMS.ReadUInt32(brushId).ReadUInt32(formatId).ReadUInt32(stringLength);
|
|
SAL_INFO("drawinglayer.emf", "EMF+\t FontId: " << OUString::number(flags & 0xFF));
|
|
SAL_INFO("drawinglayer.emf", "EMF+\t BrushId: " << BrushIDToString(flags, brushId));
|
|
SAL_INFO("drawinglayer.emf", "EMF+\t FormatId: " << formatId);
|
|
SAL_INFO("drawinglayer.emf", "EMF+\t Length: " << stringLength);
|
|
|
|
// read the layout rectangle
|
|
float lx(0), ly(0), lw(0), lh(0);
|
|
rMS.ReadFloat(lx).ReadFloat(ly).ReadFloat(lw).ReadFloat(lh);
|
|
|
|
SAL_INFO("drawinglayer.emf", "EMF+\t DrawString layoutRect: " << lx << "," << ly << " - " << lw << "x" << lh);
|
|
// parse the string
|
|
const OUString text = read_uInt16s_ToOUString(rMS, stringLength);
|
|
SAL_INFO("drawinglayer.emf", "EMF+\t DrawString string: " << text);
|
|
// get the stringFormat from the Object table ( this is OPTIONAL and may be nullptr )
|
|
const EMFPStringFormat *stringFormat = dynamic_cast<EMFPStringFormat*>(maEMFPObjects[formatId & 0xff].get());
|
|
// get the font from the flags
|
|
const EMFPFont *font = dynamic_cast<EMFPFont*>(maEMFPObjects[flags & 0xff].get());
|
|
if (!font)
|
|
{
|
|
break;
|
|
}
|
|
mrPropertyHolders.Current().setFont(vcl::Font(font->family, Size(font->emSize, font->emSize)));
|
|
|
|
drawinglayer::attribute::FontAttribute fontAttribute(
|
|
font->family, // font family
|
|
u""_ustr, // (no) font style
|
|
font->Bold() ? 8u : 1u, // weight: 8 = bold
|
|
font->family == "SYMBOL", // symbol
|
|
stringFormat && stringFormat->DirectionVertical(), // vertical
|
|
font->Italic(), // italic
|
|
false, // monospaced
|
|
false, // outline = false, no such thing in MS-EMFPLUS
|
|
stringFormat && stringFormat->DirectionRightToLeft(), // right-to-left
|
|
false); // BiDiStrong
|
|
|
|
css::lang::Locale locale;
|
|
double stringAlignmentHorizontalOffset = 0.0;
|
|
double stringAlignmentVerticalOffset = font->emSize;
|
|
std::vector<double> aDXArray;
|
|
if (stringFormat)
|
|
{
|
|
LanguageTag aLanguageTag(static_cast<LanguageType>(stringFormat->language));
|
|
locale = aLanguageTag.getLocale();
|
|
drawinglayer::primitive2d::TextLayouterDevice aTextLayouter;
|
|
|
|
aTextLayouter.setFontAttribute(fontAttribute, font->emSize,
|
|
font->emSize, locale);
|
|
|
|
double fTextWidth = aTextLayouter.getTextWidth(text, 0, stringLength);
|
|
|
|
// Apply character spacing (tracking). Tracking is a multiplier on
|
|
// each character's natural advance; default 1.0 means no change.
|
|
if (stringFormat->tracking != 1.0f && stringLength > 0)
|
|
{
|
|
aDXArray = aTextLayouter.getTextArray(text, 0, stringLength);
|
|
for (double& rPos : aDXArray)
|
|
rPos *= stringFormat->tracking;
|
|
if (!aDXArray.empty())
|
|
fTextWidth = aDXArray.back();
|
|
}
|
|
|
|
SAL_WARN_IF(stringFormat->DirectionRightToLeft(), "drawinglayer.emf",
|
|
"EMF+\t DrawString Alignment TODO For a right-to-left layout rectangle, the origin should be at the upper right.");
|
|
if (stringFormat->stringAlignment == StringAlignmentNear)
|
|
// Alignment is to the left side of the layout rectangle (lx, ly, lw, lh)
|
|
stringAlignmentHorizontalOffset = stringFormat->leadingMargin * font->emSize;
|
|
else if (stringFormat->stringAlignment == StringAlignmentCenter)
|
|
// Alignment is centered between the origin and extent of the layout rectangle
|
|
stringAlignmentHorizontalOffset = 0.5 * lw + (stringFormat->leadingMargin - stringFormat->trailingMargin) * font->emSize - 0.5 * fTextWidth;
|
|
else if (stringFormat->stringAlignment == StringAlignmentFar)
|
|
// Alignment is to the right side of the layout rectangle
|
|
stringAlignmentHorizontalOffset = lw - stringFormat->trailingMargin * font->emSize - fTextWidth;
|
|
|
|
if (stringFormat->lineAlign == StringAlignmentNear)
|
|
stringAlignmentVerticalOffset = font->emSize;
|
|
else if (stringFormat->lineAlign == StringAlignmentCenter)
|
|
stringAlignmentVerticalOffset = 0.5 * lh + 0.5 * font->emSize;
|
|
else if (stringFormat->lineAlign == StringAlignmentFar)
|
|
stringAlignmentVerticalOffset = lh;
|
|
}
|
|
else
|
|
{
|
|
// By default LeadingMargin is 1/6 inch
|
|
// TODO for typographic fonts set value to 0.
|
|
stringAlignmentHorizontalOffset = 16.0;
|
|
|
|
// use system default
|
|
locale = Application::GetSettings().GetLanguageTag().getLocale();
|
|
}
|
|
|
|
const basegfx::B2DHomMatrix transformMatrix = basegfx::utils::createScaleTranslateB2DHomMatrix(
|
|
::basegfx::B2DVector(font->emSize, font->emSize),
|
|
::basegfx::B2DPoint(lx + stringAlignmentHorizontalOffset,
|
|
ly + stringAlignmentVerticalOffset));
|
|
|
|
Color uncorrectedColor = EMFPGetBrushColorOrARGBColor(flags, brushId);
|
|
Color color;
|
|
|
|
if (mbSetTextContrast)
|
|
{
|
|
sal_uInt16 nTextContrast = std::clamp(mnTextContrast, LOWERGAMMA, UPPERGAMMA);
|
|
assert(nTextContrast >= LOWERGAMMA && nTextContrast <= UPPERGAMMA);
|
|
const auto gammaVal = nTextContrast / 1000;
|
|
assert(gammaVal != 0);
|
|
SAL_INFO("drawinglayer.emf", "EMF+\t Text contrast: " << gammaVal << " gamma");
|
|
const basegfx::BColorModifier_gamma gamma(gammaVal);
|
|
|
|
// gamma correct transparency color
|
|
sal_uInt16 alpha = uncorrectedColor.GetAlpha();
|
|
alpha = std::clamp(std::pow(alpha, 1.0 / gammaVal), 0.0, 1.0) * 255;
|
|
|
|
basegfx::BColor modifiedColor(gamma.getModifiedColor(uncorrectedColor.getBColor()));
|
|
color.SetRed(modifiedColor.getRed() * 255);
|
|
color.SetGreen(modifiedColor.getGreen() * 255);
|
|
color.SetBlue(modifiedColor.getBlue() * 255);
|
|
color.SetAlpha(alpha);
|
|
}
|
|
else
|
|
{
|
|
color = uncorrectedColor;
|
|
}
|
|
|
|
mrPropertyHolders.Current().setTextColor(color.getBColor());
|
|
mrPropertyHolders.Current().setTextColorActive(true);
|
|
|
|
if (color.GetAlpha() > 0)
|
|
{
|
|
rtl::Reference<drawinglayer::primitive2d::BasePrimitive2D> pBaseText;
|
|
if (font->Underline() || font->Strikeout())
|
|
{
|
|
pBaseText = new drawinglayer::primitive2d::TextDecoratedPortionPrimitive2D(
|
|
transformMatrix,
|
|
text,
|
|
0, // text always starts at 0
|
|
stringLength,
|
|
std::move(aDXArray),
|
|
{},
|
|
std::move(fontAttribute),
|
|
locale,
|
|
color.getBColor(), // Font Color
|
|
COL_TRANSPARENT, // Fill Color
|
|
0,
|
|
false,
|
|
{},
|
|
100, 0,
|
|
color.getBColor(), // OverlineColor
|
|
color.getBColor(), // TextlineColor
|
|
drawinglayer::primitive2d::TEXT_LINE_NONE,
|
|
font->Underline() ? drawinglayer::primitive2d::TEXT_LINE_SINGLE : drawinglayer::primitive2d::TEXT_LINE_NONE,
|
|
false,
|
|
font->Strikeout() ? drawinglayer::primitive2d::TEXT_STRIKEOUT_SINGLE : drawinglayer::primitive2d::TEXT_STRIKEOUT_NONE);
|
|
}
|
|
else
|
|
{
|
|
pBaseText = new drawinglayer::primitive2d::TextSimplePortionPrimitive2D(
|
|
transformMatrix,
|
|
text,
|
|
0, // text always starts at 0
|
|
stringLength,
|
|
std::move(aDXArray),
|
|
{},
|
|
std::move(fontAttribute),
|
|
std::move(locale),
|
|
color.getBColor());
|
|
}
|
|
drawinglayer::primitive2d::Primitive2DReference aPrimitiveText(pBaseText);
|
|
if (color.IsTransparent())
|
|
{
|
|
aPrimitiveText = new drawinglayer::primitive2d::UnifiedTransparencePrimitive2D(
|
|
drawinglayer::primitive2d::Primitive2DContainer { aPrimitiveText },
|
|
(255 - color.GetAlpha()) / 255.0);
|
|
}
|
|
|
|
mrTargetHolders.Current().append(
|
|
new drawinglayer::primitive2d::TransformPrimitive2D(
|
|
maMapTransform,
|
|
drawinglayer::primitive2d::Primitive2DContainer { aPrimitiveText } ));
|
|
}
|
|
break;
|
|
}
|
|
case EmfPlusRecordTypeSetPageTransform:
|
|
{
|
|
float pageScale(0);
|
|
rMS.ReadFloat(pageScale);
|
|
SAL_INFO("drawinglayer.emf", "EMF+\t Scale: " << pageScale << " unit: " << UnitTypeToString(flags));
|
|
|
|
mfPageScaleX = unitToPixel(pageScale, flags, Direction::horizontal);
|
|
mfPageScaleY = unitToPixel(pageScale, flags, Direction::vertical);
|
|
mappingChanged();
|
|
|
|
break;
|
|
}
|
|
case EmfPlusRecordTypeSetRenderingOrigin:
|
|
{
|
|
rMS.ReadInt32(mnOriginX).ReadInt32(mnOriginY);
|
|
SAL_INFO("drawinglayer.emf", "EMF+\t SetRenderingOrigin, [x,y]: " << mnOriginX << "," << mnOriginY);
|
|
break;
|
|
}
|
|
case EmfPlusRecordTypeSetTextContrast:
|
|
{
|
|
mbSetTextContrast = true;
|
|
mnTextContrast = flags & 0xFFF;
|
|
SAL_WARN_IF(mnTextContrast > UPPERGAMMA || mnTextContrast < LOWERGAMMA,
|
|
"drawinglayer.emf", "EMF+\t Gamma value is not with bounds 1000 to 2200, value is " << mnTextContrast);
|
|
break;
|
|
}
|
|
case EmfPlusRecordTypeSetTextRenderingHint:
|
|
{
|
|
sal_uInt8 nTextRenderingHint = (flags & 0xFF) >> 1;
|
|
SAL_INFO("drawinglayer.emf", "EMF+\t Text rendering hint: " << TextRenderingHintToString(nTextRenderingHint));
|
|
SAL_WARN("drawinglayer.emf", "EMF+\t TODO SetTextRenderingHint");
|
|
break;
|
|
}
|
|
case EmfPlusRecordTypeSetAntiAliasMode:
|
|
{
|
|
bool bUseAntiAlias = (flags & 0x0001);
|
|
sal_uInt8 nSmoothingMode = (flags & 0xFE00) >> 1;
|
|
SAL_INFO("drawinglayer.emf", "EMF+\t Antialiasing: " << (bUseAntiAlias ? "enabled" : "disabled"));
|
|
SAL_INFO("drawinglayer.emf", "EMF+\t Smoothing mode: " << SmoothingModeToString(nSmoothingMode));
|
|
SAL_WARN("drawinglayer.emf", "EMF+\t TODO SetAntiAliasMode");
|
|
break;
|
|
}
|
|
case EmfPlusRecordTypeSetInterpolationMode:
|
|
{
|
|
sal_uInt16 nInterpolationMode = flags & 0xFF;
|
|
SAL_INFO("drawinglayer.emf", "EMF+\t Interpolation mode: " << InterpolationModeToString(nInterpolationMode));
|
|
SAL_WARN("drawinglayer.emf", "EMF+\t TODO InterpolationMode");
|
|
break;
|
|
}
|
|
case EmfPlusRecordTypeSetPixelOffsetMode:
|
|
{
|
|
SAL_INFO("drawinglayer.emf", "EMF+\t Pixel offset mode: " << PixelOffsetModeToString(flags));
|
|
SAL_WARN("drawinglayer.emf", "EMF+\t TODO SetPixelOffsetMode");
|
|
break;
|
|
}
|
|
case EmfPlusRecordTypeSetCompositingQuality:
|
|
{
|
|
SAL_INFO("drawinglayer.emf", "EMF+\t TODO SetCompositingQuality");
|
|
break;
|
|
}
|
|
case EmfPlusRecordTypeSave:
|
|
{
|
|
sal_uInt32 stackIndex(0);
|
|
rMS.ReadUInt32(stackIndex);
|
|
SAL_INFO("drawinglayer.emf", "EMF+\t Save stack index: " << stackIndex);
|
|
|
|
GraphicStatePush(mGSStack, stackIndex);
|
|
|
|
break;
|
|
}
|
|
case EmfPlusRecordTypeRestore:
|
|
{
|
|
sal_uInt32 stackIndex(0);
|
|
rMS.ReadUInt32(stackIndex);
|
|
SAL_INFO("drawinglayer.emf", "EMF+\t Restore stack index: " << stackIndex);
|
|
|
|
GraphicStatePop(mGSStack, stackIndex);
|
|
break;
|
|
}
|
|
case EmfPlusRecordTypeBeginContainer:
|
|
{
|
|
float dx(0), dy(0), dw(0), dh(0);
|
|
ReadRectangle(rMS, dx, dy, dw, dh);
|
|
SAL_INFO("drawinglayer.emf", "EMF+\t Dest RectData: " << dx << "," << dy << " " << dw << "x" << dh);
|
|
|
|
float sx(0), sy(0), sw(0), sh(0);
|
|
ReadRectangle(rMS, sx, sy, sw, sh);
|
|
SAL_INFO("drawinglayer.emf", "EMF+\t Source RectData: " << sx << "," << sy << " " << sw << "x" << sh);
|
|
|
|
sal_uInt32 stackIndex(0);
|
|
rMS.ReadUInt32(stackIndex);
|
|
SAL_INFO("drawinglayer.emf", "EMF+\t Begin Container stack index: " << stackIndex << ", PageUnit: " << flags);
|
|
|
|
GraphicStatePush(mGSContainerStack, stackIndex);
|
|
const basegfx::B2DHomMatrix transform = basegfx::utils::createScaleTranslateB2DHomMatrix(
|
|
unitToPixel(static_cast<double>(dw) / sw, flags, Direction::horizontal),
|
|
unitToPixel(static_cast<double>(dh) / sh, flags, Direction::vertical),
|
|
unitToPixel(static_cast<double>(dx) - sx, flags, Direction::horizontal),
|
|
unitToPixel(static_cast<double>(dy) - sy, flags, Direction::vertical));
|
|
maWorldTransform *= transform;
|
|
mappingChanged();
|
|
break;
|
|
}
|
|
case EmfPlusRecordTypeBeginContainerNoParams:
|
|
{
|
|
sal_uInt32 stackIndex(0);
|
|
rMS.ReadUInt32(stackIndex);
|
|
SAL_INFO("drawinglayer.emf", "EMF+\t Begin Container No Params stack index: " << stackIndex);
|
|
|
|
GraphicStatePush(mGSContainerStack, stackIndex);
|
|
break;
|
|
}
|
|
case EmfPlusRecordTypeEndContainer:
|
|
{
|
|
sal_uInt32 stackIndex(0);
|
|
rMS.ReadUInt32(stackIndex);
|
|
SAL_INFO("drawinglayer.emf", "EMF+\t End Container stack index: " << stackIndex);
|
|
|
|
GraphicStatePop(mGSContainerStack, stackIndex);
|
|
break;
|
|
}
|
|
case EmfPlusRecordTypeSetWorldTransform:
|
|
{
|
|
SAL_INFO("drawinglayer.emf", "EMF+\t SetWorldTransform, Post multiply: " << bool(flags & 0x2000));
|
|
readXForm(rMS, maWorldTransform);
|
|
mappingChanged();
|
|
SAL_INFO("drawinglayer.emf", "EMF+\t\t: " << maWorldTransform);
|
|
break;
|
|
}
|
|
case EmfPlusRecordTypeResetWorldTransform:
|
|
{
|
|
maWorldTransform.identity();
|
|
SAL_INFO("drawinglayer.emf", "EMF+\t World transform: " << maWorldTransform);
|
|
mappingChanged();
|
|
break;
|
|
}
|
|
case EmfPlusRecordTypeMultiplyWorldTransform:
|
|
{
|
|
SAL_INFO("drawinglayer.emf", "EMF+\t MultiplyWorldTransform, post multiply: " << bool(flags & 0x2000));
|
|
basegfx::B2DHomMatrix transform;
|
|
readXForm(rMS, transform);
|
|
|
|
SAL_INFO("drawinglayer.emf",
|
|
"EMF+\t Transform matrix: " << transform);
|
|
|
|
if (flags & 0x2000)
|
|
{
|
|
// post multiply
|
|
maWorldTransform *= transform;
|
|
}
|
|
else
|
|
{
|
|
// pre multiply
|
|
transform *= maWorldTransform;
|
|
maWorldTransform = transform;
|
|
}
|
|
|
|
mappingChanged();
|
|
|
|
SAL_INFO("drawinglayer.emf",
|
|
"EMF+\t World transform matrix: " << maWorldTransform);
|
|
break;
|
|
}
|
|
case EmfPlusRecordTypeTranslateWorldTransform:
|
|
{
|
|
SAL_INFO("drawinglayer.emf", "EMF+\t TranslateWorldTransform, Post multiply: " << bool(flags & 0x2000));
|
|
|
|
basegfx::B2DHomMatrix transform;
|
|
float eDx(0), eDy(0);
|
|
rMS.ReadFloat(eDx).ReadFloat(eDy);
|
|
transform.set(0, 2, eDx);
|
|
transform.set(1, 2, eDy);
|
|
|
|
SAL_INFO("drawinglayer.emf",
|
|
"EMF+\t Translate matrix: " << transform);
|
|
|
|
if (flags & 0x2000)
|
|
{
|
|
// post multiply
|
|
maWorldTransform *= transform;
|
|
}
|
|
else
|
|
{
|
|
// pre multiply
|
|
transform *= maWorldTransform;
|
|
maWorldTransform = transform;
|
|
}
|
|
|
|
mappingChanged();
|
|
|
|
SAL_INFO("drawinglayer.emf",
|
|
"EMF+\t World transform matrix: " << maWorldTransform);
|
|
break;
|
|
}
|
|
case EmfPlusRecordTypeScaleWorldTransform:
|
|
{
|
|
basegfx::B2DHomMatrix transform;
|
|
float eSx(0), eSy(0);
|
|
rMS.ReadFloat(eSx).ReadFloat(eSy);
|
|
transform.set(0, 0, eSx);
|
|
transform.set(1, 1, eSy);
|
|
|
|
SAL_INFO("drawinglayer.emf", "EMF+\t ScaleWorldTransform Sx: " << eSx <<
|
|
" Sy: " << eSy << ", Post multiply:" << bool(flags & 0x2000));
|
|
SAL_INFO("drawinglayer.emf",
|
|
"EMF+\t World transform matrix: " << maWorldTransform);
|
|
|
|
if (flags & 0x2000)
|
|
{
|
|
// post multiply
|
|
maWorldTransform *= transform;
|
|
}
|
|
else
|
|
{
|
|
// pre multiply
|
|
transform *= maWorldTransform;
|
|
maWorldTransform = transform;
|
|
}
|
|
|
|
mappingChanged();
|
|
|
|
SAL_INFO("drawinglayer.emf",
|
|
"EMF+\t World transform matrix: " << maWorldTransform);
|
|
break;
|
|
}
|
|
case EmfPlusRecordTypeRotateWorldTransform:
|
|
{
|
|
// Angle of rotation in degrees
|
|
float eAngle(0);
|
|
rMS.ReadFloat(eAngle);
|
|
|
|
SAL_INFO("drawinglayer.emf", "EMF+\t RotateWorldTransform Angle: " << eAngle <<
|
|
", post multiply: " << bool(flags & 0x2000));
|
|
// Skipping flags & 0x2000
|
|
// For rotation transformation there is no difference between post and pre multiply
|
|
maWorldTransform.rotate(basegfx::deg2rad(eAngle));
|
|
mappingChanged();
|
|
|
|
SAL_INFO("drawinglayer.emf",
|
|
"EMF+\t " << maWorldTransform);
|
|
break;
|
|
}
|
|
case EmfPlusRecordTypeResetClip:
|
|
{
|
|
SAL_INFO("drawinglayer.emf", "EMF+ ResetClip");
|
|
// We don't need to read anything more, as Size needs to be set 0x0000000C
|
|
// and DataSize must be set to 0.
|
|
|
|
// Resets the current clipping region for the world space to infinity.
|
|
HandleNewClipRegion(::basegfx::B2DPolyPolygon(), mrTargetHolders, mrPropertyHolders);
|
|
break;
|
|
}
|
|
case EmfPlusRecordTypeSetClipRect:
|
|
case EmfPlusRecordTypeSetClipPath:
|
|
case EmfPlusRecordTypeSetClipRegion:
|
|
{
|
|
int combineMode = (flags >> 8) & 0xf;
|
|
::basegfx::B2DPolyPolygon polyPolygon;
|
|
if (type == EmfPlusRecordTypeSetClipRect)
|
|
{
|
|
SAL_INFO("drawinglayer.emf", "EMF+\t SetClipRect");
|
|
|
|
float dx(0), dy(0), dw(0), dh(0);
|
|
ReadRectangle(rMS, dx, dy, dw, dh);
|
|
SAL_INFO("drawinglayer.emf",
|
|
"EMF+\t RectData: " << dx << "," << dy << " " << dw << "x" << dh);
|
|
::basegfx::B2DPoint mappedPoint1(Map(dx, dy));
|
|
::basegfx::B2DPoint mappedPoint2(Map(dx + dw, dy + dh));
|
|
|
|
polyPolygon
|
|
= ::basegfx::B2DPolyPolygon(::basegfx::utils::createPolygonFromRect(
|
|
::basegfx::B2DRectangle(mappedPoint1.getX(), mappedPoint1.getY(),
|
|
mappedPoint2.getX(), mappedPoint2.getY())));
|
|
}
|
|
else if (type == EmfPlusRecordTypeSetClipPath)
|
|
{
|
|
SAL_INFO("drawinglayer.emf", "EMF+\tSetClipPath " << (flags & 0xff));
|
|
|
|
EMFPPath* path = dynamic_cast<EMFPPath*>(maEMFPObjects[flags & 0xff].get());
|
|
if (!path)
|
|
{
|
|
SAL_WARN("drawinglayer.emf",
|
|
"EMF+\t TODO Unable to find path in slot: " << (flags & 0xff));
|
|
break;
|
|
}
|
|
polyPolygon = path->GetPolygon(*this);
|
|
}
|
|
else if (type == EmfPlusRecordTypeSetClipRegion)
|
|
{
|
|
SAL_INFO("drawinglayer.emf", "EMF+\t Region in slot: " << (flags & 0xff));
|
|
EMFPRegion* region
|
|
= dynamic_cast<EMFPRegion*>(maEMFPObjects[flags & 0xff].get());
|
|
if (!region)
|
|
{
|
|
SAL_WARN(
|
|
"drawinglayer.emf",
|
|
"EMF+\t TODO Unable to find region in slot: " << (flags & 0xff));
|
|
break;
|
|
}
|
|
polyPolygon = region->regionPolyPolygon;
|
|
}
|
|
SAL_INFO("drawinglayer.emf", "EMF+\t Combine mode: " << combineMode);
|
|
::basegfx::B2DPolyPolygon aClippedPolyPolygon;
|
|
if (mrPropertyHolders.Current().getClipPolyPolygonActive())
|
|
{
|
|
aClippedPolyPolygon
|
|
= combineClip(mrPropertyHolders.Current().getClipPolyPolygon(),
|
|
combineMode, polyPolygon);
|
|
}
|
|
else
|
|
{
|
|
//Combine with infinity
|
|
switch (combineMode)
|
|
{
|
|
case EmfPlusCombineModeReplace:
|
|
case EmfPlusCombineModeIntersect:
|
|
{
|
|
aClippedPolyPolygon = polyPolygon;
|
|
break;
|
|
}
|
|
case EmfPlusCombineModeUnion:
|
|
{
|
|
// Disable clipping as the clipping is infinity
|
|
aClippedPolyPolygon = ::basegfx::B2DPolyPolygon();
|
|
break;
|
|
}
|
|
case EmfPlusCombineModeXOR:
|
|
case EmfPlusCombineModeComplement:
|
|
{
|
|
//TODO It is not correct and it should be fixed
|
|
aClippedPolyPolygon = std::move(polyPolygon);
|
|
break;
|
|
}
|
|
case EmfPlusCombineModeExclude:
|
|
{
|
|
//TODO It is not correct and it should be fixed
|
|
aClippedPolyPolygon = ::basegfx::B2DPolyPolygon();
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
HandleNewClipRegion(aClippedPolyPolygon, mrTargetHolders, mrPropertyHolders);
|
|
break;
|
|
}
|
|
case EmfPlusRecordTypeOffsetClip:
|
|
{
|
|
float dx(0), dy(0);
|
|
rMS.ReadFloat(dx).ReadFloat(dy);
|
|
SAL_INFO("drawinglayer.emf", "EMF+\tOffset x:" << dx << ", y:" << dy);
|
|
|
|
basegfx::B2DPolyPolygon aPolyPolygon(
|
|
mrPropertyHolders.Current().getClipPolyPolygon());
|
|
|
|
SAL_INFO("drawinglayer.emf",
|
|
"EMF+\t PolyPolygon before translate: " << aPolyPolygon);
|
|
|
|
basegfx::B2DPoint aOffset = Map(dx, dy);
|
|
basegfx::B2DHomMatrix transformMatrix;
|
|
transformMatrix.set(0, 2, aOffset.getX());
|
|
transformMatrix.set(1, 2, aOffset.getY());
|
|
aPolyPolygon.transform(transformMatrix);
|
|
|
|
SAL_INFO("drawinglayer.emf",
|
|
"EMF+\t PolyPolygon after translate: " << aPolyPolygon <<
|
|
", mapped offset x" << aOffset.getX() << ", mapped offset y" << aOffset.getY());
|
|
HandleNewClipRegion(aPolyPolygon, mrTargetHolders, mrPropertyHolders);
|
|
break;
|
|
}
|
|
case EmfPlusRecordTypeDrawDriverString:
|
|
{
|
|
sal_uInt32 brushIndexOrColor(0);
|
|
sal_uInt32 optionFlags(0);
|
|
sal_uInt32 hasMatrix(0);
|
|
sal_uInt32 glyphsCount(0);
|
|
rMS.ReadUInt32(brushIndexOrColor).ReadUInt32(optionFlags).ReadUInt32(hasMatrix).ReadUInt32(glyphsCount);
|
|
SAL_INFO("drawinglayer.emf", "EMF+\t " << ((flags & 0x8000) ? "Color" : "Brush index") << ": 0x" << std::hex << brushIndexOrColor << std::dec);
|
|
SAL_INFO("drawinglayer.emf", "EMF+\t Option flags: 0x" << std::hex << optionFlags << std::dec);
|
|
SAL_INFO("drawinglayer.emf", "EMF+\t Has matrix: " << hasMatrix);
|
|
SAL_INFO("drawinglayer.emf", "EMF+\t Glyphs: " << glyphsCount);
|
|
|
|
// each glyph needs 2 bytes (UTF-16) + 2*4 bytes (X,Y floats) = 10 bytes
|
|
if ((optionFlags & 1) && glyphsCount > 0
|
|
&& glyphsCount <= rMS.remainingSize() / 10)
|
|
{
|
|
std::unique_ptr<float[]> charsPosX(new float[glyphsCount]);
|
|
std::unique_ptr<float[]> charsPosY(new float[glyphsCount]);
|
|
OUString text = read_uInt16s_ToOUString(rMS, glyphsCount);
|
|
SAL_INFO("drawinglayer.emf", "EMF+\t DrawDriverString string: " << text);
|
|
|
|
for (sal_uInt32 i = 0; i<glyphsCount; i++)
|
|
{
|
|
rMS.ReadFloat(charsPosX[i]).ReadFloat(charsPosY[i]);
|
|
SAL_INFO("drawinglayer.emf", "EMF+\t\t glyphPosition[" << i << "]: " << charsPosX[i] << "," << charsPosY[i]);
|
|
}
|
|
|
|
basegfx::B2DHomMatrix transform;
|
|
|
|
if (hasMatrix)
|
|
{
|
|
readXForm(rMS, transform);
|
|
SAL_INFO("drawinglayer.emf", "EMF+\tmatrix: " << transform);
|
|
}
|
|
|
|
// get the font from the flags
|
|
EMFPFont *font = dynamic_cast<EMFPFont*>(maEMFPObjects[flags & 0xff].get());
|
|
if (!font)
|
|
{
|
|
break;
|
|
}
|
|
// done reading
|
|
|
|
drawinglayer::attribute::FontAttribute fontAttribute(
|
|
font->family, // font family
|
|
u""_ustr, // (no) font style
|
|
font->Bold() ? 8u : 1u, // weight: 8 = bold
|
|
font->family == "SYMBOL", // symbol
|
|
optionFlags & 0x2, // vertical
|
|
font->Italic(), // italic
|
|
false, // monospaced
|
|
false, // outline = false, no such thing in MS-EMFPLUS
|
|
false, // right-to-left
|
|
false); // BiDiStrong
|
|
|
|
const Color color = EMFPGetBrushColorOrARGBColor(flags, brushIndexOrColor);
|
|
|
|
// generate TextSimplePortionPrimitive2Ds or TextDecoratedPortionPrimitive2D
|
|
// for all portions of text with the same charsPosY values
|
|
sal_uInt32 pos = 0;
|
|
while (pos < glyphsCount)
|
|
{
|
|
//determine the current length
|
|
sal_uInt32 aLength = 1;
|
|
while (pos + aLength < glyphsCount && std::abs( charsPosY[pos + aLength] - charsPosY[pos] ) < std::numeric_limits< float >::epsilon())
|
|
aLength++;
|
|
|
|
// generate the DX-Array
|
|
std::vector<double> aDXArray;
|
|
for (size_t i = 0; i < aLength - 1; i++)
|
|
{
|
|
aDXArray.push_back(charsPosX[pos + i + 1] - charsPosX[pos]);
|
|
}
|
|
// last entry
|
|
aDXArray.push_back(0);
|
|
|
|
basegfx::B2DHomMatrix transformMatrix = basegfx::utils::createScaleTranslateB2DHomMatrix(
|
|
::basegfx::B2DVector(font->emSize, font->emSize),
|
|
::basegfx::B2DPoint(charsPosX[pos], charsPosY[pos]));
|
|
if (hasMatrix)
|
|
transformMatrix *= transform;
|
|
if (color.GetAlpha() > 0)
|
|
{
|
|
rtl::Reference<drawinglayer::primitive2d::BasePrimitive2D> pBaseText;
|
|
if (font->Underline() || font->Strikeout())
|
|
{
|
|
pBaseText = new drawinglayer::primitive2d::TextDecoratedPortionPrimitive2D(
|
|
transformMatrix,
|
|
text,
|
|
pos, // take character at current pos
|
|
aLength, // use determined length
|
|
std::move(aDXArray), // generated DXArray
|
|
{},
|
|
fontAttribute,
|
|
Application::GetSettings().GetLanguageTag().getLocale(),
|
|
color.getBColor(),
|
|
COL_TRANSPARENT,
|
|
0,
|
|
false,
|
|
{},
|
|
100, 0,
|
|
color.getBColor(),
|
|
color.getBColor(),
|
|
drawinglayer::primitive2d::TEXT_LINE_NONE,
|
|
font->Underline() ? drawinglayer::primitive2d::TEXT_LINE_SINGLE : drawinglayer::primitive2d::TEXT_LINE_NONE,
|
|
false,
|
|
font->Strikeout() ? drawinglayer::primitive2d::TEXT_STRIKEOUT_SINGLE : drawinglayer::primitive2d::TEXT_STRIKEOUT_NONE);
|
|
}
|
|
else
|
|
{
|
|
pBaseText = new drawinglayer::primitive2d::TextSimplePortionPrimitive2D(
|
|
transformMatrix,
|
|
text,
|
|
pos, // take character at current pos
|
|
aLength, // use determined length
|
|
std::move(aDXArray), // generated DXArray
|
|
{},
|
|
fontAttribute,
|
|
Application::GetSettings().GetLanguageTag().getLocale(),
|
|
color.getBColor());
|
|
}
|
|
drawinglayer::primitive2d::Primitive2DReference aPrimitiveText(pBaseText);
|
|
if (color.IsTransparent())
|
|
{
|
|
aPrimitiveText = new drawinglayer::primitive2d::UnifiedTransparencePrimitive2D(
|
|
drawinglayer::primitive2d::Primitive2DContainer { aPrimitiveText },
|
|
(255 - color.GetAlpha()) / 255.0);
|
|
}
|
|
mrTargetHolders.Current().append(
|
|
new drawinglayer::primitive2d::TransformPrimitive2D(
|
|
maMapTransform,
|
|
drawinglayer::primitive2d::Primitive2DContainer { aPrimitiveText } ));
|
|
}
|
|
|
|
// update pos
|
|
pos += aLength;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
SAL_WARN("drawinglayer.emf", "EMF+\tTODO: fonts (non-unicode glyphs chars)");
|
|
}
|
|
break;
|
|
}
|
|
default:
|
|
{
|
|
SAL_WARN("drawinglayer.emf", "EMF+ TODO unhandled record type: 0x" << std::hex << type << std::dec);
|
|
}
|
|
}
|
|
}
|
|
|
|
rMS.Seek(next);
|
|
|
|
if (size <= length)
|
|
{
|
|
length -= size;
|
|
}
|
|
else
|
|
{
|
|
SAL_WARN("drawinglayer.emf", "ImplRenderer::processEMFPlus: "
|
|
"size " << size << " > length " << length);
|
|
length = 0;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
|