/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */ /* * 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 .
*/
void AddPolygonToPath(CGMutablePathRef xPath, const basegfx::B2DPolygon& rPolygon, bool bClosePath, bool bPixelSnap, bool bLineDraw)
{ // short circuit if there is nothing to do constint nPointCount = rPolygon.count(); if (nPointCount <= 0)
{ return;
}
constbool bHasCurves = rPolygon.areControlPointsUsed(); for (int nPointIdx = 0, nPrevIdx = 0;; nPrevIdx = nPointIdx++)
{ int nClosedIdx = nPointIdx; if (nPointIdx >= nPointCount)
{ // prepare to close last curve segment if needed if (bClosePath && (nPointIdx == nPointCount))
{
nClosedIdx = 0;
} else
{ break;
}
}
if (bPixelSnap)
{ // snap device coordinates to full pixels
aPoint.setX(basegfx::fround(aPoint.getX()));
aPoint.setY(basegfx::fround(aPoint.getY()));
}
if (bLineDraw)
{
aPoint += aHalfPointOfs;
} if (!nPointIdx)
{ // first point => just move there
CGPathMoveToPoint(xPath, nullptr, aPoint.getX(), aPoint.getY()); continue;
}
// set current path, either as polypolgon or sequence of rectangles
RectangleVector aRectangles;
rRegion.GetRegionRectangles(aRectangles);
for (constauto& rRect : aRectangles)
{ const tools::Long nW(rRect.Right() - rRect.Left() + 1); // uses +1 logic in original
if (nW)
{ const tools::Long nH(rRect.Bottom() - rRect.Top() + 1); // uses +1 logic in original
if (nH)
{ const CGRect aRect = CGRectMake(rRect.Left(), rRect.Top(), nW, nH);
CGPathAddRect(mrShared.mxClipPath, nullptr, aRect);
}
}
} // set the current path as clip region if (mrShared.checkContext())
mrShared.setState();
}
void AquaGraphicsBackend::ResetClipRegion()
{ // release old path and indicate no clipping
mrShared.unsetClipPath();
if (mrShared.checkContext())
{
mrShared.setState();
}
}
// overwrite the fill color
CGContextSetFillColor(mrShared.maContextHolder.get(), rColor.AsArray()); // draw 1x1 rect, there is no pixel drawing in Quartz const CGRect aDstRect = CGRectMake(nX, nY, 1, 1);
CGContextFillRect(mrShared.maContextHolder.get(), aDstRect);
refreshRect(aDstRect);
// reset the fill color
CGContextSetFillColor(mrShared.maContextHolder.get(), mrShared.maFillColor.AsArray());
}
void AquaGraphicsBackend::drawPixel(tools::Long nX, tools::Long nY)
{ // draw pixel with current line color
drawPixelImpl(nX, nY, mrShared.maLineColor);
}
// Fallback: Transform to DeviceCoordinates
basegfx::B2DPolyPolygon aPolyPolygon(rPolyPolygon);
aPolyPolygon.transform(rObjectToDevice);
// setup poly-polygon path
CGMutablePathRef xPath = CGPathCreateMutable(); // tdf#120252 Use the correct, already transformed PolyPolygon (as long as // the transformation is not used here...) for (autoconst& rPolygon : std::as_const(aPolyPolygon))
{
AddPolygonToPath(xPath, rPolygon, true, !getAntiAlias(), mrShared.isPenVisible());
}
const CGRect aRefreshRect = CGPathGetBoundingBox(xPath); // #i97317# workaround for Quartz having problems with drawing small polygons if (aRefreshRect.size.width > 0.125 || aRefreshRect.size.height > 0.125)
{ // prepare drawing mode
CGPathDrawingMode eMode; if (mrShared.isBrushVisible() && mrShared.isPenVisible())
{
eMode = kCGPathEOFillStroke;
} elseif (mrShared.isPenVisible())
{
eMode = kCGPathStroke;
} elseif (mrShared.isBrushVisible())
{
eMode = kCGPathEOFill;
} else
{
SAL_WARN("vcl.quartz", "Neither pen nor brush visible");
CGPathRelease(xPath); return;
}
// use the path to prepare the graphics context
mrShared.maContextHolder.saveState();
CGContextBeginPath(mrShared.maContextHolder.get());
CGContextAddPath(mrShared.maContextHolder.get(), xPath);
#ifdef IOS if (!mrShared.checkContext()) returnfalse; #endif
// tdf#124848 get correct LineWidth in discrete coordinates, if (fLineWidth == 0) // hairline
fLineWidth = 1.0; else// Adjust line width for object-to-device scale.
fLineWidth = (rObjectToDevice * basegfx::B2DVector(fLineWidth, 0)).getLength();
// #i101491# Aqua does not support B2DLineJoin::NONE; return false to use // the fallback (own geometry preparation) // #i104886# linejoin-mode and thus the above only applies to "fat" lines if ((basegfx::B2DLineJoin::NONE == eLineJoin) && (fLineWidth > 1.3)) returnfalse;
// MM01 need to do line dashing as fallback stuff here now constdouble fDotDashLength(
nullptr != pStroke ? std::accumulate(pStroke->begin(), pStroke->end(), 0.0) : 0.0); constbool bStrokeUsed(0.0 != fDotDashLength);
assert(!bStrokeUsed || (bStrokeUsed && pStroke));
basegfx::B2DPolyPolygon aPolyPolygonLine;
if (bStrokeUsed)
{ // apply LineStyle
basegfx::utils::applyLineDashing(rPolyLine, // source
*pStroke, // pattern
&aPolyPolygonLine, // target for lines
nullptr, // target for gaps
fDotDashLength); // full length if available
} else
{ // no line dashing, just copy
aPolyPolygonLine.append(rPolyLine);
}
// Transform to DeviceCoordinates, get DeviceLineWidth, execute PixelSnapHairline
aPolyPolygonLine.transform(rObjectToDevice); if (bPixelSnapHairline)
{
aPolyPolygonLine = basegfx::utils::snapPointsOfHorizontalOrVerticalEdges(aPolyPolygonLine);
}
// setup line attributes
CGLineJoin aCGLineJoin = kCGLineJoinMiter; switch (eLineJoin)
{ case basegfx::B2DLineJoin::NONE:
aCGLineJoin = /*TODO?*/ kCGLineJoinMiter; break; case basegfx::B2DLineJoin::Bevel:
aCGLineJoin = kCGLineJoinBevel; break; case basegfx::B2DLineJoin::Miter:
aCGLineJoin = kCGLineJoinMiter; break; case basegfx::B2DLineJoin::Round:
aCGLineJoin = kCGLineJoinRound; break;
} // convert miter minimum angle to miter limit
CGFloat fCGMiterLimit = 1.0 / sin(std::max(fMiterMinimumAngle, 0.01 * M_PI) / 2.0); // setup cap attribute
CGLineCap aCGLineCap(kCGLineCapButt);
// MM01 todo - I assume that this is OKAY to be done in one run for quartz // but this NEEDS to be checked/verified for (sal_uInt32 a(0); a < aPolyPolygonLine.count(); a++)
{ const basegfx::B2DPolygon aPolyLine(aPolyPolygonLine.getB2DPolygon(a));
AddPolygonToPath(xPath, aPolyLine, aPolyLine.isClosed(), !getAntiAlias(), true);
}
const CGRect aRefreshRect = CGPathGetBoundingBox(xPath); // #i97317# workaround for Quartz having problems with drawing small polygons if ((aRefreshRect.size.width > 0.125) || (aRefreshRect.size.height > 0.125))
{ // use the path to prepare the graphics context
mrShared.maContextHolder.saveState();
CGContextBeginPath(mrShared.maContextHolder.get());
CGContextAddPath(mrShared.maContextHolder.get(), xPath); // draw path with antialiased line
CGContextSetShouldAntialias(mrShared.maContextHolder.get(), getAntiAlias());
CGContextSetAlpha(mrShared.maContextHolder.get(), 1.0 - fTransparency);
CGContextSetLineJoin(mrShared.maContextHolder.get(), aCGLineJoin);
CGContextSetLineCap(mrShared.maContextHolder.get(), aCGLineCap);
CGContextSetLineWidth(mrShared.maContextHolder.get(), fLineWidth);
CGContextSetMiterLimit(mrShared.maContextHolder.get(), fCGMiterLimit);
CGContextDrawPath(mrShared.maContextHolder.get(), kCGPathStroke);
mrShared.maContextHolder.restoreState();
// mark modified rectangle as updated
refreshRect(aRefreshRect);
}
Color AquaGraphicsBackend::getPixel(tools::Long nX, tools::Long nY)
{ // return default value on printers or when out of bounds if (!mrShared.maLayer.isSet() || (nX < 0) || (nX >= mrShared.mnWidth) || (nY < 0)
|| (nY >= mrShared.mnHeight))
{ return COL_BLACK;
}
// prepare creation of matching a CGBitmapContext #ifdefined OSL_BIGENDIAN struct
{ unsignedchar b, g, r, a;
} aPixel; #else struct
{ unsignedchar a, r, g, b;
} aPixel; #endif
// create a one-pixel bitmap context // TODO: is it worth to cache it?
CGContextRef xOnePixelContext = CGBitmapContextCreate(
&aPixel, 1, 1, 8, 32, GetSalData()->mxRGBSpace,
uint32_t(kCGImageAlphaNoneSkipFirst) | uint32_t(kCGBitmapByteOrder32Big));
// update this graphics layer
mrShared.applyXorContext();
// copy the requested pixel into the bitmap context if (mrShared.isFlipped())
{
nY = mrShared.mnHeight - nY;
} const CGPoint aCGPoint = CGPointMake(-nX, -nY);
CGContextDrawLayerAtPoint(xOnePixelContext, aCGPoint, mrShared.maLayer.get());
CGContextRelease(xOnePixelContext);
Color nColor(aPixel.r, aPixel.g, aPixel.b); return nColor;
}
if (maShared.mbPrinter)
{
rDPIX = rDPIY = 720;
} else
{
rDPIX = mnRealDPIX;
rDPIY = mnRealDPIY;
} #else // This *must* be 96 or else the iOS app will behave very badly (tiles are scaled wrongly and // don't match each others at their boundaries, and other issues). But *why* it must be 96 I // have no idea. The commit that changed it to 96 from (the arbitrary) 200 did not say. If you // know where else 96 is explicitly or implicitly hard-coded, please modify this comment.
// Follow-up: It might be this: in 'online', loleaflet/src/map/Map.js: // 15 = 1440 twips-per-inch / 96 dpi. // Chosen to match previous hardcoded value of 3840 for // the current tile pixel size of 256.
rDPIX = rDPIY = 96; #endif
}
#ifndef IOS bool AquaGraphicsBackend::drawEPS(tools::Long nX, tools::Long nY, tools::Long nWidth,
tools::Long nHeight, void* pEpsData, sal_uInt32 nByteCount)
{ // convert the raw data to an NSImageRef
NSData* xNSData = [NSData dataWithBytes:pEpsData length:static_cast<int>(nByteCount)];
SAL_WNODEPRECATED_DECLARATIONS_PUSH // 'NSEPSImageRep' is deprecated: first deprecated in macOS 14.0 - `NSEPSImageRep` instances // cannot be created on macOS 14.0 and later
NSImageRep* xEpsImage = [NSEPSImageRep imageRepWithData:xNSData];
SAL_WNODEPRECATED_DECLARATIONS_POP if (!xEpsImage)
{ returnfalse;
} // get the target context if (!mrShared.checkContext())
{ returnfalse;
} // NOTE: flip drawing, else the nsimage would be drawn upside down
mrShared.maContextHolder.saveState(); // CGContextTranslateCTM( maContextHolder.get(), 0, +mnHeight );
CGContextScaleCTM(mrShared.maContextHolder.get(), +1, -1);
nY = /*mnHeight*/ -(nY + nHeight);
// create new context
NSGraphicsContext* pDrawNSCtx =
[NSGraphicsContext graphicsContextWithCGContext:mrShared.maContextHolder.get()
flipped:mrShared.isFlipped()]; // set it, setCurrentContext also releases the previously set one
[NSGraphicsContext setCurrentContext:pDrawNSCtx];
// draw the EPS const NSRect aDstRect = NSMakeRect(nX, nY, nWidth, nHeight); constbool bOK = [xEpsImage drawInRect:aDstRect];
// restore the NSGraphicsContext
[NSGraphicsContext setCurrentContext:pOrigNSCtx];
[pOrigNSCtx release]; // restore the original retain count
mrShared.maContextHolder.restoreState(); // mark the destination rectangle as updated
refreshRect(aDstRect);
bool AquaGraphicsBackend::drawAlphaBitmap(const SalTwoRect& rTR, const SalBitmap& rSrcBitmap, const SalBitmap& rAlphaBmp)
{ // An image mask can't have a depth > 8 bits (should be 1 to 8 bits) if (rAlphaBmp.GetBitCount() > 8) returnfalse;
// are these two tests really necessary? (see vcl/unx/source/gdi/salgdi2.cxx) // horizontal/vertical mirroring not implemented yet if (rTR.mnDestWidth < 0 || rTR.mnDestHeight < 0) returnfalse;
// save the current state
mrShared.maContextHolder.saveState();
CGContextSetAlpha(mrShared.maContextHolder.get(), (100 - nTransparency) * (1.0 / 100));
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.