From ed5640d8b587fbcfed7dd7967f3de04b37a76f26 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Sun, 7 Apr 2024 11:06:44 +0200 Subject: Adding upstream version 4:7.4.7. Signed-off-by: Daniel Baumann --- vcl/unx/generic/gdi/X11CairoSalGraphicsImpl.cxx | 185 +++ vcl/unx/generic/gdi/X11CairoSalGraphicsImpl.hxx | 97 ++ vcl/unx/generic/gdi/cairo_xlib_cairo.cxx | 307 ++++ vcl/unx/generic/gdi/cairo_xlib_cairo.hxx | 96 ++ vcl/unx/generic/gdi/cairotextrender.cxx | 382 +++++ vcl/unx/generic/gdi/font.cxx | 116 ++ vcl/unx/generic/gdi/freetypetextrender.cxx | 216 +++ vcl/unx/generic/gdi/gdiimpl.cxx | 2015 +++++++++++++++++++++++ vcl/unx/generic/gdi/gdiimpl.hxx | 297 ++++ vcl/unx/generic/gdi/salbmp.cxx | 1001 +++++++++++ vcl/unx/generic/gdi/salgdi.cxx | 482 ++++++ vcl/unx/generic/gdi/salgdi2.cxx | 89 + vcl/unx/generic/gdi/salvd.cxx | 222 +++ vcl/unx/generic/gdi/x11cairotextrender.cxx | 66 + vcl/unx/generic/gdi/xrender_peer.cxx | 48 + 15 files changed, 5619 insertions(+) create mode 100644 vcl/unx/generic/gdi/X11CairoSalGraphicsImpl.cxx create mode 100644 vcl/unx/generic/gdi/X11CairoSalGraphicsImpl.hxx create mode 100644 vcl/unx/generic/gdi/cairo_xlib_cairo.cxx create mode 100644 vcl/unx/generic/gdi/cairo_xlib_cairo.hxx create mode 100644 vcl/unx/generic/gdi/cairotextrender.cxx create mode 100644 vcl/unx/generic/gdi/font.cxx create mode 100644 vcl/unx/generic/gdi/freetypetextrender.cxx create mode 100644 vcl/unx/generic/gdi/gdiimpl.cxx create mode 100644 vcl/unx/generic/gdi/gdiimpl.hxx create mode 100644 vcl/unx/generic/gdi/salbmp.cxx create mode 100644 vcl/unx/generic/gdi/salgdi.cxx create mode 100644 vcl/unx/generic/gdi/salgdi2.cxx create mode 100644 vcl/unx/generic/gdi/salvd.cxx create mode 100644 vcl/unx/generic/gdi/x11cairotextrender.cxx create mode 100644 vcl/unx/generic/gdi/xrender_peer.cxx (limited to 'vcl/unx/generic/gdi') diff --git a/vcl/unx/generic/gdi/X11CairoSalGraphicsImpl.cxx b/vcl/unx/generic/gdi/X11CairoSalGraphicsImpl.cxx new file mode 100644 index 000000000..2109e19b9 --- /dev/null +++ b/vcl/unx/generic/gdi/X11CairoSalGraphicsImpl.cxx @@ -0,0 +1,185 @@ +/* -*- 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 "X11CairoSalGraphicsImpl.hxx" + +#if ENABLE_CAIRO_CANVAS + +#include +#include + +X11CairoSalGraphicsImpl::X11CairoSalGraphicsImpl(X11SalGraphics& rParent, X11Common& rX11Common) + : X11SalGraphicsImpl(rParent) + , mrX11Common(rX11Common) + , mnPenColor(SALCOLOR_NONE) + , mnFillColor(SALCOLOR_NONE) +{ +} + +bool X11CairoSalGraphicsImpl::drawPolyPolygon(const basegfx::B2DHomMatrix& rObjectToDevice, + const basegfx::B2DPolyPolygon& rPolyPolygon, + double fTransparency) +{ + if (fTransparency >= 1.0) + { + return true; + } + + if (rPolyPolygon.count() == 0) + { + return true; + } + + // Fallback: Transform to DeviceCoordinates + basegfx::B2DPolyPolygon aPolyPolygon(rPolyPolygon); + aPolyPolygon.transform(rObjectToDevice); + + if (SALCOLOR_NONE == mnFillColor && SALCOLOR_NONE == mnPenColor) + { + return true; + } + + // enable by setting to something + static const char* pUseCairoForPolygons(getenv("SAL_ENABLE_USE_CAIRO_FOR_POLYGONS")); + + if (nullptr != pUseCairoForPolygons && mrX11Common.SupportsCairo()) + { + // snap to raster if requested + const bool bSnapPoints(!getAntiAlias()); + + if (bSnapPoints) + { + aPolyPolygon = basegfx::utils::snapPointsOfHorizontalOrVerticalEdges(aPolyPolygon); + } + + cairo_t* cr = mrX11Common.getCairoContext(); + clipRegion(cr); + + for (auto const& rPolygon : std::as_const(aPolyPolygon)) + { + const sal_uInt32 nPointCount(rPolygon.count()); + + if (nPointCount) + { + const sal_uInt32 nEdgeCount(rPolygon.isClosed() ? nPointCount : nPointCount - 1); + + if (nEdgeCount) + { + basegfx::B2DCubicBezier aEdge; + + for (sal_uInt32 b = 0; b < nEdgeCount; ++b) + { + rPolygon.getBezierSegment(b, aEdge); + + if (!b) + { + const basegfx::B2DPoint aStart(aEdge.getStartPoint()); + cairo_move_to(cr, aStart.getX(), aStart.getY()); + } + + const basegfx::B2DPoint aEnd(aEdge.getEndPoint()); + + if (aEdge.isBezier()) + { + const basegfx::B2DPoint aCP1(aEdge.getControlPointA()); + const basegfx::B2DPoint aCP2(aEdge.getControlPointB()); + cairo_curve_to(cr, aCP1.getX(), aCP1.getY(), aCP2.getX(), aCP2.getY(), + aEnd.getX(), aEnd.getY()); + } + else + { + cairo_line_to(cr, aEnd.getX(), aEnd.getY()); + } + } + + cairo_close_path(cr); + } + } + } + + if (SALCOLOR_NONE != mnFillColor) + { + cairo_set_source_rgba(cr, mnFillColor.GetRed() / 255.0, mnFillColor.GetGreen() / 255.0, + mnFillColor.GetBlue() / 255.0, 1.0 - fTransparency); + cairo_set_fill_rule(cr, CAIRO_FILL_RULE_EVEN_ODD); + cairo_fill_preserve(cr); + } + + if (SALCOLOR_NONE != mnPenColor) + { + cairo_set_source_rgba(cr, mnPenColor.GetRed() / 255.0, mnPenColor.GetGreen() / 255.0, + mnPenColor.GetBlue() / 255.0, 1.0 - fTransparency); + cairo_stroke_preserve(cr); + } + + X11Common::releaseCairoContext(cr); + return true; + } + + return X11SalGraphicsImpl::drawPolyPolygon(rObjectToDevice, rPolyPolygon, fTransparency); +} + +bool X11CairoSalGraphicsImpl::drawPolyLine(const basegfx::B2DHomMatrix& rObjectToDevice, + const basegfx::B2DPolygon& rPolygon, + double fTransparency, double fLineWidth, + const std::vector* pStroke, + basegfx::B2DLineJoin eLineJoin, + css::drawing::LineCap eLineCap, + double fMiterMinimumAngle, bool bPixelSnapHairline) +{ + if (0 == rPolygon.count()) + { + return true; + } + + if (fTransparency >= 1.0) + { + return true; + } + + // disable by setting to something + static const char* pUseCairoForFatLines(getenv("SAL_DISABLE_USE_CAIRO_FOR_FATLINES")); + + if (nullptr == pUseCairoForFatLines && mrX11Common.SupportsCairo()) + { + cairo_t* cr = mrX11Common.getCairoContext(); + clipRegion(cr); + + // Use the now available static drawPolyLine from the Cairo-Headless-Fallback + // that will take care of all needed stuff + const bool bRetval(CairoCommon::drawPolyLine( + cr, nullptr, mnPenColor, getAntiAlias(), rObjectToDevice, rPolygon, fTransparency, + fLineWidth, pStroke, eLineJoin, eLineCap, fMiterMinimumAngle, bPixelSnapHairline)); + + X11Common::releaseCairoContext(cr); + + if (bRetval) + { + return true; + } + } + + return X11SalGraphicsImpl::drawPolyLine(rObjectToDevice, rPolygon, fTransparency, fLineWidth, + pStroke, eLineJoin, eLineCap, fMiterMinimumAngle, + bPixelSnapHairline); +} + +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/unx/generic/gdi/X11CairoSalGraphicsImpl.hxx b/vcl/unx/generic/gdi/X11CairoSalGraphicsImpl.hxx new file mode 100644 index 000000000..beb07e3ff --- /dev/null +++ b/vcl/unx/generic/gdi/X11CairoSalGraphicsImpl.hxx @@ -0,0 +1,97 @@ +/* -*- 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 +#include + +#if ENABLE_CAIRO_CANVAS + +#include +#include +#include "gdiimpl.hxx" +#include "cairo_xlib_cairo.hxx" + +#include + +class X11CairoSalGraphicsImpl : public X11SalGraphicsImpl +{ +private: + X11Common& mrX11Common; + vcl::Region maClipRegion; + Color mnPenColor; + Color mnFillColor; + + using X11SalGraphicsImpl::drawPolyPolygon; + using X11SalGraphicsImpl::drawPolyLine; + +public: + X11CairoSalGraphicsImpl(X11SalGraphics& rParent, X11Common& rX11Common); + + void ResetClipRegion() override + { + maClipRegion.SetNull(); + X11SalGraphicsImpl::ResetClipRegion(); + } + + bool setClipRegion(const vcl::Region& i_rClip) override + { + maClipRegion = i_rClip; + return X11SalGraphicsImpl::setClipRegion(i_rClip); + } + + void SetLineColor() override + { + mnPenColor = SALCOLOR_NONE; + X11SalGraphicsImpl::SetLineColor(); + } + + void SetLineColor(Color nColor) override + { + mnPenColor = nColor; + X11SalGraphicsImpl::SetLineColor(nColor); + } + + void SetFillColor() override + { + mnFillColor = SALCOLOR_NONE; + X11SalGraphicsImpl::SetFillColor(); + } + + void SetFillColor(Color nColor) override + { + mnFillColor = nColor; + X11SalGraphicsImpl::SetFillColor(nColor); + } + + void clipRegion(cairo_t* cr) { CairoCommon::clipRegion(cr, maClipRegion); } + + bool drawPolyPolygon(const basegfx::B2DHomMatrix& rObjectToDevice, + const basegfx::B2DPolyPolygon& rPolyPolygon, + double fTransparency) override; + + bool drawPolyLine(const basegfx::B2DHomMatrix& rObjectToDevice, + const basegfx::B2DPolygon& rPolygon, double fTransparency, double fLineWidth, + const std::vector* pStroke, basegfx::B2DLineJoin eLineJoin, + css::drawing::LineCap eLineCap, double fMiterMinimumAngle, + bool bPixelSnapHairline) override; +}; + +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/unx/generic/gdi/cairo_xlib_cairo.cxx b/vcl/unx/generic/gdi/cairo_xlib_cairo.cxx new file mode 100644 index 000000000..17b34442b --- /dev/null +++ b/vcl/unx/generic/gdi/cairo_xlib_cairo.cxx @@ -0,0 +1,307 @@ +/* -*- 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 +#include + +#include "cairo_xlib_cairo.hxx" + +#include +#include +#include +#include + +#include +#include + +namespace +{ + Pixmap limitXCreatePixmap(Display *display, Drawable d, unsigned int width, unsigned int height, unsigned int depth) + { + // The X protocol request CreatePixmap puts an upper bound + // of 16 bit to the size. And in practice some drivers + // fall over with values close to the max. + + // see, e.g. moz#424333, fdo#48961, rhbz#1086714 + // we've a duplicate of this in vcl :-( + if (width > SAL_MAX_INT16-10 || height > SAL_MAX_INT16-10) + { + SAL_WARN("canvas", "overlarge pixmap: " << width << " x " << height); + return None; + } + return XCreatePixmap(display, d, width, height, depth); + } +} + +namespace cairo +{ + + X11SysData::X11SysData() : + pDisplay(nullptr), + hDrawable(0), + pVisual(nullptr), + nScreen(0), + pRenderFormat(nullptr) + {} + + X11SysData::X11SysData( const SystemGraphicsData& pSysDat ) : + pDisplay(pSysDat.pDisplay), + hDrawable(pSysDat.hDrawable), + pVisual(pSysDat.pVisual), + nScreen(pSysDat.nScreen), + pRenderFormat(pSysDat.pXRenderFormat) + {} + + X11SysData::X11SysData( const SystemEnvData& pSysDat, const SalFrame* pReference ) : + pDisplay(pSysDat.pDisplay), + hDrawable(pSysDat.GetWindowHandle(pReference)), + pVisual(pSysDat.pVisual), + nScreen(pSysDat.nScreen), + pRenderFormat(nullptr) + {} + + X11Pixmap::~X11Pixmap() + { + if( mpDisplay && mhDrawable ) + XFreePixmap( static_cast(mpDisplay), mhDrawable ); + } + + /** + * Surface::Surface: Create Canvas surface with existing data + * @param pSysData Platform native system environment data (struct SystemEnvData in vcl/inc/sysdata.hxx) + * @param pSurface Cairo surface + * + * pSysData contains the platform native Drawable reference + * This constructor only stores data, it does no processing. + * It is used by e.g. Surface::getSimilar() + * + * Set the mpSurface as pSurface + **/ + X11Surface::X11Surface( const X11SysData& rSysData, + const X11PixmapSharedPtr& rPixmap, + const CairoSurfaceSharedPtr& pSurface ) : + maSysData(rSysData), + mpPixmap(rPixmap), + mpSurface(pSurface) + {} + + /** + * Surface::Surface: Create generic Canvas surface using given Cairo Surface + * + * @param pSurface Cairo Surface + * + * This constructor only stores data, it does no processing. + * It is used with e.g. cairo_image_surface_create_for_data() + * Unlike other constructors, mpSysData is set to NULL + * + * Set the mpSurface as pSurface + **/ + X11Surface::X11Surface( const CairoSurfaceSharedPtr& pSurface ) : + maSysData(), + mpSurface(pSurface) + {} + + /** + * Surface::Surface: Create Canvas surface from Window reference. + * @param pSysData Platform native system environment data (struct SystemEnvData in vcl/inc/sysdata.hxx) + * @param x horizontal location of the new surface + * @param y vertical location of the new surface + * @param width width of the new surface + * @param height height of the new surface + * + * pSysData contains the platform native Window reference. + * + * pSysData is used to create a surface on the Window + * + * Set the mpSurface to the new surface or NULL + **/ + X11Surface::X11Surface( const X11SysData& rSysData, int x, int y, int width, int height ) : + maSysData(rSysData), + mpSurface( + cairo_xlib_surface_create( static_cast(rSysData.pDisplay), + rSysData.hDrawable, + static_cast(rSysData.pVisual), + width + x, height + y ), + &cairo_surface_destroy) + { + cairo_surface_set_device_offset(mpSurface.get(), x, y ); + } + + /** + * Surface::Surface: Create platform native Canvas surface from BitmapSystemData + * @param pSysData Platform native system environment data (struct SystemEnvData in vcl/inc/sysdata.hxx) + * @param pBmpData Platform native image data (struct BitmapSystemData in vcl/inc/bitmap.hxx) + * @param width width of the new surface + * @param height height of the new surface + * + * The pBmpData provides the imagedata that the created surface should contain. + * + * Set the mpSurface to the new surface or NULL + **/ + X11Surface::X11Surface( const X11SysData& rSysData, + const BitmapSystemData& rData ) : + maSysData( rSysData ), + mpSurface( + cairo_xlib_surface_create( static_cast(rSysData.pDisplay), + reinterpret_cast(rData.aPixmap), + static_cast(rSysData.pVisual), + rData.mnWidth, rData.mnHeight ), + &cairo_surface_destroy) + { + } + + /** + * Surface::getCairo: Create Cairo (drawing object) for the Canvas surface + * + * @return new Cairo or NULL + **/ + CairoSharedPtr X11Surface::getCairo() const + { + return CairoSharedPtr( cairo_create(mpSurface.get()), + &cairo_destroy ); + } + + /** + * Surface::getSimilar: Create new similar Canvas surface + * @param cairo_content_type format of the new surface (cairo_content_t from cairo/src/cairo.h) + * @param width width of the new surface + * @param height height of the new surface + * + * Creates a new Canvas surface. This normally creates platform native surface, even though + * generic function is used. + * + * Cairo surface from cairo_content_type (cairo_content_t) + * + * @return new surface or NULL + **/ + SurfaceSharedPtr X11Surface::getSimilar(int cairo_content_type, int width, int height ) const + { + if( maSysData.pDisplay && maSysData.hDrawable ) + { + XRenderPictFormat* pFormat; + int nFormat; + + switch (cairo_content_type) + { + case CAIRO_CONTENT_ALPHA: + nFormat = PictStandardA8; + break; + case CAIRO_CONTENT_COLOR: + nFormat = PictStandardRGB24; + break; + case CAIRO_CONTENT_COLOR_ALPHA: + default: + nFormat = PictStandardARGB32; + break; + } + + pFormat = XRenderFindStandardFormat( static_cast(maSysData.pDisplay), nFormat ); + Pixmap hPixmap = limitXCreatePixmap( static_cast(maSysData.pDisplay), maSysData.hDrawable, + width > 0 ? width : 1, height > 0 ? height : 1, + pFormat->depth ); + + X11SysData aSysData(maSysData); + aSysData.pRenderFormat = pFormat; + return SurfaceSharedPtr( + new X11Surface( aSysData, + std::make_shared(hPixmap, maSysData.pDisplay), + CairoSurfaceSharedPtr( + cairo_xlib_surface_create_with_xrender_format( + static_cast(maSysData.pDisplay), + hPixmap, + ScreenOfDisplay(static_cast(maSysData.pDisplay), maSysData.nScreen), + pFormat, width, height ), + &cairo_surface_destroy) )); + } + else + return SurfaceSharedPtr( + new X11Surface( maSysData, + X11PixmapSharedPtr(), + CairoSurfaceSharedPtr( + cairo_surface_create_similar( mpSurface.get(), + static_cast(cairo_content_type), width, height ), + &cairo_surface_destroy ))); + } + + VclPtr X11Surface::createVirtualDevice() const + { + SystemGraphicsData aSystemGraphicsData; + + cairo_surface_t* pSurface = mpSurface.get(); + + aSystemGraphicsData.nSize = sizeof(SystemGraphicsData); + aSystemGraphicsData.hDrawable = mpPixmap ? mpPixmap->mhDrawable : maSysData.hDrawable; + aSystemGraphicsData.pXRenderFormat = maSysData.pRenderFormat; + aSystemGraphicsData.pSurface = pSurface; + + int width = cairo_xlib_surface_get_width(pSurface); + int height = cairo_xlib_surface_get_height(pSurface); + + return VclPtr::Create(aSystemGraphicsData, + Size(width, height), + getFormat()); + } + + /** + * Surface::Resize: Resizes the Canvas surface. + * @param width new width of the surface + * @param height new height of the surface + * + * Only used on X11. + * + * @return The new surface or NULL + **/ + bool X11Surface::Resize(int width, int height) + { + cairo_xlib_surface_set_size(mpSurface.get(), width, height); + return true; + } + + void X11Surface::flush() const + { + XSync( static_cast(maSysData.pDisplay), false ); + } + + /** + * Surface::getDepth: Get the color depth of the Canvas surface. + * + * @return color depth + **/ + int X11Surface::getDepth() const + { + if (maSysData.pRenderFormat) + return static_cast(maSysData.pRenderFormat)->depth; + return -1; + } + + /** + * Surface::getFormat: Get the device format of the Canvas surface. + * + * @return color format + **/ + DeviceFormat X11Surface::getFormat() const + { + if (!maSysData.pRenderFormat) + return DeviceFormat::DEFAULT; + assert (static_cast(maSysData.pRenderFormat)->depth != 1 && "unsupported"); + return DeviceFormat::DEFAULT; + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/unx/generic/gdi/cairo_xlib_cairo.hxx b/vcl/unx/generic/gdi/cairo_xlib_cairo.hxx new file mode 100644 index 000000000..c44fde437 --- /dev/null +++ b/vcl/unx/generic/gdi/cairo_xlib_cairo.hxx @@ -0,0 +1,96 @@ +/* -*- 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 . + */ + +#pragma once + +#include +#include +#include + +struct BitmapSystemData; +class SalFrame; +struct SystemEnvData; +struct SystemGraphicsData; + +namespace cairo { + + /// Holds all X11-output relevant data + struct X11SysData + { + X11SysData(); + explicit X11SysData( const SystemGraphicsData& ); + explicit X11SysData( const SystemEnvData&, const SalFrame* pReference ); + + void* pDisplay; // the relevant display connection + Drawable hDrawable; // a drawable + void* pVisual; // the visual in use + int nScreen; // the current screen of the drawable + void* pRenderFormat; // render format for drawable + }; + + /// RAII wrapper for a pixmap + struct X11Pixmap + { + void* mpDisplay; // the relevant display connection + Pixmap mhDrawable; // a drawable + + X11Pixmap( Pixmap hDrawable, void* pDisplay ) : + mpDisplay(pDisplay), + mhDrawable(hDrawable) + {} + + ~X11Pixmap(); + }; + + typedef std::shared_ptr X11PixmapSharedPtr; + + class X11Surface : public Surface + { + const X11SysData maSysData; + X11PixmapSharedPtr mpPixmap; + CairoSurfaceSharedPtr mpSurface; + + X11Surface( const X11SysData& rSysData, const X11PixmapSharedPtr& rPixmap, const CairoSurfaceSharedPtr& pSurface ); + + public: + /// takes over ownership of passed cairo_surface + explicit X11Surface( const CairoSurfaceSharedPtr& pSurface ); + /// create surface on subarea of given drawable + X11Surface( const X11SysData& rSysData, int x, int y, int width, int height ); + /// create surface for given bitmap data + X11Surface( const X11SysData& rSysData, const BitmapSystemData& rBmpData ); + + // Surface interface + virtual CairoSharedPtr getCairo() const override; + virtual CairoSurfaceSharedPtr getCairoSurface() const override { return mpSurface; } + virtual SurfaceSharedPtr getSimilar(int cairo_content_type, int width, int height) const override; + + virtual VclPtr createVirtualDevice() const override; + + virtual bool Resize( int width, int height ) override; + + virtual void flush() const override; + + int getDepth() const; + DeviceFormat getFormat() const; + const X11PixmapSharedPtr& getPixmap() const { return mpPixmap; } + }; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/unx/generic/gdi/cairotextrender.cxx b/vcl/unx/generic/gdi/cairotextrender.cxx new file mode 100644 index 000000000..898110fee --- /dev/null +++ b/vcl/unx/generic/gdi/cairotextrender.cxx @@ -0,0 +1,382 @@ +/* -*- 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 + +#include + +#include +#include +#include +#include +#include + +#include +#include +#if defined(CAIRO_HAS_SVG_SURFACE) +#include +#elif defined(CAIRO_HAS_PDF_SURFACE) +#include +#endif + +#include + +namespace { + +typedef struct FT_FaceRec_* FT_Face; + +class CairoFontsCache +{ +public: + struct CacheId + { + FT_Face maFace; + const FontConfigFontOptions *mpOptions; + bool mbEmbolden; + bool mbVerticalMetrics; + bool operator ==(const CacheId& rOther) const + { + return maFace == rOther.maFace && + mpOptions == rOther.mpOptions && + mbEmbolden == rOther.mbEmbolden && + mbVerticalMetrics == rOther.mbVerticalMetrics; + } + }; + +private: + typedef std::deque< std::pair > LRUFonts; + static LRUFonts maLRUFonts; +public: + CairoFontsCache() = delete; + + static void CacheFont(cairo_font_face_t* pFont, const CacheId &rId); + static cairo_font_face_t* FindCachedFont(const CacheId &rId); +}; + +CairoFontsCache::LRUFonts CairoFontsCache::maLRUFonts; + +void CairoFontsCache::CacheFont(cairo_font_face_t* pFont, const CairoFontsCache::CacheId &rId) +{ + maLRUFonts.push_front( std::pair(pFont, rId) ); + if (maLRUFonts.size() > 8) + { + cairo_font_face_destroy(maLRUFonts.back().first); + maLRUFonts.pop_back(); + } +} + +cairo_font_face_t* CairoFontsCache::FindCachedFont(const CairoFontsCache::CacheId &rId) +{ + auto aI = std::find_if(maLRUFonts.begin(), maLRUFonts.end(), + [&rId](const LRUFonts::value_type& rFont) { return rFont.second == rId; }); + if (aI != maLRUFonts.end()) + return aI->first; + return nullptr; +} + +} + +namespace +{ + bool hasRotation(int nRotation) + { + return nRotation != 0; + } + + double toRadian(Degree10 nDegree10th) + { + return toRadians(3600_deg10 - nDegree10th); + } + + cairo_t* syncCairoContext(cairo_t* cr) + { + //rhbz#1283420 tdf#117413 bodge to force a read from the underlying surface which has + //the side effect of making the mysterious xrender related problem go away + cairo_surface_t *target = cairo_get_target(cr); + if (cairo_surface_get_type(target) == CAIRO_SURFACE_TYPE_XLIB) + { + cairo_surface_t *throw_away = cairo_surface_create_similar(target, cairo_surface_get_content(target), 1, 1); + cairo_t *force_read_cr = cairo_create(throw_away); + cairo_set_source_surface(force_read_cr, target, 0, 0); + cairo_paint(force_read_cr); + cairo_destroy(force_read_cr); + cairo_surface_destroy(throw_away); + } + return cr; + } +} + +#if defined(FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION) +extern "C" +{ + __attribute__((weak)) void __lsan_disable(); + __attribute__((weak)) void __lsan_enable(); +} +#endif + +CairoTextRender::CairoTextRender() +{ + // https://gitlab.freedesktop.org/cairo/cairo/-/merge_requests/235 + // I don't want to have CAIRO_ROUND_GLYPH_POS_ON set in the cairo surfaces + // font_options when trying subpixel rendering, but that's a private + // feature of cairo_font_options_t, so tricky to achieve. Hack this by + // getting the font options of a backend known to set this private feature + // to CAIRO_ROUND_GLYPH_POS_OFF and then set to defaults the public + // features and the result can be merged with new font options to set + // CAIRO_ROUND_GLYPH_POS_OFF in those + mpRoundGlyphPosOffOptions = cairo_font_options_create(); +#if defined(CAIRO_HAS_SVG_SURFACE) + // svg, pdf and ps backends have CAIRO_ROUND_GLYPH_POS_OFF by default + cairo_surface_t* hack = cairo_svg_surface_create(nullptr, 1, 1); +#elif defined(CAIRO_HAS_PDF_SURFACE) + cairo_surface_t* hack = cairo_pdf_surface_create(nullptr, 1, 1); +#endif + cairo_surface_get_font_options(hack, mpRoundGlyphPosOffOptions); + cairo_surface_destroy(hack); + cairo_font_options_set_antialias(mpRoundGlyphPosOffOptions, CAIRO_ANTIALIAS_DEFAULT); + cairo_font_options_set_subpixel_order(mpRoundGlyphPosOffOptions, CAIRO_SUBPIXEL_ORDER_DEFAULT); + cairo_font_options_set_hint_style(mpRoundGlyphPosOffOptions, CAIRO_HINT_STYLE_DEFAULT); + cairo_font_options_set_hint_metrics(mpRoundGlyphPosOffOptions, CAIRO_HINT_METRICS_DEFAULT); +} + +CairoTextRender::~CairoTextRender() +{ + cairo_font_options_destroy(mpRoundGlyphPosOffOptions); +} + +void CairoTextRender::DrawTextLayout(const GenericSalLayout& rLayout, const SalGraphics& rGraphics) +{ + const FreetypeFontInstance& rInstance = static_cast(rLayout.GetFont()); + const FreetypeFont& rFont = rInstance.GetFreetypeFont(); + + const bool bResolutionIndependentLayoutEnabled = rGraphics.getTextRenderModeForResolutionIndependentLayoutEnabled(); + + std::vector cairo_glyphs; + std::vector glyph_extrarotation; + cairo_glyphs.reserve( 256 ); + + DevicePoint aPos; + const GlyphItem* pGlyph; + int nStart = 0; + while (rLayout.GetNextGlyph(&pGlyph, aPos, nStart)) + { + cairo_glyph_t aGlyph; + aGlyph.index = pGlyph->glyphId(); + aGlyph.x = aPos.getX(); + aGlyph.y = aPos.getY(); + + const bool bVertical = pGlyph->IsVertical(); + glyph_extrarotation.push_back(bVertical ? 1 : 0); + + // tdf#150507 like skia even when subpixel rendering pixel snap y + if (bResolutionIndependentLayoutEnabled) + { + if (!bVertical) + aGlyph.y = std::floor(aGlyph.y + 0.5); + else + aGlyph.x = std::floor(aGlyph.x + 0.5); + } + + cairo_glyphs.push_back(aGlyph); + } + + if (cairo_glyphs.empty()) + return; + + const vcl::font::FontSelectPattern& rFSD = rInstance.GetFontSelectPattern(); + int nHeight = rFSD.mnHeight; + int nWidth = rFSD.mnWidth ? rFSD.mnWidth : nHeight; + if (nWidth == 0 || nHeight == 0) + return; + + if (nHeight > SAL_MAX_UINT16) + { + // as seen with freetype 2.11.0, so cairo surface status is "fail" + // ("error occurred in libfreetype") and no further operations are + // executed, so this error then leads to later leaks + SAL_WARN("vcl", "rendering text would fail with height: " << nHeight); + return; + } + + int nRatio = nWidth * 10 / nHeight; + if (FreetypeFont::AlmostHorizontalDrainsRenderingPool(nRatio, rFSD)) + return; + + /* + * It might be ideal to cache surface and cairo context between calls and + * only destroy it when the drawable changes, but to do that we need to at + * least change the SalFrame etc impls to dtor the SalGraphics *before* the + * destruction of the windows they reference + */ + cairo_t *cr = syncCairoContext(getCairoContext()); + if (!cr) + { + SAL_WARN("vcl", "no cairo context for text"); + return; + } + +#if defined(FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION) + if (__lsan_disable) + __lsan_disable(); +#endif + + const StyleSettings& rStyleSettings = Application::GetSettings().GetStyleSettings(); + const bool bDisableAA = !rStyleSettings.GetUseFontAAFromSystem() && !rGraphics.getAntiAlias(); + + const cairo_font_options_t* pFontOptions = GetSalInstance()->GetCairoFontOptions(); + if (pFontOptions || bDisableAA || bResolutionIndependentLayoutEnabled) + { + cairo_hint_style_t eHintStyle = pFontOptions ? cairo_font_options_get_hint_style(pFontOptions) : CAIRO_HINT_STYLE_DEFAULT; + bool bAllowedHintStyle = !bResolutionIndependentLayoutEnabled || (eHintStyle == CAIRO_HINT_STYLE_NONE || eHintStyle == CAIRO_HINT_STYLE_SLIGHT); + + if (bDisableAA || !bAllowedHintStyle || bResolutionIndependentLayoutEnabled) + { + // Disable font AA in case global AA setting is supposed to affect + // font rendering (not the default) and AA is disabled. + cairo_font_options_t* pOptions = pFontOptions ? cairo_font_options_copy(pFontOptions) : cairo_font_options_create(); + + if (bDisableAA) + cairo_font_options_set_antialias(pOptions, CAIRO_ANTIALIAS_NONE); + if (!bAllowedHintStyle) + cairo_font_options_set_hint_style(pOptions, CAIRO_HINT_STYLE_SLIGHT); + // Disable private CAIRO_ROUND_GLYPH_POS_ON by merging with font options known to have + // CAIRO_ROUND_GLYPH_POS_OFF + if (bResolutionIndependentLayoutEnabled) + cairo_font_options_merge(pOptions, mpRoundGlyphPosOffOptions); + cairo_set_font_options(cr, pOptions); + cairo_font_options_destroy(pOptions); + } + else if (pFontOptions) + cairo_set_font_options(cr, pFontOptions); + } + + double nDX, nDY; + getSurfaceOffset(nDX, nDY); + cairo_translate(cr, nDX, nDY); + + clipRegion(cr); + + cairo_set_source_rgb(cr, + mnTextColor.GetRed()/255.0, + mnTextColor.GetGreen()/255.0, + mnTextColor.GetBlue()/255.0); + + FT_Face aFace = rFont.GetFtFace(); + CairoFontsCache::CacheId aId; + aId.maFace = aFace; + aId.mpOptions = rFont.GetFontOptions(); + aId.mbEmbolden = rFont.NeedsArtificialBold(); + + cairo_matrix_t m; + + std::vector::const_iterator aEnd = glyph_extrarotation.end(); + std::vector::const_iterator aStart = glyph_extrarotation.begin(); + std::vector::const_iterator aI = aStart; + while (aI != aEnd) + { + int nGlyphRotation = *aI; + + std::vector::const_iterator aNext = nGlyphRotation?(aI+1):std::find_if(aI+1, aEnd, hasRotation); + + size_t nStartIndex = std::distance(aStart, aI); + size_t nLen = std::distance(aI, aNext); + + aId.mbVerticalMetrics = nGlyphRotation != 0.0; + cairo_font_face_t* font_face = CairoFontsCache::FindCachedFont(aId); + if (!font_face) + { + const FontConfigFontOptions *pOptions = aId.mpOptions; + FcPattern *pPattern = pOptions->GetPattern(); + font_face = cairo_ft_font_face_create_for_pattern(pPattern); + CairoFontsCache::CacheFont(font_face, aId); + } + cairo_set_font_face(cr, font_face); + + cairo_set_font_size(cr, nHeight); + + cairo_matrix_init_identity(&m); + + if (rLayout.GetOrientation()) + cairo_matrix_rotate(&m, toRadian(rLayout.GetOrientation())); + + cairo_matrix_scale(&m, nWidth, nHeight); + + if (nGlyphRotation) + { + cairo_matrix_rotate(&m, toRadian(Degree10(nGlyphRotation * 900))); + + cairo_matrix_t em_square; + cairo_matrix_init_identity(&em_square); + cairo_get_matrix(cr, &em_square); + + cairo_matrix_scale(&em_square, aFace->units_per_EM, + aFace->units_per_EM); + cairo_set_matrix(cr, &em_square); + + cairo_font_extents_t font_extents; + cairo_font_extents(cr, &font_extents); + + cairo_matrix_init_identity(&em_square); + cairo_set_matrix(cr, &em_square); + } + + if (rFont.NeedsArtificialItalic()) + { + cairo_matrix_t shear; + cairo_matrix_init_identity(&shear); + shear.xy = -shear.xx * 0x6000L / 0x10000L; + cairo_matrix_multiply(&m, &shear, &m); + } + + cairo_set_font_matrix(cr, &m); + cairo_show_glyphs(cr, &cairo_glyphs[nStartIndex], nLen); + if (cairo_status(cr) != CAIRO_STATUS_SUCCESS) + { + SAL_WARN("vcl", "rendering text failed with stretch ratio of: " << nRatio << ", " << cairo_status_to_string(cairo_status(cr))); + } + +#if OSL_DEBUG_LEVEL > 2 + //draw origin + cairo_save (cr); + cairo_rectangle (cr, cairo_glyphs[nStartIndex].x, cairo_glyphs[nStartIndex].y, 5, 5); + cairo_set_source_rgba (cr, 1, 0, 0, 0.80); + cairo_fill (cr); + cairo_restore (cr); +#endif + + aI = aNext; + } + + releaseCairoContext(cr); + +#if defined(FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION) + if (__lsan_enable) + __lsan_enable(); +#endif +} + +void FontConfigFontOptions::cairo_font_options_substitute(FcPattern* pPattern) +{ + const cairo_font_options_t* pFontOptions = GetSalInstance()->GetCairoFontOptions(); + if( !pFontOptions ) + return; + cairo_ft_font_options_substitute(pFontOptions, pPattern); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/unx/generic/gdi/font.cxx b/vcl/unx/generic/gdi/font.cxx new file mode 100644 index 000000000..57ff99a1c --- /dev/null +++ b/vcl/unx/generic/gdi/font.cxx @@ -0,0 +1,116 @@ +/* -*- 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 + +#include +#include + +#include +#include +#include + +void X11SalGraphics::DrawTextLayout(const GenericSalLayout& rLayout) +{ + mxTextRenderImpl->DrawTextLayout(rLayout, *this); +} + +FontCharMapRef X11SalGraphics::GetFontCharMap() const +{ + return mxTextRenderImpl->GetFontCharMap(); +} + +bool X11SalGraphics::GetFontCapabilities(vcl::FontCapabilities &rGetImplFontCapabilities) const +{ + return mxTextRenderImpl->GetFontCapabilities(rGetImplFontCapabilities); +} + +// SalGraphics +void X11SalGraphics::SetFont(LogicalFontInstance* pEntry, int nFallbackLevel) +{ + mxTextRenderImpl->SetFont(pEntry, nFallbackLevel); +} + +void +X11SalGraphics::SetTextColor( Color nColor ) +{ + mxTextRenderImpl->SetTextColor(nColor); +} + +bool X11SalGraphics::AddTempDevFont( vcl::font::PhysicalFontCollection* pFontCollection, + const OUString& rFileURL, + const OUString& rFontName ) +{ + return mxTextRenderImpl->AddTempDevFont(pFontCollection, rFileURL, rFontName); +} + +void X11SalGraphics::ClearDevFontCache() +{ + mxTextRenderImpl->ClearDevFontCache(); +} + +void X11SalGraphics::GetDevFontList( vcl::font::PhysicalFontCollection* pFontCollection ) +{ + mxTextRenderImpl->GetDevFontList(pFontCollection); +} + +void +X11SalGraphics::GetFontMetric( ImplFontMetricDataRef &rxFontMetric, int nFallbackLevel ) +{ + mxTextRenderImpl->GetFontMetric(rxFontMetric, nFallbackLevel); +} + +std::unique_ptr X11SalGraphics::GetTextLayout(int nFallbackLevel) +{ + return mxTextRenderImpl->GetTextLayout(nFallbackLevel); +} + +bool X11SalGraphics::CreateFontSubset( + const OUString& rToFile, + const vcl::font::PhysicalFontFace* pFont, + const sal_GlyphId* pGlyphIds, + const sal_uInt8* pEncoding, + sal_Int32* pWidths, + int nGlyphCount, + FontSubsetInfo& rInfo + ) +{ + return mxTextRenderImpl->CreateFontSubset(rToFile, pFont, + pGlyphIds, pEncoding, pWidths, nGlyphCount, rInfo); +} + +const void* X11SalGraphics::GetEmbedFontData(const vcl::font::PhysicalFontFace* pFont, tools::Long* pDataLen) +{ + return mxTextRenderImpl->GetEmbedFontData(pFont, pDataLen); +} + +void X11SalGraphics::FreeEmbedFontData( const void* pData, tools::Long nLen ) +{ + mxTextRenderImpl->FreeEmbedFontData(pData, nLen); +} + +void X11SalGraphics::GetGlyphWidths( const vcl::font::PhysicalFontFace* pFont, + bool bVertical, + std::vector< sal_Int32 >& rWidths, + Ucs2UIntMap& rUnicodeEnc ) +{ + mxTextRenderImpl->GetGlyphWidths(pFont, bVertical, rWidths, rUnicodeEnc); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/unx/generic/gdi/freetypetextrender.cxx b/vcl/unx/generic/gdi/freetypetextrender.cxx new file mode 100644 index 000000000..ea8474e69 --- /dev/null +++ b/vcl/unx/generic/gdi/freetypetextrender.cxx @@ -0,0 +1,216 @@ +/* -*- 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 + +#include + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include + +FreeTypeTextRenderImpl::FreeTypeTextRenderImpl() + : mnTextColor(Color(0x00, 0x00, 0x00)) //black +{ +} + +FreeTypeTextRenderImpl::~FreeTypeTextRenderImpl() +{ + ReleaseFonts(); +} + +void FreeTypeTextRenderImpl::SetFont(LogicalFontInstance *pEntry, int nFallbackLevel) +{ + // release all no longer needed font resources + for( int i = nFallbackLevel; i < MAX_FALLBACK; ++i ) + { + // old server side font is no longer referenced + mpFreetypeFont[i] = nullptr; + } + + // return early if there is no new font + if( !pEntry ) + return; + + FreetypeFontInstance* pFreetypeFont = static_cast(pEntry); + mpFreetypeFont[ nFallbackLevel ] = pFreetypeFont; + + // ignore fonts with e.g. corrupted font files + if (!mpFreetypeFont[nFallbackLevel]->GetFreetypeFont().TestFont()) + mpFreetypeFont[nFallbackLevel] = nullptr; +} + +FontCharMapRef FreeTypeTextRenderImpl::GetFontCharMap() const +{ + if (!mpFreetypeFont[0]) + return nullptr; + return mpFreetypeFont[0]->GetFreetypeFont().GetFontCharMap(); +} + +bool FreeTypeTextRenderImpl::GetFontCapabilities(vcl::FontCapabilities &rGetImplFontCapabilities) const +{ + if (!mpFreetypeFont[0]) + return false; + return mpFreetypeFont[0]->GetFreetypeFont().GetFontCapabilities(rGetImplFontCapabilities); +} + +// SalGraphics +void +FreeTypeTextRenderImpl::SetTextColor( Color nColor ) +{ + if( mnTextColor != nColor ) + { + mnTextColor = nColor; + } +} + +bool FreeTypeTextRenderImpl::AddTempDevFont( vcl::font::PhysicalFontCollection* pFontCollection, + const OUString& rFileURL, + const OUString& rFontName ) +{ + return GenPspGraphics::AddTempDevFontHelper(pFontCollection, rFileURL, rFontName); +} + +void FreeTypeTextRenderImpl::ClearDevFontCache() +{ + FreetypeManager::get().ClearFontCache(); +} + +void FreeTypeTextRenderImpl::GetDevFontList( vcl::font::PhysicalFontCollection* pFontCollection ) +{ + // prepare the FreetypeManager using psprint's font infos + FreetypeManager& rFreetypeManager = FreetypeManager::get(); + + psp::PrintFontManager& rMgr = psp::PrintFontManager::get(); + ::std::vector< psp::fontID > aList; + psp::FastPrintFontInfo aInfo; + rMgr.getFontList( aList ); + for (auto const& elem : aList) + { + if( !rMgr.getFontFastInfo( elem, aInfo ) ) + continue; + + // normalize face number to the FreetypeManager + int nFaceNum = rMgr.getFontFaceNumber( aInfo.m_nID ); + int nVariantNum = rMgr.getFontFaceVariation( aInfo.m_nID ); + + // inform FreetypeManager about this font provided by the PsPrint subsystem + FontAttributes aDFA = GenPspGraphics::Info2FontAttributes( aInfo ); + aDFA.IncreaseQualityBy( 4096 ); + const OString& rFileName = rMgr.getFontFileSysPath( aInfo.m_nID ); + rFreetypeManager.AddFontFile(rFileName, nFaceNum, nVariantNum, aInfo.m_nID, aDFA); + } + + // announce glyphcache fonts + rFreetypeManager.AnnounceFonts(pFontCollection); + + // register platform specific font substitutions if available + if (!utl::ConfigManager::IsFuzzing()) + SalGenericInstance::RegisterFontSubstitutors( pFontCollection ); +} + +void FreeTypeTextRenderImpl::GetFontMetric( ImplFontMetricDataRef& rxFontMetric, int nFallbackLevel ) +{ + if( nFallbackLevel >= MAX_FALLBACK ) + return; + + if (mpFreetypeFont[nFallbackLevel]) + mpFreetypeFont[nFallbackLevel]->GetFreetypeFont().GetFontMetric(rxFontMetric); +} + +std::unique_ptr FreeTypeTextRenderImpl::GetTextLayout(int nFallbackLevel) +{ + assert(mpFreetypeFont[nFallbackLevel]); + if (!mpFreetypeFont[nFallbackLevel]) + return nullptr; + return std::make_unique(*mpFreetypeFont[nFallbackLevel]); +} + +bool FreeTypeTextRenderImpl::CreateFontSubset( + const OUString& rToFile, + const vcl::font::PhysicalFontFace* pFont, + const sal_GlyphId* pGlyphIds, + const sal_uInt8* pEncoding, + sal_Int32* pWidths, + int nGlyphCount, + FontSubsetInfo& rInfo + ) +{ + // in this context the pFont->GetFontId() is a valid PSP + // font since they are the only ones left after the PDF + // export has filtered its list of subsettable fonts (for + // which this method was created). The correct way would + // be to have the FreetypeManager search for the PhysicalFontFace pFont + psp::fontID aFont = pFont->GetFontId(); + + psp::PrintFontManager& rMgr = psp::PrintFontManager::get(); + bool bSuccess = rMgr.createFontSubset( rInfo, + aFont, + rToFile, + pGlyphIds, + pEncoding, + pWidths, + nGlyphCount ); + return bSuccess; +} + +const void* FreeTypeTextRenderImpl::GetEmbedFontData(const vcl::font::PhysicalFontFace* pFont, tools::Long* pDataLen) +{ + // in this context the pFont->GetFontId() is a valid PSP + // font since they are the only ones left after the PDF + // export has filtered its list of subsettable fonts (for + // which this method was created). The correct way would + // be to have the FreetypeManager search for the PhysicalFontFace pFont + psp::fontID aFont = pFont->GetFontId(); + return GenPspGraphics::DoGetEmbedFontData(aFont, pDataLen); +} + +void FreeTypeTextRenderImpl::FreeEmbedFontData( const void* pData, tools::Long nLen ) +{ + GenPspGraphics::DoFreeEmbedFontData( pData, nLen ); +} + +void FreeTypeTextRenderImpl::GetGlyphWidths( const vcl::font::PhysicalFontFace* pFont, + bool bVertical, + std::vector< sal_Int32 >& rWidths, + Ucs2UIntMap& rUnicodeEnc ) +{ + // in this context the pFont->GetFontId() is a valid PSP + // font since they are the only ones left after the PDF + // export has filtered its list of subsettable fonts (for + // which this method was created). The correct way would + // be to have the FreetypeManager search for the PhysicalFontFace pFont + psp::fontID aFont = pFont->GetFontId(); + GenPspGraphics::DoGetGlyphWidths( aFont, bVertical, rWidths, rUnicodeEnc ); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/unx/generic/gdi/gdiimpl.cxx b/vcl/unx/generic/gdi/gdiimpl.cxx new file mode 100644 index 000000000..d5fc4d6c1 --- /dev/null +++ b/vcl/unx/generic/gdi/gdiimpl.cxx @@ -0,0 +1,2015 @@ +/* -*- 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 +#include + +#include +#include +#include +#include + +#include "gdiimpl.hxx" + +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#undef SALGDI2_TESTTRANS + +#if (OSL_DEBUG_LEVEL > 1) && defined SALGDI2_TESTTRANS +#define DBG_TESTTRANS( _def_drawable ) \ +{ \ + XCopyArea( pXDisp, _def_drawable, aDrawable, GetCopyGC(), \ + 0, 0, \ + rPosAry.mnDestWidth, rPosAry.mnDestHeight, \ + 0, 0 ); \ +} +#else // (OSL_DEBUG_LEVEL > 1) && defined SALGDI2_TESTTRANS +#define DBG_TESTTRANS( _def_drawable ) +#endif // (OSL_DEBUG_LEVEL > 1) && defined SALGDI2_TESTTRANS + +/* From */ +typedef unsigned long Pixel; + +class SalPolyLine +{ + std::vector Points_; +public: + SalPolyLine(sal_uLong nPoints, const Point *p) + : Points_(nPoints+1) + { + for (sal_uLong i = 0; i < nPoints; ++i) + { + Points_[i].x = static_cast(p[i].getX()); + Points_[i].y = static_cast(p[i].getY()); + } + Points_[nPoints] = Points_[0]; // close polyline + } + + const XPoint &operator[](sal_uLong n) const + { + return Points_[n]; + } + + XPoint &operator[](sal_uLong n) + { + return Points_[n]; + } +}; + +namespace +{ + void setForeBack(XGCValues& rValues, const SalColormap& rColMap, const SalBitmap& rSalBitmap) + { + rValues.foreground = rColMap.GetWhitePixel(); + rValues.background = rColMap.GetBlackPixel(); + + //fdo#33455 and fdo#80160 handle 1 bit depth pngs with palette entries + //to set fore/back colors + SalBitmap& rBitmap = const_cast(rSalBitmap); + BitmapBuffer* pBitmapBuffer = rBitmap.AcquireBuffer(BitmapAccessMode::Read); + if (!pBitmapBuffer) + return; + + const BitmapPalette& rPalette = pBitmapBuffer->maPalette; + if (rPalette.GetEntryCount() == 2) + { + const BitmapColor aWhite(rPalette[rPalette.GetBestIndex(COL_WHITE)]); + rValues.foreground = rColMap.GetPixel(aWhite); + + const BitmapColor aBlack(rPalette[rPalette.GetBestIndex(COL_BLACK)]); + rValues.background = rColMap.GetPixel(aBlack); + } + rBitmap.ReleaseBuffer(pBitmapBuffer, BitmapAccessMode::Read); + } +} + +X11SalGraphicsImpl::X11SalGraphicsImpl(X11SalGraphics& rParent): + mrParent(rParent), + mnBrushColor( 0xFF, 0xFF, 0XFF ), + mpBrushGC(nullptr), + mnBrushPixel(0), + mbPenGC(false), + mbBrushGC(false), + mbCopyGC(false), + mbInvertGC(false), + mbInvert50GC(false), + mbStippleGC(false), + mbTrackingGC(false), + mbDitherBrush(false), + mbXORMode(false), + mpPenGC(nullptr), + mnPenColor( 0x00, 0x00, 0x00 ), + mnPenPixel(0), + mpMonoGC(nullptr), + mpCopyGC(nullptr), + mpMaskGC(nullptr), + mpInvertGC(nullptr), + mpInvert50GC(nullptr), + mpStippleGC(nullptr), + mpTrackingGC(nullptr) +{ +} + +X11SalGraphicsImpl::~X11SalGraphicsImpl() +{ +} + +void X11SalGraphicsImpl::Init() +{ + mnPenPixel = mrParent.GetPixel( mnPenColor ); + mnBrushPixel = mrParent.GetPixel( mnBrushColor ); +} + +XID X11SalGraphicsImpl::GetXRenderPicture() +{ + XRenderPeer& rRenderPeer = XRenderPeer::GetInstance(); + + if( !mrParent.m_aXRenderPicture ) + { + // check xrender support for matching visual + XRenderPictFormat* pXRenderFormat = mrParent.GetXRenderFormat(); + if( !pXRenderFormat ) + return 0; + // get the matching xrender target for drawable + mrParent.m_aXRenderPicture = rRenderPeer.CreatePicture( mrParent.GetDrawable(), pXRenderFormat, 0, nullptr ); + } + + { + // reset clip region + // TODO: avoid clip reset if already done + XRenderPictureAttributes aAttr; + aAttr.clip_mask = None; + rRenderPeer.ChangePicture( mrParent.m_aXRenderPicture, CPClipMask, &aAttr ); + } + + return mrParent.m_aXRenderPicture; +} + +static void freeGC(Display *pDisplay, GC& rGC) +{ + if( rGC ) + { + XFreeGC( pDisplay, rGC ); + rGC = None; + } +} + +void X11SalGraphicsImpl::freeResources() +{ + Display *pDisplay = mrParent.GetXDisplay(); + + freeGC( pDisplay, mpPenGC ); + freeGC( pDisplay, mpBrushGC ); + freeGC( pDisplay, mpMonoGC ); + freeGC( pDisplay, mpTrackingGC ); + freeGC( pDisplay, mpCopyGC ); + freeGC( pDisplay, mpMaskGC ); + freeGC( pDisplay, mpInvertGC ); + freeGC( pDisplay, mpInvert50GC ); + freeGC( pDisplay, mpStippleGC ); + mbTrackingGC = mbPenGC = mbBrushGC = mbCopyGC = mbInvertGC = mbInvert50GC = mbStippleGC = false; +} + +GC X11SalGraphicsImpl::CreateGC( Drawable hDrawable, unsigned long nMask ) +{ + XGCValues values; + + values.graphics_exposures = False; + values.foreground = mrParent.GetColormap().GetBlackPixel() + ^ mrParent.GetColormap().GetWhitePixel(); + values.function = GXxor; + values.line_width = 1; + values.fill_style = FillStippled; + values.stipple = mrParent.GetDisplay()->GetInvert50( mrParent.m_nXScreen ); + values.subwindow_mode = ClipByChildren; + + return XCreateGC( mrParent.GetXDisplay(), hDrawable, nMask | GCSubwindowMode, &values ); +} + +inline GC X11SalGraphicsImpl::GetCopyGC() +{ + if( mbXORMode ) return GetInvertGC(); + + if( !mpCopyGC ) + mpCopyGC = CreateGC( mrParent.GetDrawable() ); + + if( !mbCopyGC ) + { + mrParent.SetClipRegion( mpCopyGC ); + mbCopyGC = true; + } + return mpCopyGC; +} + +GC X11SalGraphicsImpl::GetTrackingGC() +{ + if( !mpTrackingGC ) + { + XGCValues values; + + values.graphics_exposures = False; + values.foreground = mrParent.GetColormap().GetBlackPixel() + ^ mrParent.GetColormap().GetWhitePixel(); + values.function = GXxor; + values.line_width = 1; + values.line_style = LineOnOffDash; + + mpTrackingGC = XCreateGC( mrParent.GetXDisplay(), mrParent.GetDrawable(), + GCGraphicsExposures | GCForeground | GCFunction + | GCLineWidth | GCLineStyle, + &values ); + const char dash_list[2] = {2, 2}; + XSetDashes( mrParent.GetXDisplay(), mpTrackingGC, 0, dash_list, 2 ); + } + + if( !mbTrackingGC ) + { + mrParent.SetClipRegion( mpTrackingGC ); + mbTrackingGC = true; + } + + return mpTrackingGC; +} + +GC X11SalGraphicsImpl::GetInvertGC() +{ + if( !mpInvertGC ) + mpInvertGC = CreateGC( mrParent.GetDrawable(), + GCGraphicsExposures + | GCForeground + | GCFunction + | GCLineWidth ); + + if( !mbInvertGC ) + { + mrParent.SetClipRegion( mpInvertGC ); + mbInvertGC = true; + } + return mpInvertGC; +} + +GC X11SalGraphicsImpl::GetInvert50GC() +{ + if( !mpInvert50GC ) + { + XGCValues values; + + values.graphics_exposures = False; + values.foreground = mrParent.GetColormap().GetWhitePixel(); + values.background = mrParent.GetColormap().GetBlackPixel(); + values.function = GXinvert; + values.line_width = 1; + values.line_style = LineSolid; + unsigned long const nValueMask = + GCGraphicsExposures + | GCForeground + | GCBackground + | GCFunction + | GCLineWidth + | GCLineStyle + | GCFillStyle + | GCStipple; + + values.fill_style = FillStippled; + values.stipple = mrParent.GetDisplay()->GetInvert50( mrParent.m_nXScreen ); + + mpInvert50GC = XCreateGC( mrParent.GetXDisplay(), mrParent.GetDrawable(), + nValueMask, + &values ); + } + + if( !mbInvert50GC ) + { + mrParent.SetClipRegion( mpInvert50GC ); + mbInvert50GC = true; + } + return mpInvert50GC; +} + +inline GC X11SalGraphicsImpl::GetStippleGC() +{ + if( !mpStippleGC ) + mpStippleGC = CreateGC( mrParent.GetDrawable(), + GCGraphicsExposures + | GCFillStyle + | GCLineWidth ); + + if( !mbStippleGC ) + { + XSetFunction( mrParent.GetXDisplay(), mpStippleGC, mbXORMode ? GXxor : GXcopy ); + mrParent.SetClipRegion( mpStippleGC ); + mbStippleGC = true; + } + + return mpStippleGC; +} + +GC X11SalGraphicsImpl::SelectBrush() +{ + Display *pDisplay = mrParent.GetXDisplay(); + + SAL_WARN_IF( mnBrushColor == SALCOLOR_NONE, "vcl", "Brush Transparent" ); + + if( !mpBrushGC ) + { + XGCValues values; + values.subwindow_mode = ClipByChildren; + values.fill_rule = EvenOddRule; // Pict import/ Gradient + values.graphics_exposures = False; + + mpBrushGC = XCreateGC( pDisplay, mrParent.GetDrawable(), + GCSubwindowMode | GCFillRule | GCGraphicsExposures, + &values ); + } + + if( !mbBrushGC ) + { + if( !mbDitherBrush ) + { + XSetFillStyle ( pDisplay, mpBrushGC, FillSolid ); + XSetForeground( pDisplay, mpBrushGC, mnBrushPixel ); + } + else + { + XSetFillStyle ( pDisplay, mpBrushGC, FillTiled ); + XSetTile ( pDisplay, mpBrushGC, mrParent.hBrush_ ); + } + XSetFunction ( pDisplay, mpBrushGC, mbXORMode ? GXxor : GXcopy ); + mrParent.SetClipRegion( mpBrushGC ); + + mbBrushGC = true; + } + + return mpBrushGC; +} + +GC X11SalGraphicsImpl::SelectPen() +{ + Display *pDisplay = mrParent.GetXDisplay(); + + if( !mpPenGC ) + { + XGCValues values; + values.subwindow_mode = ClipByChildren; + values.fill_rule = EvenOddRule; // Pict import/ Gradient + values.graphics_exposures = False; + + mpPenGC = XCreateGC( pDisplay, mrParent.GetDrawable(), + GCSubwindowMode | GCFillRule | GCGraphicsExposures, + &values ); + } + + if( !mbPenGC ) + { + if( mnPenColor != SALCOLOR_NONE ) + XSetForeground( pDisplay, mpPenGC, mnPenPixel ); + XSetFunction ( pDisplay, mpPenGC, mbXORMode ? GXxor : GXcopy ); + mrParent.SetClipRegion( mpPenGC ); + mbPenGC = true; + } + + return mpPenGC; +} + +void X11SalGraphicsImpl::DrawLines(sal_uInt32 nPoints, + const SalPolyLine &rPoints, + GC pGC, + bool bClose) +{ + // calculate how many lines XWindow can draw in one go + sal_uLong nMaxLines = (mrParent.GetDisplay()->GetMaxRequestSize() - sizeof(xPolyPointReq)) + / sizeof(xPoint); + if( nMaxLines > nPoints ) nMaxLines = nPoints; + + // print all lines that XWindows can draw + sal_uLong n; + for( n = 0; nPoints - n > nMaxLines; n += nMaxLines - 1 ) + XDrawLines( mrParent.GetXDisplay(), + mrParent.GetDrawable(), + pGC, + const_cast(&rPoints[n]), + nMaxLines, + CoordModeOrigin ); + + if( n < nPoints ) + XDrawLines( mrParent.GetXDisplay(), + mrParent.GetDrawable(), + pGC, + const_cast(&rPoints[n]), + nPoints - n, + CoordModeOrigin ); + if( bClose ) + { + if( rPoints[nPoints-1].x != rPoints[0].x || rPoints[nPoints-1].y != rPoints[0].y ) + drawLine( rPoints[nPoints-1].x, rPoints[nPoints-1].y, rPoints[0].x, rPoints[0].y ); + } +} + +void X11SalGraphicsImpl::copyBits( const SalTwoRect& rPosAry, + SalGraphics *pSSrcGraphics ) +{ + X11SalGraphics* pSrcGraphics = pSSrcGraphics + ? static_cast(pSSrcGraphics) + : &mrParent; + + if( rPosAry.mnSrcWidth <= 0 + || rPosAry.mnSrcHeight <= 0 + || rPosAry.mnDestWidth <= 0 + || rPosAry.mnDestHeight <= 0 ) + { + return; + } + + int n; + if( pSrcGraphics == &mrParent ) + { + n = 2; + } + else if( pSrcGraphics->bWindow_ ) + { + // window or compatible virtual device + if( pSrcGraphics->GetDisplay() == mrParent.GetDisplay() && + pSrcGraphics->m_nXScreen == mrParent.m_nXScreen && + pSrcGraphics->GetVisual().GetDepth() == mrParent.GetVisual().GetDepth() + ) + n = 2; // same Display + else + n = 1; // printer or other display + } + else if( pSrcGraphics->bVirDev_ ) + { + n = 1; // window or compatible virtual device + } + else + n = 0; + + if( n == 2 + && rPosAry.mnSrcWidth == rPosAry.mnDestWidth + && rPosAry.mnSrcHeight == rPosAry.mnDestHeight + ) + { + // #i60699# Need to generate graphics exposures (to repaint + // obscured areas beneath overlapping windows), src and dest + // are the same window. + const bool bNeedGraphicsExposures( pSrcGraphics == &mrParent && + !mrParent.bVirDev_ && + pSrcGraphics->bWindow_ ); + + GC pCopyGC = GetCopyGC(); + + if( bNeedGraphicsExposures ) + XSetGraphicsExposures( mrParent.GetXDisplay(), + pCopyGC, + True ); + + XCopyArea( mrParent.GetXDisplay(), + pSrcGraphics->GetDrawable(), // source + mrParent.GetDrawable(), // destination + pCopyGC, // destination clipping + rPosAry.mnSrcX, rPosAry.mnSrcY, + rPosAry.mnSrcWidth, rPosAry.mnSrcHeight, + rPosAry.mnDestX, rPosAry.mnDestY ); + + if( bNeedGraphicsExposures ) + { + mrParent.YieldGraphicsExpose(); + + if( pCopyGC ) + XSetGraphicsExposures( mrParent.GetXDisplay(), + pCopyGC, + False ); + } + } + else if( n ) + { + // #i60699# No chance to handle graphics exposures - we copy + // to a temp bitmap first, into which no repaints are + // technically possible. + std::shared_ptr xDDB(pSrcGraphics->getBitmap( rPosAry.mnSrcX, + rPosAry.mnSrcY, + rPosAry.mnSrcWidth, + rPosAry.mnSrcHeight )); + + if( !xDDB ) + { + SAL_WARN( "vcl", "SalGraphics::CopyBits !pSrcGraphics->GetBitmap()" ); + return; + } + + SalTwoRect aPosAry( rPosAry ); + + aPosAry.mnSrcX = 0; + aPosAry.mnSrcY = 0; + drawBitmap( aPosAry, *xDDB ); + } + else { + SAL_WARN( "vcl", "X11SalGraphicsImpl::CopyBits from Printer not yet implemented" ); + } +} + +void X11SalGraphicsImpl::copyArea ( tools::Long nDestX, tools::Long nDestY, + tools::Long nSrcX, tools::Long nSrcY, + tools::Long nSrcWidth, tools::Long nSrcHeight, + bool /*bWindowInvalidate*/) +{ + SalTwoRect aPosAry(nSrcX, nSrcY, nSrcWidth, nSrcHeight, nDestX, nDestY, nSrcWidth, nSrcHeight); + copyBits(aPosAry, nullptr); +} + +void X11SalGraphicsImpl::drawBitmap( const SalTwoRect& rPosAry, const SalBitmap& rSalBitmap ) +{ + const SalDisplay* pSalDisp = mrParent.GetDisplay(); + Display* pXDisp = pSalDisp->GetDisplay(); + const Drawable aDrawable( mrParent.GetDrawable() ); + const SalColormap& rColMap = pSalDisp->GetColormap( mrParent.m_nXScreen ); + const tools::Long nDepth = mrParent.GetDisplay()->GetVisual( mrParent.m_nXScreen ).GetDepth(); + GC aGC( GetCopyGC() ); + XGCValues aOldVal, aNewVal; + int nValues = GCForeground | GCBackground; + + if( rSalBitmap.GetBitCount() == 1 ) + { + // set foreground/background values for 1Bit bitmaps + XGetGCValues( pXDisp, aGC, nValues, &aOldVal ); + setForeBack(aNewVal, rColMap, rSalBitmap); + XChangeGC( pXDisp, aGC, nValues, &aNewVal ); + } + + static_cast(rSalBitmap).ImplDraw( aDrawable, mrParent.m_nXScreen, nDepth, rPosAry, aGC ); + + if( rSalBitmap.GetBitCount() == 1 ) + XChangeGC( pXDisp, aGC, nValues, &aOldVal ); + XFlush( pXDisp ); +} + +void X11SalGraphicsImpl::drawBitmap( const SalTwoRect& rPosAry, + const SalBitmap& rSrcBitmap, + const SalBitmap& rMaskBitmap ) +{ + // decide if alpha masking or transparency masking is needed + BitmapBuffer* pAlphaBuffer = const_cast(rMaskBitmap).AcquireBuffer( BitmapAccessMode::Read ); + if( pAlphaBuffer != nullptr ) + { + ScanlineFormat nMaskFormat = pAlphaBuffer->mnFormat; + const_cast(rMaskBitmap).ReleaseBuffer( pAlphaBuffer, BitmapAccessMode::Read ); + if( nMaskFormat == ScanlineFormat::N8BitPal ) + drawAlphaBitmap( rPosAry, rSrcBitmap, rMaskBitmap ); + } + + drawMaskedBitmap( rPosAry, rSrcBitmap, rMaskBitmap ); +} + +void X11SalGraphicsImpl::drawMaskedBitmap( const SalTwoRect& rPosAry, + const SalBitmap& rSalBitmap, + const SalBitmap& rTransBitmap ) +{ + const SalDisplay* pSalDisp = mrParent.GetDisplay(); + Display* pXDisp = pSalDisp->GetDisplay(); + Drawable aDrawable( mrParent.GetDrawable() ); + + // figure work mode depth. If this is a VDev Drawable, use its + // bitdepth to create pixmaps for, otherwise, XCopyArea will + // refuse to work. + const sal_uInt16 nDepth( mrParent.m_pVDev ? + static_cast< X11SalVirtualDevice* >(mrParent.m_pVDev)->GetDepth() : + pSalDisp->GetVisual( mrParent.m_nXScreen ).GetDepth() ); + Pixmap aFG( limitXCreatePixmap( pXDisp, aDrawable, rPosAry.mnDestWidth, + rPosAry.mnDestHeight, nDepth ) ); + Pixmap aBG( limitXCreatePixmap( pXDisp, aDrawable, rPosAry.mnDestWidth, + rPosAry.mnDestHeight, nDepth ) ); + + if( aFG && aBG ) + { + GC aTmpGC; + XGCValues aValues; + setForeBack(aValues, pSalDisp->GetColormap(mrParent.m_nXScreen), rSalBitmap); + const int nValues = GCFunction | GCForeground | GCBackground; + SalTwoRect aTmpRect( rPosAry ); aTmpRect.mnDestX = aTmpRect.mnDestY = 0; + + // draw paint bitmap in pixmap #1 + aValues.function = GXcopy; + aTmpGC = XCreateGC( pXDisp, aFG, nValues, &aValues ); + static_cast(rSalBitmap).ImplDraw( aFG, mrParent.m_nXScreen, nDepth, aTmpRect, aTmpGC ); + DBG_TESTTRANS( aFG ); + + // draw background in pixmap #2 + XCopyArea( pXDisp, aDrawable, aBG, aTmpGC, + rPosAry.mnDestX, rPosAry.mnDestY, + rPosAry.mnDestWidth, rPosAry.mnDestHeight, + 0, 0 ); + + DBG_TESTTRANS( aBG ); + + // mask out paint bitmap in pixmap #1 (transparent areas 0) + aValues.function = GXand; + aValues.foreground = 0x00000000; + aValues.background = 0xffffffff; + XChangeGC( pXDisp, aTmpGC, nValues, &aValues ); + static_cast(rTransBitmap).ImplDraw( aFG, mrParent.m_nXScreen, 1, aTmpRect, aTmpGC ); + + DBG_TESTTRANS( aFG ); + + // #105055# For XOR mode, keep background behind bitmap intact + if( !mbXORMode ) + { + // mask out background in pixmap #2 (nontransparent areas 0) + aValues.function = GXand; + aValues.foreground = 0xffffffff; + aValues.background = 0x00000000; + XChangeGC( pXDisp, aTmpGC, nValues, &aValues ); + static_cast(rTransBitmap).ImplDraw( aBG, mrParent.m_nXScreen, 1, aTmpRect, aTmpGC ); + + DBG_TESTTRANS( aBG ); + } + + // merge pixmap #1 and pixmap #2 in pixmap #2 + aValues.function = GXxor; + aValues.foreground = 0xffffffff; + aValues.background = 0x00000000; + XChangeGC( pXDisp, aTmpGC, nValues, &aValues ); + XCopyArea( pXDisp, aFG, aBG, aTmpGC, + 0, 0, + rPosAry.mnDestWidth, rPosAry.mnDestHeight, + 0, 0 ); + DBG_TESTTRANS( aBG ); + + // #105055# Disable XOR temporarily + bool bOldXORMode( mbXORMode ); + mbXORMode = false; + + // copy pixmap #2 (result) to background + XCopyArea( pXDisp, aBG, aDrawable, GetCopyGC(), + 0, 0, + rPosAry.mnDestWidth, rPosAry.mnDestHeight, + rPosAry.mnDestX, rPosAry.mnDestY ); + + DBG_TESTTRANS( aBG ); + + mbXORMode = bOldXORMode; + + XFreeGC( pXDisp, aTmpGC ); + XFlush( pXDisp ); + } + else + drawBitmap( rPosAry, rSalBitmap ); + + if( aFG ) + XFreePixmap( pXDisp, aFG ); + + if( aBG ) + XFreePixmap( pXDisp, aBG ); +} + +bool X11SalGraphicsImpl::blendBitmap( const SalTwoRect&, + const SalBitmap& ) +{ + return false; +} + +bool X11SalGraphicsImpl::blendAlphaBitmap( const SalTwoRect&, + const SalBitmap&, const SalBitmap&, const SalBitmap& ) +{ + return false; +} + +bool X11SalGraphicsImpl::drawAlphaBitmap( const SalTwoRect& rTR, + const SalBitmap& rSrcBitmap, const SalBitmap& rAlphaBmp ) +{ + // non 8-bit alpha not implemented yet + if( rAlphaBmp.GetBitCount() != 8 ) + return false; + // #i75531# the workaround below can go when + // X11SalGraphics::drawAlphaBitmap()'s render acceleration + // can handle the bitmap depth mismatch directly + if( rSrcBitmap.GetBitCount() < rAlphaBmp.GetBitCount() ) + return false; + + // horizontal mirroring not implemented yet + if( rTR.mnDestWidth < 0 ) + return false; + + // stretched conversion is not implemented yet + if( rTR.mnDestWidth != rTR.mnSrcWidth ) + return false; + if( rTR.mnDestHeight!= rTR.mnSrcHeight ) + return false; + + // create destination picture + Picture aDstPic = GetXRenderPicture(); + if( !aDstPic ) + return false; + + const SalDisplay* pSalDisp = mrParent.GetDisplay(); + const SalVisual& rSalVis = pSalDisp->GetVisual( mrParent.m_nXScreen ); + Display* pXDisplay = pSalDisp->GetDisplay(); + + // create source Picture + int nDepth = mrParent.m_pVDev ? static_cast< X11SalVirtualDevice* >(mrParent.m_pVDev)->GetDepth() : rSalVis.GetDepth(); + const X11SalBitmap& rSrcX11Bmp = static_cast( rSrcBitmap ); + ImplSalDDB* pSrcDDB = rSrcX11Bmp.ImplGetDDB( mrParent.GetDrawable(), mrParent.m_nXScreen, nDepth, rTR ); + if( !pSrcDDB ) + return false; + + //#i75249# workaround for ImplGetDDB() giving us back a different depth than + // we requested. E.g. mask pixmaps are always compatible with the drawable + // TODO: find an appropriate picture format for these cases + // then remove the workaround below and the one for #i75531# + if( nDepth != pSrcDDB->ImplGetDepth() ) + return false; + + Pixmap aSrcPM = pSrcDDB->ImplGetPixmap(); + if( !aSrcPM ) + return false; + + // create source picture + // TODO: use scoped picture + Visual* pSrcXVisual = rSalVis.GetVisual(); + XRenderPeer& rPeer = XRenderPeer::GetInstance(); + XRenderPictFormat* pSrcVisFmt = rPeer.FindVisualFormat( pSrcXVisual ); + if( !pSrcVisFmt ) + return false; + Picture aSrcPic = rPeer.CreatePicture( aSrcPM, pSrcVisFmt, 0, nullptr ); + if( !aSrcPic ) + return false; + + // create alpha Picture + + // TODO: use SalX11Bitmap functionality and caching for the Alpha Pixmap + // problem is that they don't provide an 8bit Pixmap on a non-8bit display + BitmapBuffer* pAlphaBuffer = const_cast(rAlphaBmp).AcquireBuffer( BitmapAccessMode::Read ); + + // an XImage needs its data top_down + // TODO: avoid wrongly oriented images in upper layers! + const int nImageSize = pAlphaBuffer->mnHeight * pAlphaBuffer->mnScanlineSize; + const char* pSrcBits = reinterpret_cast(pAlphaBuffer->mpBits); + char* pAlphaBits = new char[ nImageSize ]; + if( pAlphaBuffer->mnFormat & ScanlineFormat::TopDown ) + memcpy( pAlphaBits, pSrcBits, nImageSize ); + else + { + char* pDstBits = pAlphaBits + nImageSize; + const int nLineSize = pAlphaBuffer->mnScanlineSize; + for(; (pDstBits -= nLineSize) >= pAlphaBits; pSrcBits += nLineSize ) + memcpy( pDstBits, pSrcBits, nLineSize ); + } + + // the alpha values need to be inverted for XRender + // TODO: make upper layers use standard alpha + tools::Long* pLDst = reinterpret_cast(pAlphaBits); + for( int i = nImageSize/sizeof(long); --i >= 0; ++pLDst ) + *pLDst = ~*pLDst; + + char* pCDst = reinterpret_cast(pLDst); + for( int i = nImageSize & (sizeof(long)-1); --i >= 0; ++pCDst ) + *pCDst = ~*pCDst; + + const XRenderPictFormat* pAlphaFormat = rPeer.GetStandardFormatA8(); + XImage* pAlphaImg = XCreateImage( pXDisplay, pSrcXVisual, 8, ZPixmap, 0, + pAlphaBits, pAlphaBuffer->mnWidth, pAlphaBuffer->mnHeight, + pAlphaFormat->depth, pAlphaBuffer->mnScanlineSize ); + + Pixmap aAlphaPM = limitXCreatePixmap( pXDisplay, mrParent.GetDrawable(), + rTR.mnDestWidth, rTR.mnDestHeight, 8 ); + + XGCValues aAlphaGCV; + aAlphaGCV.function = GXcopy; + GC aAlphaGC = XCreateGC( pXDisplay, aAlphaPM, GCFunction, &aAlphaGCV ); + XPutImage( pXDisplay, aAlphaPM, aAlphaGC, pAlphaImg, + rTR.mnSrcX, rTR.mnSrcY, 0, 0, rTR.mnDestWidth, rTR.mnDestHeight ); + XFreeGC( pXDisplay, aAlphaGC ); + XFree( pAlphaImg ); + if( pAlphaBits != reinterpret_cast(pAlphaBuffer->mpBits) ) + delete[] pAlphaBits; + + const_cast(rAlphaBmp).ReleaseBuffer( pAlphaBuffer, BitmapAccessMode::Read ); + + XRenderPictureAttributes aAttr; + aAttr.repeat = int(true); + Picture aAlphaPic = rPeer.CreatePicture( aAlphaPM, pAlphaFormat, CPRepeat, &aAttr ); + if( !aAlphaPic ) + return false; + + // set clipping + if( mrParent.mpClipRegion && !XEmptyRegion( mrParent.mpClipRegion ) ) + rPeer.SetPictureClipRegion( aDstPic, mrParent.mpClipRegion ); + + // paint source * mask over destination picture + rPeer.CompositePicture( PictOpOver, aSrcPic, aAlphaPic, aDstPic, + rTR.mnSrcX, rTR.mnSrcY, + rTR.mnDestX, rTR.mnDestY, rTR.mnDestWidth, rTR.mnDestHeight ); + + rPeer.FreePicture( aAlphaPic ); + XFreePixmap(pXDisplay, aAlphaPM); + rPeer.FreePicture( aSrcPic ); + return true; +} + +bool X11SalGraphicsImpl::drawTransformedBitmap( + const basegfx::B2DPoint&, + const basegfx::B2DPoint&, + const basegfx::B2DPoint&, + const SalBitmap&, + const SalBitmap*, + double) +{ + // here direct support for transformed bitmaps can be implemented + return false; +} + +bool X11SalGraphicsImpl::hasFastDrawTransformedBitmap() const +{ + return false; +} + +bool X11SalGraphicsImpl::drawAlphaRect( tools::Long nX, tools::Long nY, tools::Long nWidth, + tools::Long nHeight, sal_uInt8 nTransparency ) +{ + if( ! mrParent.m_pFrame && ! mrParent.m_pVDev ) + return false; + + if( mbPenGC || !mbBrushGC || mbXORMode ) + return false; // can only perform solid fills without XOR. + + if( mrParent.m_pVDev && static_cast< X11SalVirtualDevice* >(mrParent.m_pVDev)->GetDepth() < 8 ) + return false; + + Picture aDstPic = GetXRenderPicture(); + if( !aDstPic ) + return false; + + const double fTransparency = (100 - nTransparency) * (1.0/100); + const XRenderColor aRenderColor = GetXRenderColor( mnBrushColor , fTransparency); + + XRenderPeer& rPeer = XRenderPeer::GetInstance(); + rPeer.FillRectangle( PictOpOver, + aDstPic, + &aRenderColor, + nX, nY, + nWidth, nHeight ); + + return true; +} + +void X11SalGraphicsImpl::drawMask( const SalTwoRect& rPosAry, + const SalBitmap &rSalBitmap, + Color nMaskColor ) +{ + const SalDisplay* pSalDisp = mrParent.GetDisplay(); + Display* pXDisp = pSalDisp->GetDisplay(); + Drawable aDrawable( mrParent.GetDrawable() ); + Pixmap aStipple( limitXCreatePixmap( pXDisp, aDrawable, + rPosAry.mnDestWidth, + rPosAry.mnDestHeight, 1 ) ); + + if( aStipple ) + { + SalTwoRect aTwoRect( rPosAry ); aTwoRect.mnDestX = aTwoRect.mnDestY = 0; + GC aTmpGC; + XGCValues aValues; + + // create a stipple bitmap first (set bits are changed to unset bits and vice versa) + aValues.function = GXcopyInverted; + aValues.foreground = 1; + aValues.background = 0; + aTmpGC = XCreateGC( pXDisp, aStipple, GCFunction | GCForeground | GCBackground, &aValues ); + static_cast(rSalBitmap).ImplDraw( aStipple, mrParent.m_nXScreen, 1, aTwoRect, aTmpGC ); + + XFreeGC( pXDisp, aTmpGC ); + + // Set stipple and draw rectangle + GC aStippleGC( GetStippleGC() ); + int nX = rPosAry.mnDestX, nY = rPosAry.mnDestY; + + XSetStipple( pXDisp, aStippleGC, aStipple ); + XSetTSOrigin( pXDisp, aStippleGC, nX, nY ); + XSetForeground( pXDisp, aStippleGC, mrParent.GetPixel( nMaskColor ) ); + XFillRectangle( pXDisp, aDrawable, aStippleGC, + nX, nY, + rPosAry.mnDestWidth, rPosAry.mnDestHeight ); + XFreePixmap( pXDisp, aStipple ); + XFlush( pXDisp ); + } + else + drawBitmap( rPosAry, rSalBitmap ); +} + +void X11SalGraphicsImpl::ResetClipRegion() +{ + if( !mrParent.mpClipRegion ) + return; + + mbPenGC = false; + mbBrushGC = false; + mbCopyGC = false; + mbInvertGC = false; + mbInvert50GC = false; + mbStippleGC = false; + mbTrackingGC = false; + + XDestroyRegion( mrParent.mpClipRegion ); + mrParent.mpClipRegion = nullptr; +} + +bool X11SalGraphicsImpl::setClipRegion( const vcl::Region& i_rClip ) +{ + if( mrParent.mpClipRegion ) + XDestroyRegion( mrParent.mpClipRegion ); + mrParent.mpClipRegion = XCreateRegion(); + + RectangleVector aRectangles; + i_rClip.GetRegionRectangles(aRectangles); + + for (auto const& rectangle : aRectangles) + { + const tools::Long nW(rectangle.GetWidth()); + + if(nW) + { + const tools::Long nH(rectangle.GetHeight()); + + if(nH) + { + XRectangle aRect; + + aRect.x = static_cast(rectangle.Left()); + aRect.y = static_cast(rectangle.Top()); + aRect.width = static_cast(nW); + aRect.height = static_cast(nH); + XUnionRectWithRegion(&aRect, mrParent.mpClipRegion, mrParent.mpClipRegion); + } + } + } + + //ImplRegionInfo aInfo; + //long nX, nY, nW, nH; + //bool bRegionRect = i_rClip.ImplGetFirstRect(aInfo, nX, nY, nW, nH ); + //while( bRegionRect ) + //{ + // if ( nW && nH ) + // { + // XRectangle aRect; + // aRect.x = (short)nX; + // aRect.y = (short)nY; + // aRect.width = (unsigned short)nW; + // aRect.height = (unsigned short)nH; + + // XUnionRectWithRegion( &aRect, mrParent.mpClipRegion, mrParent.mpClipRegion ); + // } + // bRegionRect = i_rClip.ImplGetNextRect( aInfo, nX, nY, nW, nH ); + //} + + // done, invalidate GCs + mbPenGC = false; + mbBrushGC = false; + mbCopyGC = false; + mbInvertGC = false; + mbInvert50GC = false; + mbStippleGC = false; + mbTrackingGC = false; + + if( XEmptyRegion( mrParent.mpClipRegion ) ) + { + XDestroyRegion( mrParent.mpClipRegion ); + mrParent.mpClipRegion= nullptr; + } + return true; +} + +void X11SalGraphicsImpl::SetLineColor() +{ + if( mnPenColor != SALCOLOR_NONE ) + { + mnPenColor = SALCOLOR_NONE; + mbPenGC = false; + } +} + +void X11SalGraphicsImpl::SetLineColor( Color nColor ) +{ + if( mnPenColor != nColor ) + { + mnPenColor = nColor; + mnPenPixel = mrParent.GetPixel( nColor ); + mbPenGC = false; + } +} + +void X11SalGraphicsImpl::SetFillColor() +{ + if( mnBrushColor != SALCOLOR_NONE ) + { + mbDitherBrush = false; + mnBrushColor = SALCOLOR_NONE; + mbBrushGC = false; + } +} + +void X11SalGraphicsImpl::SetFillColor( Color nColor ) +{ + if( mnBrushColor == nColor ) + return; + + mbDitherBrush = false; + mnBrushColor = nColor; + mnBrushPixel = mrParent.GetPixel( nColor ); + if( TrueColor != mrParent.GetColormap().GetVisual().GetClass() + && mrParent.GetColormap().GetColor( mnBrushPixel ) != mnBrushColor + && nColor != Color( 0x00, 0x00, 0x00 ) // black + && nColor != Color( 0x00, 0x00, 0x80 ) // blue + && nColor != Color( 0x00, 0x80, 0x00 ) // green + && nColor != Color( 0x00, 0x80, 0x80 ) // cyan + && nColor != Color( 0x80, 0x00, 0x00 ) // red + && nColor != Color( 0x80, 0x00, 0x80 ) // magenta + && nColor != Color( 0x80, 0x80, 0x00 ) // brown + && nColor != Color( 0x80, 0x80, 0x80 ) // gray + && nColor != Color( 0xC0, 0xC0, 0xC0 ) // light gray + && nColor != Color( 0x00, 0x00, 0xFF ) // light blue + && nColor != Color( 0x00, 0xFF, 0x00 ) // light green + && nColor != Color( 0x00, 0xFF, 0xFF ) // light cyan + && nColor != Color( 0xFF, 0x00, 0x00 ) // light red + && nColor != Color( 0xFF, 0x00, 0xFF ) // light magenta + && nColor != Color( 0xFF, 0xFF, 0x00 ) // light brown + && nColor != Color( 0xFF, 0xFF, 0xFF ) ) + mbDitherBrush = mrParent.GetDitherPixmap(nColor); + mbBrushGC = false; +} + +void X11SalGraphicsImpl::SetROPLineColor( SalROPColor nROPColor ) +{ + switch( nROPColor ) + { + case SalROPColor::N0 : // 0 + mnPenPixel = Pixel(0); + break; + case SalROPColor::N1 : // 1 + mnPenPixel = static_cast(1 << mrParent.GetVisual().GetDepth()) - 1; + break; + case SalROPColor::Invert : // 2 + mnPenPixel = static_cast(1 << mrParent.GetVisual().GetDepth()) - 1; + break; + } + mnPenColor = mrParent.GetColormap().GetColor( mnPenPixel ); + mbPenGC = false; +} + +void X11SalGraphicsImpl::SetROPFillColor( SalROPColor nROPColor ) +{ + switch( nROPColor ) + { + case SalROPColor::N0 : // 0 + mnBrushPixel = Pixel(0); + break; + case SalROPColor::N1 : // 1 + mnBrushPixel = static_cast(1 << mrParent.GetVisual().GetDepth()) - 1; + break; + case SalROPColor::Invert : // 2 + mnBrushPixel = static_cast(1 << mrParent.GetVisual().GetDepth()) - 1; + break; + } + mbDitherBrush = false; + mnBrushColor = mrParent.GetColormap().GetColor( mnBrushPixel ); + mbBrushGC = false; +} + +void X11SalGraphicsImpl::SetXORMode( bool bSet, bool ) +{ + if (mbXORMode != bSet) + { + mbXORMode = bSet; + mbPenGC = false; + mbBrushGC = false; + mbCopyGC = false; + mbInvertGC = false; + mbInvert50GC = false; + mbStippleGC = false; + mbTrackingGC = false; + } +} + +void X11SalGraphicsImpl::drawPixel( tools::Long nX, tools::Long nY ) +{ + if( mnPenColor != SALCOLOR_NONE ) + XDrawPoint( mrParent.GetXDisplay(), mrParent.GetDrawable(), SelectPen(), nX, nY ); +} + +void X11SalGraphicsImpl::drawPixel( tools::Long nX, tools::Long nY, Color nColor ) +{ + if( nColor == SALCOLOR_NONE ) + return; + + Display *pDisplay = mrParent.GetXDisplay(); + + if( (mnPenColor == SALCOLOR_NONE) && !mbPenGC ) + { + SetLineColor( nColor ); + XDrawPoint( pDisplay, mrParent.GetDrawable(), SelectPen(), nX, nY ); + mnPenColor = SALCOLOR_NONE; + mbPenGC = False; + } + else + { + GC pGC = SelectPen(); + + if( nColor != mnPenColor ) + XSetForeground( pDisplay, pGC, mrParent.GetPixel( nColor ) ); + + XDrawPoint( pDisplay, mrParent.GetDrawable(), pGC, nX, nY ); + + if( nColor != mnPenColor ) + XSetForeground( pDisplay, pGC, mnPenPixel ); + } +} + +void X11SalGraphicsImpl::drawLine( tools::Long nX1, tools::Long nY1, tools::Long nX2, tools::Long nY2 ) +{ + if( mnPenColor != SALCOLOR_NONE ) + { + XDrawLine( mrParent.GetXDisplay(), mrParent.GetDrawable(),SelectPen(), + nX1, nY1, nX2, nY2 ); + } +} + +void X11SalGraphicsImpl::drawRect( tools::Long nX, tools::Long nY, tools::Long nDX, tools::Long nDY ) +{ + if( mnBrushColor != SALCOLOR_NONE ) + { + XFillRectangle( mrParent.GetXDisplay(), + mrParent.GetDrawable(), + SelectBrush(), + nX, nY, nDX, nDY ); + } + // description DrawRect is wrong; thus -1 + if( mnPenColor != SALCOLOR_NONE ) + XDrawRectangle( mrParent.GetXDisplay(), + mrParent.GetDrawable(), + SelectPen(), + nX, nY, nDX-1, nDY-1 ); +} + +void X11SalGraphicsImpl::drawPolyLine( sal_uInt32 nPoints, const Point *pPtAry ) +{ + internalDrawPolyLine( nPoints, pPtAry, false ); +} + +void X11SalGraphicsImpl::internalDrawPolyLine( sal_uInt32 nPoints, const Point *pPtAry, bool bClose ) +{ + if( mnPenColor != SALCOLOR_NONE ) + { + SalPolyLine Points( nPoints, pPtAry ); + + DrawLines( nPoints, Points, SelectPen(), bClose ); + } +} + +void X11SalGraphicsImpl::drawPolygon( sal_uInt32 nPoints, const Point* pPtAry ) +{ + if( nPoints == 0 ) + return; + + if( nPoints < 3 ) + { + if( !mbXORMode ) + { + if( 1 == nPoints ) + drawPixel( pPtAry[0].getX(), pPtAry[0].getY() ); + else + drawLine( pPtAry[0].getX(), pPtAry[0].getY(), + pPtAry[1].getX(), pPtAry[1].getY() ); + } + return; + } + + SalPolyLine Points( nPoints, pPtAry ); + + nPoints++; + + /* WORKAROUND: some Xservers (Xorg, VIA chipset in this case) + * do not draw the visible part of a polygon + * if it overlaps to the left of screen 0,y. + * This happens to be the case in the gradient drawn in the + * menubar background. workaround for the special case of + * of a rectangle overlapping to the left. + */ + if (nPoints == 5 && + Points[ 0 ].x == Points[ 1 ].x && + Points[ 1 ].y == Points[ 2 ].y && + Points[ 2 ].x == Points[ 3 ].x && + Points[ 0 ].x == Points[ 4 ].x && Points[ 0 ].y == Points[ 4 ].y + ) + { + bool bLeft = false; + bool bRight = false; + for(unsigned int i = 0; i < nPoints; i++ ) + { + if( Points[i].x < 0 ) + bLeft = true; + else + bRight= true; + } + if( bLeft && ! bRight ) + return; + if( bLeft && bRight ) + { + for( unsigned int i = 0; i < nPoints; i++ ) + if( Points[i].x < 0 ) + Points[i].x = 0; + } + } + + if( mnBrushColor != SALCOLOR_NONE ) + XFillPolygon( mrParent.GetXDisplay(), + mrParent.GetDrawable(), + SelectBrush(), + &Points[0], nPoints, + Complex, CoordModeOrigin ); + + if( mnPenColor != SALCOLOR_NONE ) + DrawLines( nPoints, Points, SelectPen(), true ); +} + +void X11SalGraphicsImpl::drawPolyPolygon( sal_uInt32 nPoly, + const sal_uInt32 *pPoints, + const Point* *pPtAry ) +{ + if( mnBrushColor != SALCOLOR_NONE ) + { + sal_uInt32 i, n; + Region pXRegA = nullptr; + + for( i = 0; i < nPoly; i++ ) { + n = pPoints[i]; + SalPolyLine Points( n, pPtAry[i] ); + if( n > 2 ) + { + Region pXRegB = XPolygonRegion( &Points[0], n+1, WindingRule ); + if( !pXRegA ) + pXRegA = pXRegB; + else + { + XXorRegion( pXRegA, pXRegB, pXRegA ); + XDestroyRegion( pXRegB ); + } + } + } + + if( pXRegA ) + { + XRectangle aXRect; + XClipBox( pXRegA, &aXRect ); + + GC pGC = SelectBrush(); + mrParent.SetClipRegion( pGC, pXRegA ); // ??? twice + XDestroyRegion( pXRegA ); + mbBrushGC = false; + + XFillRectangle( mrParent.GetXDisplay(), + mrParent.GetDrawable(), + pGC, + aXRect.x, aXRect.y, aXRect.width, aXRect.height ); + } + } + + if( mnPenColor != SALCOLOR_NONE ) + for( sal_uInt32 i = 0; i < nPoly; i++ ) + internalDrawPolyLine( pPoints[i], pPtAry[i], true ); +} + +bool X11SalGraphicsImpl::drawPolyLineBezier( sal_uInt32, const Point*, const PolyFlags* ) +{ + return false; +} + +bool X11SalGraphicsImpl::drawPolygonBezier( sal_uInt32, const Point*, const PolyFlags* ) +{ + return false; +} + +bool X11SalGraphicsImpl::drawPolyPolygonBezier( sal_uInt32, const sal_uInt32*, + const Point* const*, const PolyFlags* const* ) +{ + return false; +} + +void X11SalGraphicsImpl::invert( tools::Long nX, + tools::Long nY, + tools::Long nDX, + tools::Long nDY, + SalInvert nFlags ) +{ + GC pGC; + if( SalInvert::N50 & nFlags ) + { + pGC = GetInvert50GC(); + XFillRectangle( mrParent.GetXDisplay(), mrParent.GetDrawable(), pGC, nX, nY, nDX, nDY ); + } + else + { + if ( SalInvert::TrackFrame & nFlags ) + { + pGC = GetTrackingGC(); + XDrawRectangle( mrParent.GetXDisplay(), mrParent.GetDrawable(), pGC, nX, nY, nDX, nDY ); + } + else + { + pGC = GetInvertGC(); + XFillRectangle( mrParent.GetXDisplay(), mrParent.GetDrawable(), pGC, nX, nY, nDX, nDY ); + } + } +} + +void X11SalGraphicsImpl::invert( sal_uInt32 nPoints, + const Point* pPtAry, + SalInvert nFlags ) +{ + SalPolyLine Points ( nPoints, pPtAry ); + + GC pGC; + if( SalInvert::N50 & nFlags ) + pGC = GetInvert50GC(); + else + if ( SalInvert::TrackFrame & nFlags ) + pGC = GetTrackingGC(); + else + pGC = GetInvertGC(); + + if( SalInvert::TrackFrame & nFlags ) + DrawLines ( nPoints, Points, pGC, true ); + else + XFillPolygon( mrParent.GetXDisplay(), + mrParent.GetDrawable(), + pGC, + &Points[0], nPoints, + Complex, CoordModeOrigin ); +} + +bool X11SalGraphicsImpl::drawEPS( tools::Long,tools::Long,tools::Long,tools::Long,void*,sal_uInt32 ) +{ + return false; +} + +// draw a poly-polygon +bool X11SalGraphicsImpl::drawPolyPolygon( + const basegfx::B2DHomMatrix& rObjectToDevice, + const basegfx::B2DPolyPolygon& rPolyPolygon, + double fTransparency) +{ + // nothing to do for empty polypolygons + const int nOrigPolyCount = rPolyPolygon.count(); + if( nOrigPolyCount <= 0 ) + return true; + + // nothing to do if everything is transparent + if( (mnBrushColor == SALCOLOR_NONE) + && (mnPenColor == SALCOLOR_NONE) ) + return true; + + // cannot handle pencolor!=brushcolor yet + if( (mnPenColor != SALCOLOR_NONE) + && (mnPenColor != mnBrushColor) ) + return false; + + // TODO: remove the env-variable when no longer needed + static const char* pRenderEnv = getenv( "SAL_DISABLE_RENDER_POLY" ); + if( pRenderEnv ) + return false; + + // Fallback: Transform to DeviceCoordinates + basegfx::B2DPolyPolygon aPolyPolygon(rPolyPolygon); + aPolyPolygon.transform(rObjectToDevice); + + // snap to raster if requested + const bool bSnapToRaster = !mrParent.getAntiAlias(); + if( bSnapToRaster ) + aPolyPolygon = basegfx::utils::snapPointsOfHorizontalOrVerticalEdges( aPolyPolygon ); + + // don't bother with polygons outside of visible area + const basegfx::B2DRange aViewRange( 0, 0, GetGraphicsWidth(), GetGraphicsHeight() ); + aPolyPolygon = basegfx::utils::clipPolyPolygonOnRange( aPolyPolygon, aViewRange, true, false ); + if( !aPolyPolygon.count() ) + return true; + + // tessellate the polypolygon into trapezoids + basegfx::B2DTrapezoidVector aB2DTrapVector; + basegfx::utils::trapezoidSubdivide( aB2DTrapVector, aPolyPolygon ); + const int nTrapCount = aB2DTrapVector.size(); + if( !nTrapCount ) + return true; + const bool bDrawn = drawFilledTrapezoids( aB2DTrapVector.data(), nTrapCount, fTransparency ); + return bDrawn; +} + +tools::Long X11SalGraphicsImpl::GetGraphicsHeight() const +{ + if( mrParent.m_pFrame ) + return mrParent.m_pFrame->maGeometry.nHeight; + else if( mrParent.m_pVDev ) + return static_cast< X11SalVirtualDevice* >(mrParent.m_pVDev)->GetHeight(); + else + return 0; +} + +bool X11SalGraphicsImpl::drawFilledTrapezoids( const basegfx::B2DTrapezoid* pB2DTraps, int nTrapCount, double fTransparency ) +{ + if( nTrapCount <= 0 ) + return true; + + Picture aDstPic = GetXRenderPicture(); + // check xrender support for this drawable + if( !aDstPic ) + return false; + + // convert the B2DTrapezoids into XRender-Trapezoids + std::vector aTrapVector( nTrapCount ); + const basegfx::B2DTrapezoid* pB2DTrap = pB2DTraps; + for( int i = 0; i < nTrapCount; ++pB2DTrap, ++i ) + { + XTrapezoid& rTrap = aTrapVector[ i ] ; + + // set y-coordinates + const double fY1 = pB2DTrap->getTopY(); + rTrap.left.p1.y = rTrap.right.p1.y = rTrap.top = XDoubleToFixed( fY1 ); + const double fY2 = pB2DTrap->getBottomY(); + rTrap.left.p2.y = rTrap.right.p2.y = rTrap.bottom = XDoubleToFixed( fY2 ); + + // set x-coordinates + const double fXL1 = pB2DTrap->getTopXLeft(); + rTrap.left.p1.x = XDoubleToFixed( fXL1 ); + const double fXR1 = pB2DTrap->getTopXRight(); + rTrap.right.p1.x = XDoubleToFixed( fXR1 ); + const double fXL2 = pB2DTrap->getBottomXLeft(); + rTrap.left.p2.x = XDoubleToFixed( fXL2 ); + const double fXR2 = pB2DTrap->getBottomXRight(); + rTrap.right.p2.x = XDoubleToFixed( fXR2 ); + } + + // get xrender Picture for polygon foreground + // TODO: cache it like the target picture which uses GetXRenderPicture() + XRenderPeer& rRenderPeer = XRenderPeer::GetInstance(); + SalDisplay::RenderEntry& rEntry = mrParent.GetDisplay()->GetRenderEntries( mrParent.m_nXScreen )[ 32 ]; + if( !rEntry.m_aPicture ) + { + Display* pXDisplay = mrParent.GetXDisplay(); + + rEntry.m_aPixmap = limitXCreatePixmap( pXDisplay, mrParent.GetDrawable(), 1, 1, 32 ); + XRenderPictureAttributes aAttr; + aAttr.repeat = int(true); + + XRenderPictFormat* pXRPF = rRenderPeer.FindStandardFormat( PictStandardARGB32 ); + rEntry.m_aPicture = rRenderPeer.CreatePicture( rEntry.m_aPixmap, pXRPF, CPRepeat, &aAttr ); + } + + // set polygon foreground color and opacity + XRenderColor aRenderColor = GetXRenderColor( mnBrushColor , fTransparency ); + rRenderPeer.FillRectangle( PictOpSrc, rEntry.m_aPicture, &aRenderColor, 0, 0, 1, 1 ); + + // set clipping + // TODO: move into GetXRenderPicture? + if( mrParent.mpClipRegion && !XEmptyRegion( mrParent.mpClipRegion ) ) + rRenderPeer.SetPictureClipRegion( aDstPic, mrParent.mpClipRegion ); + + // render the trapezoids + const XRenderPictFormat* pMaskFormat = rRenderPeer.GetStandardFormatA8(); + rRenderPeer.CompositeTrapezoids( PictOpOver, + rEntry.m_aPicture, aDstPic, pMaskFormat, 0, 0, aTrapVector.data(), aTrapVector.size() ); + + return true; +} + +bool X11SalGraphicsImpl::drawFilledTriangles( + const basegfx::B2DHomMatrix& rObjectToDevice, + const basegfx::triangulator::B2DTriangleVector& rTriangles, + double fTransparency) +{ + if(rTriangles.empty()) + return true; + + Picture aDstPic = GetXRenderPicture(); + // check xrender support for this drawable + if( !aDstPic ) + { + return false; + } + + // prepare transformation for ObjectToDevice coordinate system + basegfx::B2DHomMatrix aObjectToDevice = basegfx::utils::createTranslateB2DHomMatrix(0.5, 0.5) * rObjectToDevice; + + // convert the Triangles into XRender-Triangles + std::vector aTriVector(rTriangles.size()); + sal_uInt32 nIndex(0); + + for(const auto& rCandidate : rTriangles) + { + const basegfx::B2DPoint aP1(aObjectToDevice * rCandidate.getA()); + const basegfx::B2DPoint aP2(aObjectToDevice * rCandidate.getB()); + const basegfx::B2DPoint aP3(aObjectToDevice * rCandidate.getC()); + XTriangle& rTri(aTriVector[nIndex++]); + + rTri.p1.x = XDoubleToFixed(aP1.getX()); + rTri.p1.y = XDoubleToFixed(aP1.getY()); + + rTri.p2.x = XDoubleToFixed(aP2.getX()); + rTri.p2.y = XDoubleToFixed(aP2.getY()); + + rTri.p3.x = XDoubleToFixed(aP3.getX()); + rTri.p3.y = XDoubleToFixed(aP3.getY()); + } + + // get xrender Picture for polygon foreground + // TODO: cache it like the target picture which uses GetXRenderPicture() + XRenderPeer& rRenderPeer = XRenderPeer::GetInstance(); + SalDisplay::RenderEntry& rEntry = mrParent.GetDisplay()->GetRenderEntries( mrParent.m_nXScreen )[ 32 ]; + if( !rEntry.m_aPicture ) + { + Display* pXDisplay = mrParent.GetXDisplay(); + + rEntry.m_aPixmap = limitXCreatePixmap( pXDisplay, mrParent.GetDrawable(), 1, 1, 32 ); + XRenderPictureAttributes aAttr; + aAttr.repeat = int(true); + + XRenderPictFormat* pXRPF = rRenderPeer.FindStandardFormat( PictStandardARGB32 ); + rEntry.m_aPicture = rRenderPeer.CreatePicture( rEntry.m_aPixmap, pXRPF, CPRepeat, &aAttr ); + } + + // set polygon foreground color and opacity + XRenderColor aRenderColor = GetXRenderColor( mnBrushColor , fTransparency ); + rRenderPeer.FillRectangle( PictOpSrc, rEntry.m_aPicture, &aRenderColor, 0, 0, 1, 1 ); + + // set clipping + // TODO: move into GetXRenderPicture? + if( mrParent.mpClipRegion && !XEmptyRegion( mrParent.mpClipRegion ) ) + rRenderPeer.SetPictureClipRegion( aDstPic, mrParent.mpClipRegion ); + + // render the trapezoids + const XRenderPictFormat* pMaskFormat = rRenderPeer.GetStandardFormatA8(); + rRenderPeer.CompositeTriangles( PictOpOver, + rEntry.m_aPicture, aDstPic, pMaskFormat, 0, 0, aTriVector.data(), aTriVector.size() ); + + return true; +} + +namespace { + +class SystemDependentData_Triangulation : public basegfx::SystemDependentData +{ +private: + // the triangulation itself + basegfx::triangulator::B2DTriangleVector maTriangles; + + // all other values the triangulation is based on and + // need to be compared with to check for data validity + double mfLineWidth; + basegfx::B2DLineJoin meJoin; + css::drawing::LineCap meCap; + double mfMiterMinimumAngle; + std::vector< double > maStroke; + +public: + SystemDependentData_Triangulation( + basegfx::SystemDependentDataManager& rSystemDependentDataManager, + basegfx::triangulator::B2DTriangleVector&& rTriangles, + double fLineWidth, + basegfx::B2DLineJoin eJoin, + css::drawing::LineCap eCap, + double fMiterMinimumAngle, + const std::vector< double >* pStroke); // MM01 + + // read access + const basegfx::triangulator::B2DTriangleVector& getTriangles() const { return maTriangles; } + double getLineWidth() const { return mfLineWidth; } + const basegfx::B2DLineJoin& getJoin() const { return meJoin; } + const css::drawing::LineCap& getCap() const { return meCap; } + double getMiterMinimumAngle() const { return mfMiterMinimumAngle; } + const std::vector< double >& getStroke() const { return maStroke; } + + virtual sal_Int64 estimateUsageInBytes() const override; +}; + +} + +SystemDependentData_Triangulation::SystemDependentData_Triangulation( + basegfx::SystemDependentDataManager& rSystemDependentDataManager, + basegfx::triangulator::B2DTriangleVector&& rTriangles, + double fLineWidth, + basegfx::B2DLineJoin eJoin, + css::drawing::LineCap eCap, + double fMiterMinimumAngle, + const std::vector< double >* pStroke) +: basegfx::SystemDependentData(rSystemDependentDataManager), + maTriangles(std::move(rTriangles)), + mfLineWidth(fLineWidth), + meJoin(eJoin), + meCap(eCap), + mfMiterMinimumAngle(fMiterMinimumAngle) +{ + if(nullptr != pStroke) + { + maStroke = *pStroke; + } +} + +sal_Int64 SystemDependentData_Triangulation::estimateUsageInBytes() const +{ + sal_Int64 nRetval(0); + + if(!maTriangles.empty()) + { + nRetval = maTriangles.size() * sizeof(basegfx::triangulator::B2DTriangle); + } + + return nRetval; +} + +bool X11SalGraphicsImpl::drawPolyLine( + const basegfx::B2DHomMatrix& rObjectToDevice, + const basegfx::B2DPolygon& rPolygon, + double fTransparency, + double fLineWidth, + const std::vector< double >* pStroke, // MM01 + basegfx::B2DLineJoin eLineJoin, + css::drawing::LineCap eLineCap, + double fMiterMinimumAngle, + bool bPixelSnapHairline) +{ + // short circuit if there is nothing to do + if(0 == rPolygon.count() || fTransparency < 0.0 || fTransparency >= 1.0) + { + return true; + } + + // need to check/handle LineWidth when ObjectToDevice transformation is used + const bool bObjectToDeviceIsIdentity(rObjectToDevice.isIdentity()); + basegfx::B2DHomMatrix aObjectToDeviceInv; + + // tdf#124848 calculate-back logical LineWidth for a hairline. + // This implementation does not hand over the transformation to + // the graphic sub-system, but the triangulation data is prepared + // view-independent based on the logic LineWidth, so we need to + // know it + if(fLineWidth == 0) + { + fLineWidth = 1.0; + + if(!bObjectToDeviceIsIdentity) + { + if(aObjectToDeviceInv.isIdentity()) + { + aObjectToDeviceInv = rObjectToDevice; + aObjectToDeviceInv.invert(); + } + + fLineWidth = (aObjectToDeviceInv * basegfx::B2DVector(fLineWidth, 0)).getLength(); + } + } + + // try to access buffered data + std::shared_ptr pSystemDependentData_Triangulation( + rPolygon.getSystemDependentData()); + + // MM01 need to do line dashing as fallback stuff here now + const double fDotDashLength(nullptr != pStroke ? std::accumulate(pStroke->begin(), pStroke->end(), 0.0) : 0.0); + const bool bStrokeUsed(0.0 != fDotDashLength); + assert(!bStrokeUsed || (bStrokeUsed && pStroke)); + + if(pSystemDependentData_Triangulation) + { + // MM01 - check on stroke change. Used against not used, or if oth used, + // equal or different? Triangulation geometry creation depends heavily + // on stroke, independent of being transformation independent + const bool bStrokeWasUsed(!pSystemDependentData_Triangulation->getStroke().empty()); + + if(bStrokeWasUsed != bStrokeUsed + || (bStrokeUsed && *pStroke != pSystemDependentData_Triangulation->getStroke())) + { + // data invalid, forget + pSystemDependentData_Triangulation.reset(); + } + } + + if(pSystemDependentData_Triangulation) + { + // check data validity (I) + if(pSystemDependentData_Triangulation->getJoin() != eLineJoin + || pSystemDependentData_Triangulation->getCap() != eLineCap + || pSystemDependentData_Triangulation->getMiterMinimumAngle() != fMiterMinimumAngle) + { + // data invalid, forget + pSystemDependentData_Triangulation.reset(); + } + } + + if(pSystemDependentData_Triangulation) + { + // check data validity (II) + if(pSystemDependentData_Triangulation->getLineWidth() != fLineWidth) + { + // sometimes small inconsistencies, use a percentage tolerance + const double fFactor(basegfx::fTools::equalZero(fLineWidth) + ? 0.0 + : fabs(1.0 - (pSystemDependentData_Triangulation->getLineWidth() / fLineWidth))); + // compare with 5.0% tolerance + if(basegfx::fTools::more(fFactor, 0.05)) + { + // data invalid, forget + pSystemDependentData_Triangulation.reset(); + } + } + } + + if(!pSystemDependentData_Triangulation) + { + // MM01 need to do line dashing as fallback stuff here now + basegfx::B2DPolyPolygon aPolyPolygonLine; + + if(bStrokeUsed) + { + // apply LineStyle + basegfx::utils::applyLineDashing( + rPolygon, // source + *pStroke, // pattern + &aPolyPolygonLine, // target for lines + nullptr, // target for gaps + fDotDashLength); // full length if available + } + else + { + // no line dashing, just copy + aPolyPolygonLine.append(rPolygon); + } + + // try to create data + if(bPixelSnapHairline) + { + // Do NOT transform, but keep device-independent. To + // do so, transform to device for snap, but back again after + if(!bObjectToDeviceIsIdentity) + { + aPolyPolygonLine.transform(rObjectToDevice); + } + + aPolyPolygonLine = basegfx::utils::snapPointsOfHorizontalOrVerticalEdges(aPolyPolygonLine); + + if(!bObjectToDeviceIsIdentity) + { + if(aObjectToDeviceInv.isIdentity()) + { + aObjectToDeviceInv = rObjectToDevice; + aObjectToDeviceInv.invert(); + } + + aPolyPolygonLine.transform(aObjectToDeviceInv); + } + } + + basegfx::triangulator::B2DTriangleVector aTriangles; + + // MM01 checked/verified for X11 (linux) + for(sal_uInt32 a(0); a < aPolyPolygonLine.count(); a++) + { + const basegfx::B2DPolygon aPolyLine(aPolyPolygonLine.getB2DPolygon(a)); + // MM01 upps - commit 51b5b93092d6231615de470c62494c24e54828a1 removed + // this *central* geometry-creating lines (!) probably due to aAreaPolyPoly + // *not* being used - that's true, but the work is inside of filling + // aTriangles data (!) + basegfx::utils::createAreaGeometry( + aPolyLine, + 0.5 * fLineWidth, + eLineJoin, + eLineCap, + basegfx::deg2rad(12.5), + 0.4, + fMiterMinimumAngle, + &aTriangles); // CAUTION! This is *needed* since it creates the data! + } + + if(!aTriangles.empty()) + { + // Add to buffering mechanism + // Add all values the triangulation is based off, too, to check for + // validity (see above) + pSystemDependentData_Triangulation = rPolygon.addOrReplaceSystemDependentData( + ImplGetSystemDependentDataManager(), + std::move(aTriangles), + fLineWidth, + eLineJoin, + eLineCap, + fMiterMinimumAngle, + pStroke); + } + } + + if(!pSystemDependentData_Triangulation) + { + return false; + } + + // temporarily adjust brush color to pen color + // since the line is drawn as an area-polygon + const Color aKeepBrushColor = mnBrushColor; + mnBrushColor = mnPenColor; + + // create the area-polygon for the line + const bool bDrawnOk( + drawFilledTriangles( + rObjectToDevice, + pSystemDependentData_Triangulation->getTriangles(), + fTransparency)); + + // restore the original brush GC + mnBrushColor = aKeepBrushColor; + return bDrawnOk; +} + +Color X11SalGraphicsImpl::getPixel( tools::Long nX, tools::Long nY ) +{ + if( mrParent.bWindow_ && !mrParent.bVirDev_ ) + { + XWindowAttributes aAttrib; + + XGetWindowAttributes( mrParent.GetXDisplay(), mrParent.GetDrawable(), &aAttrib ); + if( aAttrib.map_state != IsViewable ) + { + SAL_WARN( "vcl", "X11SalGraphics::GetPixel drawable not viewable" ); + return 0; + } + } + + XImage *pXImage = XGetImage( mrParent.GetXDisplay(), + mrParent.GetDrawable(), + nX, nY, + 1, 1, + AllPlanes, + ZPixmap ); + if( !pXImage ) + { + SAL_WARN( "vcl", "X11SalGraphics::GetPixel !XGetImage()" ); + return 0; + } + + XColor aXColor; + + aXColor.pixel = XGetPixel( pXImage, 0, 0 ); + XDestroyImage( pXImage ); + + return mrParent.GetColormap().GetColor( aXColor.pixel ); +} + +std::shared_ptr X11SalGraphicsImpl::getBitmap( tools::Long nX, tools::Long nY, tools::Long nDX, tools::Long nDY ) +{ + bool bFakeWindowBG = false; + + // normalize + if( nDX < 0 ) + { + nX += nDX; + nDX = -nDX; + } + if ( nDY < 0 ) + { + nY += nDY; + nDY = -nDY; + } + + if( mrParent.bWindow_ && !mrParent.bVirDev_ ) + { + XWindowAttributes aAttrib; + + XGetWindowAttributes( mrParent.GetXDisplay(), mrParent.GetDrawable(), &aAttrib ); + if( aAttrib.map_state != IsViewable ) + bFakeWindowBG = true; + else + { + tools::Long nOrgDX = nDX, nOrgDY = nDY; + + // clip to window size + if ( nX < 0 ) + { + nDX += nX; + nX = 0; + } + if ( nY < 0 ) + { + nDY += nY; + nY = 0; + } + if( nX + nDX > aAttrib.width ) + nDX = aAttrib.width - nX; + if( nY + nDY > aAttrib.height ) + nDY = aAttrib.height - nY; + + // inside ? + if( nDX <= 0 || nDY <= 0 ) + { + bFakeWindowBG = true; + nDX = nOrgDX; + nDY = nOrgDY; + } + } + } + + std::shared_ptr pSalBitmap = std::make_shared(); + sal_uInt16 nBitCount = GetBitCount(); + vcl::PixelFormat ePixelFormat = vcl::bitDepthToPixelFormat(nBitCount); + + if( &mrParent.GetDisplay()->GetColormap( mrParent.m_nXScreen ) != &mrParent.GetColormap() ) + { + ePixelFormat = vcl::PixelFormat::N1_BPP; + nBitCount = 1; + } + + if (nBitCount > 8) + ePixelFormat = vcl::PixelFormat::N24_BPP; + + if( ! bFakeWindowBG ) + pSalBitmap->ImplCreateFromDrawable( mrParent.GetDrawable(), mrParent.m_nXScreen, nBitCount, nX, nY, nDX, nDY ); + else + pSalBitmap->Create( Size( nDX, nDY ), ePixelFormat, BitmapPalette( nBitCount > 8 ? nBitCount : 0 ) ); + + return pSalBitmap; +} + +sal_uInt16 X11SalGraphicsImpl::GetBitCount() const +{ + return mrParent.GetVisual().GetDepth(); +} + +tools::Long X11SalGraphicsImpl::GetGraphicsWidth() const +{ + if( mrParent.m_pFrame ) + return mrParent.m_pFrame->maGeometry.nWidth; + else if( mrParent.m_pVDev ) + return static_cast< X11SalVirtualDevice* >(mrParent.m_pVDev)->GetWidth(); + else + return 0; +} + +bool X11SalGraphicsImpl::drawGradient(const tools::PolyPolygon& /*rPolygon*/, const Gradient& /*rGradient*/) +{ + return false; +} + +bool X11SalGraphicsImpl::implDrawGradient(basegfx::B2DPolyPolygon const & /*rPolyPolygon*/, SalGradient const & /*rGradient*/) +{ + return false; +} + +bool X11SalGraphicsImpl::supportsOperation(OutDevSupportType eType) const +{ + bool bRet = false; + switch (eType) + { + case OutDevSupportType::TransparentRect: + case OutDevSupportType::B2DDraw: + { + XRenderPeer& rPeer = XRenderPeer::GetInstance(); + const SalDisplay* pSalDisp = mrParent.GetDisplay(); + const SalVisual& rSalVis = pSalDisp->GetVisual(mrParent.GetScreenNumber()); + + Visual* pDstXVisual = rSalVis.GetVisual(); + XRenderPictFormat* pDstVisFmt = rPeer.FindVisualFormat(pDstXVisual); + if (pDstVisFmt) + bRet = true; + } + break; + default: + break; + } + return bRet; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/unx/generic/gdi/gdiimpl.hxx b/vcl/unx/generic/gdi/gdiimpl.hxx new file mode 100644 index 000000000..48211b13d --- /dev/null +++ b/vcl/unx/generic/gdi/gdiimpl.hxx @@ -0,0 +1,297 @@ +/* -*- 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 . + */ + +#pragma once + +#include + +#include + +#include + +#include +#include +#include + +/* From */ +typedef unsigned long Pixel; + +class SalGraphics; +class SalBitmap; +class SalPolyLine; +class X11SalGraphics; +class Gradient; + +class X11SalGraphicsImpl : public SalGraphicsImpl, public X11GraphicsImpl +{ +private: + X11SalGraphics& mrParent; + + Color mnBrushColor; + GC mpBrushGC; // Brush attributes + Pixel mnBrushPixel; + + bool mbPenGC : 1; // is Pen GC valid + bool mbBrushGC : 1; // is Brush GC valid + bool mbCopyGC : 1; // is Copy GC valid + bool mbInvertGC : 1; // is Invert GC valid + bool mbInvert50GC : 1; // is Invert50 GC valid + bool mbStippleGC : 1; // is Stipple GC valid + bool mbTrackingGC : 1; // is Tracking GC valid + bool mbDitherBrush : 1; // is solid or tile + + bool mbXORMode : 1; // is ROP XOR Mode set + + GC mpPenGC; // Pen attributes + Color mnPenColor; + Pixel mnPenPixel; + + + GC mpMonoGC; + GC mpCopyGC; + GC mpMaskGC; + GC mpInvertGC; + GC mpInvert50GC; + GC mpStippleGC; + GC mpTrackingGC; + + GC CreateGC( Drawable hDrawable, + unsigned long nMask = GCGraphicsExposures ); + + GC SelectBrush(); + GC SelectPen(); + inline GC GetCopyGC(); + inline GC GetStippleGC(); + GC GetTrackingGC(); + GC GetInvertGC(); + GC GetInvert50GC(); + + void DrawLines( sal_uInt32 nPoints, + const SalPolyLine &rPoints, + GC pGC, + bool bClose + ); + + XID GetXRenderPicture(); + bool drawFilledTrapezoids( const basegfx::B2DTrapezoid*, int nTrapCount, double fTransparency ); + bool drawFilledTriangles( + const basegfx::B2DHomMatrix& rObjectToDevice, + const basegfx::triangulator::B2DTriangleVector& rTriangles, + double fTransparency); + + tools::Long GetGraphicsHeight() const; + + void drawMaskedBitmap( const SalTwoRect& rPosAry, + const SalBitmap& rSalBitmap, + const SalBitmap& rTransparentBitmap ); + + void internalDrawPolyLine( sal_uInt32 nPoints, const Point* pPtAry, bool bClose ); + +public: + + explicit X11SalGraphicsImpl(X11SalGraphics& rParent); + + virtual void freeResources() override; + + virtual ~X11SalGraphicsImpl() override; + + virtual OUString getRenderBackendName() const override { return "gen"; } + + virtual bool setClipRegion( const vcl::Region& ) override; + // + // get the depth of the device + virtual sal_uInt16 GetBitCount() const override; + + // get the width of the device + virtual tools::Long GetGraphicsWidth() const override; + + // set the clip region to empty + virtual void ResetClipRegion() override; + + // set the line color to transparent (= don't draw lines) + + virtual void SetLineColor() override; + + // set the line color to a specific color + virtual void SetLineColor( Color nColor ) override; + + // set the fill color to transparent (= don't fill) + virtual void SetFillColor() override; + + // set the fill color to a specific color, shapes will be + // filled accordingly + virtual void SetFillColor( Color nColor ) override; + + // enable/disable XOR drawing + virtual void SetXORMode( bool bSet, bool bInvertOnly ) override; + + // set line color for raster operations + virtual void SetROPLineColor( SalROPColor nROPColor ) override; + + // set fill color for raster operations + virtual void SetROPFillColor( SalROPColor nROPColor ) override; + + // draw --> LineColor and FillColor and RasterOp and ClipRegion + virtual void drawPixel( tools::Long nX, tools::Long nY ) override; + virtual void drawPixel( tools::Long nX, tools::Long nY, Color nColor ) override; + + virtual void drawLine( tools::Long nX1, tools::Long nY1, tools::Long nX2, tools::Long nY2 ) override; + + virtual void drawRect( tools::Long nX, tools::Long nY, tools::Long nWidth, tools::Long nHeight ) override; + + virtual void drawPolyLine( sal_uInt32 nPoints, const Point* pPtAry ) override; + + virtual void drawPolygon( sal_uInt32 nPoints, const Point* pPtAry ) override; + + virtual void drawPolyPolygon( sal_uInt32 nPoly, const sal_uInt32* pPoints, const Point** pPtAry ) override; + + virtual bool drawPolyPolygon( + const basegfx::B2DHomMatrix& rObjectToDevice, + const basegfx::B2DPolyPolygon&, + double fTransparency) override; + + virtual bool drawPolyLine( + const basegfx::B2DHomMatrix& rObjectToDevice, + const basegfx::B2DPolygon&, + double fTransparency, + double fLineWidth, + const std::vector< double >* pStroke, // MM01 + basegfx::B2DLineJoin, + css::drawing::LineCap, + double fMiterMinimumAngle, + bool bPixelSnapHairline) override; + + virtual bool drawPolyLineBezier( + sal_uInt32 nPoints, + const Point* pPtAry, + const PolyFlags* pFlgAry ) override; + + virtual bool drawPolygonBezier( + sal_uInt32 nPoints, + const Point* pPtAry, + const PolyFlags* pFlgAry ) override; + + virtual bool drawPolyPolygonBezier( + sal_uInt32 nPoly, + const sal_uInt32* pPoints, + const Point* const* pPtAry, + const PolyFlags* const* pFlgAry ) override; + + // CopyArea --> No RasterOp, but ClipRegion + virtual void copyArea( + tools::Long nDestX, tools::Long nDestY, + tools::Long nSrcX, tools::Long nSrcY, + tools::Long nSrcWidth, tools::Long nSrcHeight, + bool bWindowInvalidate ) override; + + // CopyBits and DrawBitmap --> RasterOp and ClipRegion + // CopyBits() --> pSrcGraphics == NULL, then CopyBits on same Graphics + virtual void copyBits( const SalTwoRect& rPosAry, SalGraphics* pSrcGraphics ) override; + + virtual void drawBitmap( const SalTwoRect& rPosAry, const SalBitmap& rSalBitmap ) override; + + virtual void drawBitmap( + const SalTwoRect& rPosAry, + const SalBitmap& rSalBitmap, + const SalBitmap& rMaskBitmap ) override; + + virtual void drawMask( + const SalTwoRect& rPosAry, + const SalBitmap& rSalBitmap, + Color nMaskColor ) override; + + virtual std::shared_ptr getBitmap( tools::Long nX, tools::Long nY, tools::Long nWidth, tools::Long nHeight ) override; + + virtual Color getPixel( tools::Long nX, tools::Long nY ) override; + + // invert --> ClipRegion (only Windows or VirDevs) + virtual void invert( + tools::Long nX, tools::Long nY, + tools::Long nWidth, tools::Long nHeight, + SalInvert nFlags) override; + + virtual void invert( sal_uInt32 nPoints, const Point* pPtAry, SalInvert nFlags ) override; + + virtual bool drawEPS( + tools::Long nX, tools::Long nY, + tools::Long nWidth, tools::Long nHeight, + void* pPtr, + sal_uInt32 nSize ) override; + + /** Blend bitmap with color channels */ + virtual bool blendBitmap( + const SalTwoRect&, + const SalBitmap& rBitmap ) override; + + /** Render bitmap by blending using the mask and alpha channel */ + virtual bool blendAlphaBitmap( + const SalTwoRect&, + const SalBitmap& rSrcBitmap, + const SalBitmap& rMaskBitmap, + const SalBitmap& rAlphaBitmap ) override; + + /** Render bitmap with alpha channel + + @param rSourceBitmap + Source bitmap to blit + + @param rAlphaBitmap + Alpha channel to use for blitting + + @return true, if the operation succeeded, and false + otherwise. In this case, clients should try to emulate alpha + compositing themselves + */ + virtual bool drawAlphaBitmap( + const SalTwoRect&, + const SalBitmap& rSourceBitmap, + const SalBitmap& rAlphaBitmap ) override; + + /** draw transformed bitmap (maybe with alpha) where Null, X, Y define the coordinate system */ + virtual bool drawTransformedBitmap( + const basegfx::B2DPoint& rNull, + const basegfx::B2DPoint& rX, + const basegfx::B2DPoint& rY, + const SalBitmap& rSourceBitmap, + const SalBitmap* pAlphaBitmap, + double fAlpha) override; + + virtual bool hasFastDrawTransformedBitmap() const override; + + /** Render solid rectangle with given transparency + + @param nTransparency + Transparency value (0-255) to use. 0 blits and opaque, 255 a + fully transparent rectangle + */ + virtual bool drawAlphaRect( + tools::Long nX, tools::Long nY, + tools::Long nWidth, tools::Long nHeight, + sal_uInt8 nTransparency ) override; + + virtual bool drawGradient(const tools::PolyPolygon& rPolygon, const Gradient& rGradient) override; + virtual bool implDrawGradient(basegfx::B2DPolyPolygon const & rPolyPolygon, SalGradient const & rGradient) override; + + virtual bool supportsOperation(OutDevSupportType eType) const override; + +public: + void Init() override; +}; + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/unx/generic/gdi/salbmp.cxx b/vcl/unx/generic/gdi/salbmp.cxx new file mode 100644 index 000000000..804b50184 --- /dev/null +++ b/vcl/unx/generic/gdi/salbmp.cxx @@ -0,0 +1,1001 @@ +/* -*- 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 + +#ifdef FREEBSD +#include +#endif + +#include +#include + +#include +#include + +#include +#include +#include +#include + +#include +#include +#include +#include + +#include + +#include + +#if defined HAVE_VALGRIND_HEADERS +#include +#endif + +#include + +ImplSalBitmapCache* X11SalBitmap::mpCache = nullptr; +unsigned int X11SalBitmap::mnCacheInstCount = 0; + +X11SalBitmap::X11SalBitmap() + : mbGrey( false ) +{ +} + +X11SalBitmap::~X11SalBitmap() +{ + Destroy(); +} + +void X11SalBitmap::ImplCreateCache() +{ + if( !mnCacheInstCount++ ) + mpCache = new ImplSalBitmapCache; +} + +void X11SalBitmap::ImplDestroyCache() +{ + SAL_WARN_IF( !mnCacheInstCount, "vcl", "X11SalBitmap::ImplDestroyCache(): underflow" ); + + if( mnCacheInstCount && !--mnCacheInstCount ) + { + delete mpCache; + mpCache = nullptr; + } +} + +void X11SalBitmap::ImplRemovedFromCache() +{ + mpDDB.reset(); +} + +#if defined HAVE_VALGRIND_HEADERS +namespace +{ + void blankExtraSpace(BitmapBuffer* pDIB) + { + size_t nExtraSpaceInScanLine = pDIB->mnScanlineSize - pDIB->mnWidth * pDIB->mnBitCount / 8; + if (nExtraSpaceInScanLine) + { + for (tools::Long i = 0; i < pDIB->mnHeight; ++i) + { + sal_uInt8 *pRow = pDIB->mpBits + (i * pDIB->mnScanlineSize); + memset(pRow + (pDIB->mnScanlineSize - nExtraSpaceInScanLine), 0, nExtraSpaceInScanLine); + } + } + } +} +#endif + +std::unique_ptr X11SalBitmap::ImplCreateDIB( + const Size& rSize, + vcl::PixelFormat ePixelFormat, + const BitmapPalette& rPal) +{ + std::unique_ptr pDIB; + + if( !rSize.Width() || !rSize.Height() ) + return nullptr; + + try + { + pDIB.reset(new BitmapBuffer); + } + catch (const std::bad_alloc&) + { + return nullptr; + } + + pDIB->mnFormat = ScanlineFormat::NONE; + + switch(ePixelFormat) + { + case vcl::PixelFormat::N1_BPP: + pDIB->mnFormat |= ScanlineFormat::N1BitMsbPal; + break; + case vcl::PixelFormat::N8_BPP: + pDIB->mnFormat |= ScanlineFormat::N8BitPal; + break; + case vcl::PixelFormat::N24_BPP: + pDIB->mnFormat |= ScanlineFormat::N24BitTcBgr; + break; + case vcl::PixelFormat::N32_BPP: + default: + SAL_WARN("vcl.gdi", "32-bit images not supported, converting to 24-bit"); + ePixelFormat = vcl::PixelFormat::N24_BPP; + pDIB->mnFormat |= ScanlineFormat::N24BitTcBgr; + break; + } + + sal_uInt16 nColors = 0; + if (ePixelFormat <= vcl::PixelFormat::N8_BPP) + nColors = vcl::numberOfColors(ePixelFormat); + + pDIB->mnWidth = rSize.Width(); + pDIB->mnHeight = rSize.Height(); + tools::Long nScanlineBase; + bool bFail = o3tl::checked_multiply(pDIB->mnWidth, vcl::pixelFormatBitCount(ePixelFormat), nScanlineBase); + if (bFail) + { + SAL_WARN("vcl.gdi", "checked multiply failed"); + return nullptr; + } + pDIB->mnScanlineSize = AlignedWidth4Bytes(nScanlineBase); + if (pDIB->mnScanlineSize < nScanlineBase/8) + { + SAL_WARN("vcl.gdi", "scanline calculation wraparound"); + return nullptr; + } + pDIB->mnBitCount = vcl::pixelFormatBitCount(ePixelFormat); + + if( nColors ) + { + pDIB->maPalette = rPal; + pDIB->maPalette.SetEntryCount( nColors ); + } + + try + { + pDIB->mpBits = new sal_uInt8[ pDIB->mnScanlineSize * pDIB->mnHeight ]; +#if defined HAVE_VALGRIND_HEADERS + if (RUNNING_ON_VALGRIND) + blankExtraSpace(pDIB.get()); +#endif + } + catch (const std::bad_alloc&) + { + return nullptr; + } + + return pDIB; +} + +std::unique_ptr X11SalBitmap::ImplCreateDIB( + Drawable aDrawable, + SalX11Screen nScreen, + tools::Long nDrawableDepth, + tools::Long nX, + tools::Long nY, + tools::Long nWidth, + tools::Long nHeight, + bool bGrey +) { + std::unique_ptr pDIB; + + if( aDrawable && nWidth && nHeight && nDrawableDepth ) + { + SalDisplay* pSalDisp = vcl_sal::getSalDisplay(GetGenericUnixSalData()); + Display* pXDisp = pSalDisp->GetDisplay(); + + // do not die on XError here + // alternatively one could check the coordinates for being offscreen + // but this call can actually work on servers with backing store + // defaults even if the rectangle is offscreen + // so better catch the XError + GetGenericUnixSalData()->ErrorTrapPush(); + XImage* pImage = XGetImage( pXDisp, aDrawable, nX, nY, nWidth, nHeight, AllPlanes, ZPixmap ); + bool bWasError = GetGenericUnixSalData()->ErrorTrapPop( false ); + + if( ! bWasError && pImage && pImage->data ) + { + const SalTwoRect aTwoRect = { 0, 0, nWidth, nHeight, 0, 0, nWidth, nHeight }; + BitmapBuffer aSrcBuf; + std::optional pDstPal; + + aSrcBuf.mnFormat = ScanlineFormat::TopDown; + aSrcBuf.mnWidth = nWidth; + aSrcBuf.mnHeight = nHeight; + aSrcBuf.mnBitCount = pImage->bits_per_pixel; + aSrcBuf.mnScanlineSize = pImage->bytes_per_line; + aSrcBuf.mpBits = reinterpret_cast(pImage->data); + + pImage->red_mask = pSalDisp->GetVisual( nScreen ).red_mask; + pImage->green_mask = pSalDisp->GetVisual( nScreen ).green_mask; + pImage->blue_mask = pSalDisp->GetVisual( nScreen ).blue_mask; + + switch( aSrcBuf.mnBitCount ) + { + case 1: + { + aSrcBuf.mnFormat |= ( LSBFirst == pImage->bitmap_bit_order + ? ScanlineFormat::N1BitLsbPal + : ScanlineFormat::N1BitMsbPal + ); + } + break; + + case 8: + { + aSrcBuf.mnFormat |= ScanlineFormat::N8BitPal; + } + break; + + case 24: + { + if( ( LSBFirst == pImage->byte_order ) && ( pImage->red_mask == 0xFF ) ) + aSrcBuf.mnFormat |= ScanlineFormat::N24BitTcRgb; + else + aSrcBuf.mnFormat |= ScanlineFormat::N24BitTcBgr; + } + break; + + case 32: + { + if( LSBFirst == pImage->byte_order ) + aSrcBuf.mnFormat |= ( pSalDisp->GetVisual(nScreen).red_mask == 0xFF + ? ScanlineFormat::N32BitTcRgba + : ScanlineFormat::N32BitTcBgra + ); + else + aSrcBuf.mnFormat |= ( pSalDisp->GetVisual(nScreen).red_mask == 0xFF + ? ScanlineFormat::N32BitTcAbgr + : ScanlineFormat::N32BitTcArgb + ); + } + break; + + default: assert(false); + } + + BitmapPalette& rPal = aSrcBuf.maPalette; + + if( aSrcBuf.mnBitCount == 1 ) + { + rPal.SetEntryCount( 2 ); + rPal[ 0 ] = COL_BLACK; + rPal[ 1 ] = COL_WHITE; + pDstPal = rPal; + } + else if( pImage->depth == 8 && bGrey ) + { + rPal.SetEntryCount( 256 ); + + for( sal_uInt16 i = 0; i < 256; i++ ) + { + BitmapColor& rBmpCol = rPal[ i ]; + + rBmpCol.SetRed( i ); + rBmpCol.SetGreen( i ); + rBmpCol.SetBlue( i ); + } + + pDstPal = rPal; + } + else if( aSrcBuf.mnBitCount <= 8 ) + { + const SalColormap& rColMap = pSalDisp->GetColormap( nScreen ); + const sal_uInt16 nCols = std::min(static_cast(rColMap.GetUsed()), + sal_uLong(1) << nDrawableDepth); + + rPal.SetEntryCount( nCols ); + + for( sal_uInt16 i = 0; i < nCols; i++ ) + { + const Color nColor( rColMap.GetColor( i ) ); + BitmapColor& rBmpCol = rPal[ i ]; + + rBmpCol.SetRed( nColor.GetRed() ); + rBmpCol.SetGreen( nColor.GetGreen() ); + rBmpCol.SetBlue( nColor.GetBlue() ); + } + pDstPal = rPal; + } + + pDIB = StretchAndConvert( aSrcBuf, aTwoRect, aSrcBuf.mnFormat, + pDstPal, &aSrcBuf.maColorMask ); + XDestroyImage( pImage ); + } + } + + return pDIB; +} + +XImage* X11SalBitmap::ImplCreateXImage( + SalDisplay const *pSalDisp, + SalX11Screen nScreen, + tools::Long nDepth, + const SalTwoRect& rTwoRect +) const +{ + XImage* pImage = nullptr; + + if( !mpDIB && mpDDB ) + { + const_cast(this)->mpDIB = + ImplCreateDIB( mpDDB->ImplGetPixmap(), + mpDDB->ImplGetScreen(), + mpDDB->ImplGetDepth(), + 0, 0, + mpDDB->ImplGetWidth(), + mpDDB->ImplGetHeight(), + mbGrey ); + } + + if( mpDIB && mpDIB->mnWidth && mpDIB->mnHeight ) + { + Display* pXDisp = pSalDisp->GetDisplay(); + tools::Long nWidth = rTwoRect.mnDestWidth; + tools::Long nHeight = rTwoRect.mnDestHeight; + + if( 1 == GetBitCount() ) + nDepth = 1; + + pImage = XCreateImage( pXDisp, pSalDisp->GetVisual( nScreen ).GetVisual(), + nDepth, ( 1 == nDepth ) ? XYBitmap :ZPixmap, 0, nullptr, + nWidth, nHeight, 32, 0 ); + + if( pImage ) + { + std::unique_ptr pDstBuf; + ScanlineFormat nDstFormat = ScanlineFormat::TopDown; + std::optional xPal; + std::unique_ptr xMask; + + switch( pImage->bits_per_pixel ) + { + case 1: + nDstFormat |= ( LSBFirst == pImage->bitmap_bit_order + ? ScanlineFormat::N1BitLsbPal + : ScanlineFormat::N1BitMsbPal + ); + break; + + case 8: + nDstFormat |= ScanlineFormat::N8BitPal; + break; + + case 24: + { + if( ( LSBFirst == pImage->byte_order ) && ( pImage->red_mask == 0xFF ) ) + nDstFormat |= ScanlineFormat::N24BitTcRgb; + else + nDstFormat |= ScanlineFormat::N24BitTcBgr; + } + break; + + case 32: + { + if( LSBFirst == pImage->byte_order ) + nDstFormat |= ( pImage->red_mask == 0xFF + ? ScanlineFormat::N32BitTcRgba + : ScanlineFormat::N32BitTcBgra + ); + else + nDstFormat |= ( pImage->red_mask == 0xFF + ? ScanlineFormat::N32BitTcAbgr + : ScanlineFormat::N32BitTcArgb + ); + } + break; + + default: assert(false); + } + + if( pImage->depth == 1 ) + { + xPal.emplace(2); + (*xPal)[ 0 ] = COL_BLACK; + (*xPal)[ 1 ] = COL_WHITE; + } + else if( pImage->depth == 8 && mbGrey ) + { + xPal.emplace(256); + + for( sal_uInt16 i = 0; i < 256; i++ ) + { + BitmapColor& rBmpCol = (*xPal)[ i ]; + + rBmpCol.SetRed( i ); + rBmpCol.SetGreen( i ); + rBmpCol.SetBlue( i ); + } + + } + else if( pImage->depth <= 8 ) + { + const SalColormap& rColMap = pSalDisp->GetColormap( nScreen ); + const sal_uInt16 nCols = std::min( static_cast(rColMap.GetUsed()) + , static_cast(1 << pImage->depth) + ); + + xPal.emplace(nCols); + + for( sal_uInt16 i = 0; i < nCols; i++ ) + { + const Color nColor( rColMap.GetColor( i ) ); + BitmapColor& rBmpCol = (*xPal)[ i ]; + + rBmpCol.SetRed( nColor.GetRed() ); + rBmpCol.SetGreen( nColor.GetGreen() ); + rBmpCol.SetBlue( nColor.GetBlue() ); + } + } + + pDstBuf = StretchAndConvert( *mpDIB, rTwoRect, nDstFormat, xPal, xMask.get() ); + xPal.reset(); + xMask.reset(); + + if( pDstBuf && pDstBuf->mpBits ) + { +#if defined HAVE_VALGRIND_HEADERS + if (RUNNING_ON_VALGRIND) + blankExtraSpace(pDstBuf.get()); +#endif + // set data in buffer as data member in pImage + pImage->data = reinterpret_cast(pDstBuf->mpBits); + } + else + { + XDestroyImage( pImage ); + pImage = nullptr; + } + + // note that pDstBuf it deleted here, but that doesn't destroy allocated data in buffer + } + } + + return pImage; +} + +bool X11SalBitmap::ImplCreateFromDrawable( + Drawable aDrawable, + SalX11Screen nScreen, + tools::Long nDrawableDepth, + tools::Long nX, + tools::Long nY, + tools::Long nWidth, + tools::Long nHeight +) { + Destroy(); + + if( aDrawable && nWidth && nHeight && nDrawableDepth ) + mpDDB.reset(new ImplSalDDB( aDrawable, nScreen, nDrawableDepth, nX, nY, nWidth, nHeight )); + + return( mpDDB != nullptr ); +} + +ImplSalDDB* X11SalBitmap::ImplGetDDB( + Drawable aDrawable, + SalX11Screen nXScreen, + tools::Long nDrawableDepth, + const SalTwoRect& rTwoRect +) const +{ + if( !mpDDB || !mpDDB->ImplMatches( nXScreen, nDrawableDepth, rTwoRect ) ) + { + if( mpDDB ) + { + // do we already have a DIB? if not, create aDIB from current DDB first + if( !mpDIB ) + { + const_cast(this)->mpDIB = ImplCreateDIB( mpDDB->ImplGetPixmap(), + mpDDB->ImplGetScreen(), + mpDDB->ImplGetDepth(), + 0, 0, + mpDDB->ImplGetWidth(), + mpDDB->ImplGetHeight(), + mbGrey ); + } + + mpDDB.reset(); + } + + if( mpCache ) + mpCache->ImplRemove( this ); + + SalTwoRect aTwoRect( rTwoRect ); + if( aTwoRect.mnSrcX < 0 ) + { + aTwoRect.mnSrcWidth += aTwoRect.mnSrcX; + aTwoRect.mnSrcX = 0; + } + if( aTwoRect.mnSrcY < 0 ) + { + aTwoRect.mnSrcHeight += aTwoRect.mnSrcY; + aTwoRect.mnSrcY = 0; + } + + // create new DDB from DIB + const Size aSize( GetSize() ); + if( aTwoRect.mnSrcWidth == aTwoRect.mnDestWidth && + aTwoRect.mnSrcHeight == aTwoRect.mnDestHeight ) + { + aTwoRect.mnSrcX = aTwoRect.mnSrcY = aTwoRect.mnDestX = aTwoRect.mnDestY = 0; + aTwoRect.mnSrcWidth = aTwoRect.mnDestWidth = aSize.Width(); + aTwoRect.mnSrcHeight = aTwoRect.mnDestHeight = aSize.Height(); + } + else if( aTwoRect.mnSrcWidth+aTwoRect.mnSrcX > aSize.Width() || + aTwoRect.mnSrcHeight+aTwoRect.mnSrcY > aSize.Height() ) + { + // #i47823# this should not happen at all, but does nonetheless + // because BitmapEx allows for mask bitmaps of different size + // than image bitmap (broken) + if( aTwoRect.mnSrcX >= aSize.Width() || + aTwoRect.mnSrcY >= aSize.Height() ) + return nullptr; // this would be a really mad case + + if( aTwoRect.mnSrcWidth+aTwoRect.mnSrcX > aSize.Width() ) + { + aTwoRect.mnSrcWidth = aSize.Width()-aTwoRect.mnSrcX; + if( aTwoRect.mnSrcWidth < 1 ) + { + aTwoRect.mnSrcX = 0; + aTwoRect.mnSrcWidth = aSize.Width(); + } + } + if( aTwoRect.mnSrcHeight+aTwoRect.mnSrcY > aSize.Height() ) + { + aTwoRect.mnSrcHeight = aSize.Height() - aTwoRect.mnSrcY; + if( aTwoRect.mnSrcHeight < 1 ) + { + aTwoRect.mnSrcY = 0; + aTwoRect.mnSrcHeight = aSize.Height(); + } + } + } + + XImage* pImage = ImplCreateXImage( vcl_sal::getSalDisplay(GetGenericUnixSalData()), nXScreen, + nDrawableDepth, aTwoRect ); + + if( pImage ) + { + mpDDB.reset(new ImplSalDDB( pImage, aDrawable, nXScreen, aTwoRect )); + delete[] pImage->data; + pImage->data = nullptr; + XDestroyImage( pImage ); + + if( mpCache ) + mpCache->ImplAdd( const_cast(this) ); + } + } + + return mpDDB.get(); +} + +void X11SalBitmap::ImplDraw( + Drawable aDrawable, + SalX11Screen nXScreen, + tools::Long nDrawableDepth, + const SalTwoRect& rTwoRect, + const GC& rGC +) const +{ + ImplGetDDB( aDrawable, nXScreen, nDrawableDepth, rTwoRect ); + if( mpDDB ) + mpDDB->ImplDraw( aDrawable, rTwoRect, rGC ); +} + +bool X11SalBitmap::Create( const Size& rSize, vcl::PixelFormat ePixelFormat, const BitmapPalette& rPal ) +{ + Destroy(); + mpDIB = ImplCreateDIB( rSize, ePixelFormat, rPal ); + + return( mpDIB != nullptr ); +} + +bool X11SalBitmap::Create( const SalBitmap& rSSalBmp ) +{ + Destroy(); + + auto pX11Bmp = dynamic_cast( &rSSalBmp ); + if (!pX11Bmp) + return false; + + const X11SalBitmap& rSalBmp = *pX11Bmp; + + if( rSalBmp.mpDIB ) + { + // TODO: reference counting... + mpDIB.reset(new BitmapBuffer( *rSalBmp.mpDIB )); + // TODO: get rid of this when BitmapBuffer gets copy constructor + try + { + mpDIB->mpBits = new sal_uInt8[ mpDIB->mnScanlineSize * mpDIB->mnHeight ]; +#if defined HAVE_VALGRIND_HEADERS + if (RUNNING_ON_VALGRIND) + blankExtraSpace(mpDIB.get()); +#endif + } + catch (const std::bad_alloc&) + { + mpDIB.reset(); + } + + if( mpDIB ) + memcpy( mpDIB->mpBits, rSalBmp.mpDIB->mpBits, mpDIB->mnScanlineSize * mpDIB->mnHeight ); + } + else if( rSalBmp.mpDDB ) + ImplCreateFromDrawable( rSalBmp.mpDDB->ImplGetPixmap(), + rSalBmp.mpDDB->ImplGetScreen(), + rSalBmp.mpDDB->ImplGetDepth(), + 0, 0, rSalBmp.mpDDB->ImplGetWidth(), rSalBmp.mpDDB->ImplGetHeight() ); + + return( ( !rSalBmp.mpDIB && !rSalBmp.mpDDB ) || + ( rSalBmp.mpDIB && ( mpDIB != nullptr ) ) || + ( rSalBmp.mpDDB && ( mpDDB != nullptr ) ) ); +} + +bool X11SalBitmap::Create( const SalBitmap&, SalGraphics* ) +{ + return false; +} + +bool X11SalBitmap::Create(const SalBitmap&, vcl::PixelFormat /*eNewPixelFormat*/) +{ + return false; +} + +bool X11SalBitmap::Create( + const css::uno::Reference< css::rendering::XBitmapCanvas >& rBitmapCanvas, + Size& rSize, + bool bMask +) { + css::uno::Reference< css::beans::XFastPropertySet > xFastPropertySet( rBitmapCanvas, css::uno::UNO_QUERY ); + + if( xFastPropertySet ) { + css::uno::Sequence< css::uno::Any > args; + + if( xFastPropertySet->getFastPropertyValue(bMask ? 2 : 1) >>= args ) { + sal_Int64 pixmapHandle = {}; // spurious -Werror=maybe-uninitialized + sal_Int32 depth; + if( ( args[1] >>= pixmapHandle ) && ( args[2] >>= depth ) ) { + + mbGrey = bMask; + bool bSuccess = ImplCreateFromDrawable( + pixmapHandle, + // FIXME: this seems multi-screen broken to me + SalX11Screen( 0 ), + depth, + 0, + 0, + rSize.Width(), + rSize.Height() + ); + bool bFreePixmap = false; + if( bSuccess && (args[0] >>= bFreePixmap) && bFreePixmap ) + XFreePixmap( vcl_sal::getSalDisplay(GetGenericUnixSalData())->GetDisplay(), pixmapHandle ); + + return bSuccess; + } + } + } + + return false; +} + +void X11SalBitmap::Destroy() +{ + if( mpDIB ) + { + delete[] mpDIB->mpBits; + mpDIB.reset(); + } + + mpDDB.reset(); + + if( mpCache ) + mpCache->ImplRemove( this ); +} + +Size X11SalBitmap::GetSize() const +{ + Size aSize; + + if( mpDIB ) + { + aSize.setWidth( mpDIB->mnWidth ); + aSize.setHeight( mpDIB->mnHeight ); + } + else if( mpDDB ) + { + aSize.setWidth( mpDDB->ImplGetWidth() ); + aSize.setHeight( mpDDB->ImplGetHeight() ); + } + + return aSize; +} + +sal_uInt16 X11SalBitmap::GetBitCount() const +{ + sal_uInt16 nBitCount; + + if( mpDIB ) + nBitCount = mpDIB->mnBitCount; + else if( mpDDB ) + nBitCount = mpDDB->ImplGetDepth(); + else + nBitCount = 0; + + return nBitCount; +} + +BitmapBuffer* X11SalBitmap::AcquireBuffer( BitmapAccessMode /*nMode*/ ) +{ + if( !mpDIB && mpDDB ) + { + mpDIB = ImplCreateDIB( + mpDDB->ImplGetPixmap(), + mpDDB->ImplGetScreen(), + mpDDB->ImplGetDepth(), + 0, 0, + mpDDB->ImplGetWidth(), + mpDDB->ImplGetHeight(), + mbGrey + ); + } + + return mpDIB.get(); +} + +void X11SalBitmap::ReleaseBuffer( BitmapBuffer*, BitmapAccessMode nMode ) +{ + if( nMode == BitmapAccessMode::Write ) + { + mpDDB.reset(); + + if( mpCache ) + mpCache->ImplRemove( this ); + InvalidateChecksum(); + } +} + +bool X11SalBitmap::GetSystemData( BitmapSystemData& rData ) +{ + if( mpDDB ) + { + // Rename/retype pDummy to your likings (though X11 Pixmap is + // prolly not a good idea, since it's accessed from + // non-platform aware code in vcl/bitmap.hxx) + rData.aPixmap = reinterpret_cast(mpDDB->ImplGetPixmap()); + rData.mnWidth = mpDDB->ImplGetWidth (); + rData.mnHeight = mpDDB->ImplGetHeight (); + return true; + } + + return false; +} + +bool X11SalBitmap::ScalingSupported() const +{ + return false; +} + +bool X11SalBitmap::Scale( const double& /*rScaleX*/, const double& /*rScaleY*/, BmpScaleFlag /*nScaleFlag*/ ) +{ + return false; +} + +bool X11SalBitmap::Replace( const Color& /*rSearchColor*/, const Color& /*rReplaceColor*/, sal_uInt8 /*nTol*/ ) +{ + return false; +} + + +ImplSalDDB::ImplSalDDB( XImage* pImage, Drawable aDrawable, + SalX11Screen nXScreen, const SalTwoRect& rTwoRect ) + : maPixmap ( 0 ) + , maTwoRect ( rTwoRect ) + , mnDepth ( pImage->depth ) + , mnXScreen ( nXScreen ) +{ + SalDisplay* pSalDisp = vcl_sal::getSalDisplay(GetGenericUnixSalData()); + Display* pXDisp = pSalDisp->GetDisplay(); + + maPixmap = limitXCreatePixmap( pXDisp, aDrawable, ImplGetWidth(), ImplGetHeight(), ImplGetDepth() ); + if (!maPixmap) + return; + + XGCValues aValues; + GC aGC; + int nValues = GCFunction; + + aValues.function = GXcopy; + + if( 1 == mnDepth ) + { + nValues |= ( GCForeground | GCBackground ); + aValues.foreground = 1; + aValues.background = 0; + } + + aGC = XCreateGC( pXDisp, maPixmap, nValues, &aValues ); + XPutImage( pXDisp, maPixmap, aGC, pImage, 0, 0, 0, 0, maTwoRect.mnDestWidth, maTwoRect.mnDestHeight ); + XFreeGC( pXDisp, aGC ); +} + +ImplSalDDB::ImplSalDDB( + Drawable aDrawable, + SalX11Screen nXScreen, + tools::Long nDrawableDepth, + tools::Long nX, + tools::Long nY, + tools::Long nWidth, + tools::Long nHeight +) : maTwoRect(0, 0, nWidth, nHeight, 0, 0, nWidth, nHeight) + , mnDepth( nDrawableDepth ) + , mnXScreen( nXScreen ) +{ + SalDisplay* pSalDisp = vcl_sal::getSalDisplay(GetGenericUnixSalData()); + Display* pXDisp = pSalDisp->GetDisplay(); + + if( (maPixmap = limitXCreatePixmap( pXDisp, aDrawable, nWidth, nHeight, nDrawableDepth )) ) + { + XGCValues aValues; + GC aGC; + int nValues = GCFunction; + + aValues.function = GXcopy; + + if( 1 == mnDepth ) + { + nValues |= ( GCForeground | GCBackground ); + aValues.foreground = 1; + aValues.background = 0; + } + + aGC = XCreateGC( pXDisp, maPixmap, nValues, &aValues ); + ImplDraw( aDrawable, nDrawableDepth, maPixmap, + nX, nY, nWidth, nHeight, 0, 0, aGC ); + XFreeGC( pXDisp, aGC ); + } + else + { + maTwoRect.mnSrcWidth = maTwoRect.mnDestWidth = 0; + maTwoRect.mnSrcHeight = maTwoRect.mnDestHeight = 0; + } +} + +ImplSalDDB::~ImplSalDDB() +{ + if( maPixmap && ImplGetSVData() ) + XFreePixmap( vcl_sal::getSalDisplay(GetGenericUnixSalData())->GetDisplay(), maPixmap ); +} + +bool ImplSalDDB::ImplMatches( SalX11Screen nXScreen, tools::Long nDepth, const SalTwoRect& rTwoRect ) const +{ + bool bRet = false; + + if( ( maPixmap != 0 ) && ( ( mnDepth == nDepth ) || ( 1 == mnDepth ) ) && nXScreen == mnXScreen) + { + if ( rTwoRect.mnSrcX == maTwoRect.mnSrcX + && rTwoRect.mnSrcY == maTwoRect.mnSrcY + && rTwoRect.mnSrcWidth == maTwoRect.mnSrcWidth + && rTwoRect.mnSrcHeight == maTwoRect.mnSrcHeight + && rTwoRect.mnDestWidth == maTwoRect.mnDestWidth + && rTwoRect.mnDestHeight == maTwoRect.mnDestHeight + ) + { + // absolutely identically + bRet = true; + } + else if( rTwoRect.mnSrcWidth == rTwoRect.mnDestWidth + && rTwoRect.mnSrcHeight == rTwoRect.mnDestHeight + && maTwoRect.mnSrcWidth == maTwoRect.mnDestWidth + && maTwoRect.mnSrcHeight == maTwoRect.mnDestHeight + && rTwoRect.mnSrcX >= maTwoRect.mnSrcX + && rTwoRect.mnSrcY >= maTwoRect.mnSrcY + && ( rTwoRect.mnSrcX + rTwoRect.mnSrcWidth ) <= ( maTwoRect.mnSrcX + maTwoRect.mnSrcWidth ) + && ( rTwoRect.mnSrcY + rTwoRect.mnSrcHeight ) <= ( maTwoRect.mnSrcY + maTwoRect.mnSrcHeight ) + ) + { + bRet = true; + } + } + + return bRet; +} + +void ImplSalDDB::ImplDraw( + Drawable aDrawable, + const SalTwoRect& rTwoRect, + const GC& rGC +) const +{ + ImplDraw( maPixmap, mnDepth, aDrawable, + rTwoRect.mnSrcX - maTwoRect.mnSrcX, rTwoRect.mnSrcY - maTwoRect.mnSrcY, + rTwoRect.mnDestWidth, rTwoRect.mnDestHeight, + rTwoRect.mnDestX, rTwoRect.mnDestY, rGC ); +} + +void ImplSalDDB::ImplDraw( + Drawable aSrcDrawable, + tools::Long nSrcDrawableDepth, + Drawable aDstDrawable, + tools::Long nSrcX, + tools::Long nSrcY, + tools::Long nDestWidth, + tools::Long nDestHeight, + tools::Long nDestX, + tools::Long nDestY, + const GC& rGC +) { + SalDisplay* pSalDisp = vcl_sal::getSalDisplay(GetGenericUnixSalData()); + Display* pXDisp = pSalDisp->GetDisplay(); + + if( 1 == nSrcDrawableDepth ) + { + XCopyPlane( pXDisp, aSrcDrawable, aDstDrawable, rGC, + nSrcX, nSrcY, nDestWidth, nDestHeight, nDestX, nDestY, 1 ); + } + else + { + XCopyArea( pXDisp, aSrcDrawable, aDstDrawable, rGC, + nSrcX, nSrcY, nDestWidth, nDestHeight, nDestX, nDestY ); + } +} + + +ImplSalBitmapCache::ImplSalBitmapCache() +{ +} + +ImplSalBitmapCache::~ImplSalBitmapCache() +{ + ImplClear(); +} + +void ImplSalBitmapCache::ImplAdd( X11SalBitmap* pBmp ) +{ + for(auto pObj : maBmpList) + { + if( pObj == pBmp ) + return; + } + maBmpList.push_back( pBmp ); +} + +void ImplSalBitmapCache::ImplRemove( X11SalBitmap const * pBmp ) +{ + auto it = std::find(maBmpList.begin(), maBmpList.end(), pBmp); + if( it != maBmpList.end() ) + { + (*it)->ImplRemovedFromCache(); + maBmpList.erase( it ); + } +} + +void ImplSalBitmapCache::ImplClear() +{ + for(auto pObj : maBmpList) + { + pObj->ImplRemovedFromCache(); + } + maBmpList.clear(); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/unx/generic/gdi/salgdi.cxx b/vcl/unx/generic/gdi/salgdi.cxx new file mode 100644 index 000000000..d6eecfecb --- /dev/null +++ b/vcl/unx/generic/gdi/salgdi.cxx @@ -0,0 +1,482 @@ +/* -*- 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 +#include +#if HAVE_FEATURE_SKIA +#include +#include +#endif + +#include +#include +#include + + +#include +#include +#include +#include +#include + +#include + +#include +#include +#include + +#include +#include +#include +#include + +#include +#include +#include +#include +#include "gdiimpl.hxx" + +#include +#include +#include "cairo_xlib_cairo.hxx" +#include + +#if ENABLE_CAIRO_CANVAS +#include "X11CairoSalGraphicsImpl.hxx" +#endif + + +// X11Common + +X11Common::X11Common() + : m_hDrawable(None) + , m_pColormap(nullptr) + , m_pExternalSurface(nullptr) +{} + +cairo_t* X11Common::getCairoContext() +{ + if (m_pExternalSurface) + return cairo_create(m_pExternalSurface); + + cairo_surface_t* surface = cairo_xlib_surface_create(GetXDisplay(), m_hDrawable, GetVisual().visual, SAL_MAX_INT16, SAL_MAX_INT16); + + cairo_t *cr = cairo_create(surface); + cairo_surface_destroy(surface); + + return cr; +} + +void X11Common::releaseCairoContext(cairo_t* cr) +{ + cairo_destroy(cr); +} + +bool X11Common::SupportsCairo() const +{ + static bool bSupportsCairo = [this] { + Display *pDisplay = GetXDisplay(); + int nDummy; + return XQueryExtension(pDisplay, "RENDER", &nDummy, &nDummy, &nDummy); + }(); + return bSupportsCairo; +} + +// X11SalGraphics + +X11SalGraphics::X11SalGraphics(): + m_pFrame(nullptr), + m_pVDev(nullptr), + m_nXScreen( 0 ), + m_pXRenderFormat(nullptr), + m_aXRenderPicture(0), + mpClipRegion(nullptr), + hBrush_(None), + bWindow_(false), + bVirDev_(false) +{ +#if HAVE_FEATURE_SKIA + if (SkiaHelper::isVCLSkiaEnabled()) + { + mxImpl.reset(new X11SkiaSalGraphicsImpl(*this)); + mxTextRenderImpl.reset(new SkiaTextRender); + } + else +#endif + { + mxTextRenderImpl.reset(new X11CairoTextRender(*this)); +#if ENABLE_CAIRO_CANVAS + mxImpl.reset(new X11CairoSalGraphicsImpl(*this, maX11Common)); +#else + mxImpl.reset(new X11SalGraphicsImpl(*this)); +#endif + } +} + +X11SalGraphics::~X11SalGraphics() COVERITY_NOEXCEPT_FALSE +{ + DeInit(); + ReleaseFonts(); + freeResources(); +} + +void X11SalGraphics::freeResources() +{ + Display *pDisplay = GetXDisplay(); + + if( mpClipRegion ) + { + XDestroyRegion( mpClipRegion ); + mpClipRegion = nullptr; + } + + mxImpl->freeResources(); + + if( hBrush_ ) + { + XFreePixmap( pDisplay, hBrush_ ); + hBrush_ = None; + } + if( m_pDeleteColormap ) + { + m_pDeleteColormap.reset(); + maX11Common.m_pColormap = nullptr; + } + if( m_aXRenderPicture ) + { + XRenderPeer::GetInstance().FreePicture( m_aXRenderPicture ); + m_aXRenderPicture = 0; + } +} + +SalGraphicsImpl* X11SalGraphics::GetImpl() const +{ + return mxImpl.get(); +} + +void X11SalGraphics::SetDrawable(Drawable aDrawable, cairo_surface_t* pExternalSurface, SalX11Screen nXScreen) +{ + maX11Common.m_pExternalSurface = pExternalSurface; + + // shortcut if nothing changed + if( maX11Common.m_hDrawable == aDrawable ) + return; + + // free screen specific resources if needed + if( nXScreen != m_nXScreen ) + { + freeResources(); + maX11Common.m_pColormap = &vcl_sal::getSalDisplay(GetGenericUnixSalData())->GetColormap( nXScreen ); + m_nXScreen = nXScreen; + } + + maX11Common.m_hDrawable = aDrawable; + SetXRenderFormat( nullptr ); + if( m_aXRenderPicture ) + { + XRenderPeer::GetInstance().FreePicture( m_aXRenderPicture ); + m_aXRenderPicture = 0; + } +} + +void X11SalGraphics::Init( SalFrame *pFrame, Drawable aTarget, + SalX11Screen nXScreen ) +{ + maX11Common.m_pColormap = &vcl_sal::getSalDisplay(GetGenericUnixSalData())->GetColormap(nXScreen); + m_nXScreen = nXScreen; + + m_pFrame = pFrame; + m_pVDev = nullptr; + + bWindow_ = true; + bVirDev_ = false; + + SetDrawable(aTarget, nullptr, nXScreen); + mxImpl->Init(); +} + +void X11SalGraphics::DeInit() +{ + mxImpl->DeInit(); + SetDrawable(None, nullptr, m_nXScreen); +} + +void X11SalGraphics::SetClipRegion( GC pGC, Region pXReg ) const +{ + Display *pDisplay = GetXDisplay(); + + int n = 0; + Region Regions[3]; + + if( mpClipRegion ) + Regions[n++] = mpClipRegion; + + if( pXReg && !XEmptyRegion( pXReg ) ) + Regions[n++] = pXReg; + + if( 0 == n ) + XSetClipMask( pDisplay, pGC, None ); + else if( 1 == n ) + XSetRegion( pDisplay, pGC, Regions[0] ); + else + { + Region pTmpRegion = XCreateRegion(); + XIntersectRegion( Regions[0], Regions[1], pTmpRegion ); + + XSetRegion( pDisplay, pGC, pTmpRegion ); + XDestroyRegion( pTmpRegion ); + } +} + +// Calculate a dither-pixmap and make a brush of it +#define P_DELTA 51 +#define DMAP( v, m ) ((v % P_DELTA) > m ? (v / P_DELTA) + 1 : (v / P_DELTA)) + +bool X11SalGraphics::GetDitherPixmap( Color nColor ) +{ + static const short nOrdDither8Bit[ 8 ][ 8 ] = + { + { 0, 38, 9, 48, 2, 40, 12, 50}, + {25, 12, 35, 22, 28, 15, 37, 24}, + { 6, 44, 3, 41, 8, 47, 5, 44}, + {32, 19, 28, 16, 34, 21, 31, 18}, + { 1, 40, 11, 49, 0, 39, 10, 48}, + {27, 14, 36, 24, 26, 13, 36, 23}, + { 8, 46, 4, 43, 7, 45, 4, 42}, + {33, 20, 30, 17, 32, 20, 29, 16} + }; + + // test for correct depth (8bit) + if( GetColormap().GetVisual().GetDepth() != 8 ) + return false; + + char pBits[64]; + char *pBitsPtr = pBits; + + // Set the palette-entries for the dithering tile + sal_uInt8 nColorRed = nColor.GetRed(); + sal_uInt8 nColorGreen = nColor.GetGreen(); + sal_uInt8 nColorBlue = nColor.GetBlue(); + + for(auto & nY : nOrdDither8Bit) + { + for( int nX = 0; nX < 8; nX++ ) + { + short nMagic = nY[nX]; + sal_uInt8 nR = P_DELTA * DMAP( nColorRed, nMagic ); + sal_uInt8 nG = P_DELTA * DMAP( nColorGreen, nMagic ); + sal_uInt8 nB = P_DELTA * DMAP( nColorBlue, nMagic ); + + *pBitsPtr++ = GetColormap().GetPixel( Color( nR, nG, nB ) ); + } + } + + // create the tile as ximage and an according pixmap -> caching + XImage *pImage = XCreateImage( GetXDisplay(), + GetColormap().GetXVisual(), + 8, + ZPixmap, + 0, // offset + pBits, // data + 8, 8, // width & height + 8, // bitmap_pad + 0 ); // (default) bytes_per_line + + if( !hBrush_ ) + hBrush_ = limitXCreatePixmap( GetXDisplay(), GetDrawable(), 8, 8, 8 ); + + // put the ximage to the pixmap + XPutImage( GetXDisplay(), + hBrush_, + GetDisplay()->GetCopyGC( m_nXScreen ), + pImage, + 0, 0, // Source + 0, 0, // Destination + 8, 8 ); // width & height + + // destroy image-frame but not palette-data + pImage->data = nullptr; + XDestroyImage( pImage ); + + return true; +} + +void X11SalGraphics::GetResolution( sal_Int32 &rDPIX, sal_Int32 &rDPIY ) // const +{ + char* pForceDpi; + if ((pForceDpi = getenv("SAL_FORCEDPI"))) + { + OString sForceDPI(pForceDpi); + rDPIX = rDPIY = sForceDPI.toInt32(); + return; + } + + const SalDisplay *pDisplay = GetDisplay(); + if (!pDisplay) + { + SAL_WARN( "vcl", "Null display"); + rDPIX = rDPIY = 96; + return; + } + + Pair dpi = pDisplay->GetResolution(); + rDPIX = dpi.A(); + rDPIY = dpi.B(); + + if ( rDPIY > 200 ) + { + rDPIX = Divide( rDPIX * 200, rDPIY ); + rDPIY = 200; + } + + // #i12705# equalize x- and y-resolution if they are close enough + if( rDPIX == rDPIY ) + return; + + // different x- and y- resolutions are usually artifacts of + // a wrongly calculated screen size. +#ifdef DEBUG + SAL_INFO("vcl.gdi", "Forcing Resolution from " + << std::hex << rDPIX + << std::dec << rDPIX + << " to " + << std::hex << rDPIY + << std::dec << rDPIY); +#endif + rDPIX = rDPIY; // y-resolution is more trustworthy +} + +XRenderPictFormat* X11SalGraphics::GetXRenderFormat() const +{ + if( m_pXRenderFormat == nullptr ) + m_pXRenderFormat = XRenderPeer::GetInstance().FindVisualFormat( GetVisual().visual ); + return m_pXRenderFormat; +} + +SystemGraphicsData X11SalGraphics::GetGraphicsData() const +{ + SystemGraphicsData aRes; + + aRes.nSize = sizeof(aRes); + aRes.pDisplay = GetXDisplay(); + aRes.hDrawable = maX11Common.m_hDrawable; + aRes.pVisual = GetVisual().visual; + aRes.nScreen = m_nXScreen.getXScreen(); + aRes.pXRenderFormat = m_pXRenderFormat; + return aRes; +} + +void X11SalGraphics::Flush() +{ + if( X11GraphicsImpl* x11Impl = dynamic_cast< X11GraphicsImpl* >( mxImpl.get())) + x11Impl->Flush(); +} + +#if ENABLE_CAIRO_CANVAS + +bool X11SalGraphics::SupportsCairo() const +{ + return maX11Common.SupportsCairo(); +} + +cairo::SurfaceSharedPtr X11SalGraphics::CreateSurface(const cairo::CairoSurfaceSharedPtr& rSurface) const +{ + return std::make_shared(rSurface); +} + +namespace +{ + cairo::X11SysData getSysData( const vcl::Window& rWindow ) + { + const SystemEnvData* pSysData = rWindow.GetSystemData(); + + if( !pSysData ) + return cairo::X11SysData(); + else + return cairo::X11SysData(*pSysData, rWindow.ImplGetFrame()); + } + + cairo::X11SysData getSysData( const VirtualDevice& rVirDev ) + { + return cairo::X11SysData( rVirDev.GetSystemGfxData() ); + } +} + +cairo::SurfaceSharedPtr X11SalGraphics::CreateSurface( const OutputDevice& rRefDevice, + int x, int y, int width, int height ) const +{ + if( rRefDevice.GetOutDevType() == OUTDEV_WINDOW ) + return std::make_shared(getSysData(*rRefDevice.GetOwnerWindow()), + x,y,width,height); + if( rRefDevice.IsVirtual() ) + return std::make_shared(getSysData(static_cast(rRefDevice)), + x,y,width,height); + return cairo::SurfaceSharedPtr(); +} + +cairo::SurfaceSharedPtr X11SalGraphics::CreateBitmapSurface( const OutputDevice& rRefDevice, + const BitmapSystemData& rData, + const Size& rSize ) const +{ + SAL_INFO("vcl", "requested size: " << rSize.Width() << " x " << rSize.Height() + << " available size: " << rData.mnWidth << " x " + << rData.mnHeight); + if ( rData.mnWidth == rSize.Width() && rData.mnHeight == rSize.Height() ) + { + if( rRefDevice.GetOutDevType() == OUTDEV_WINDOW ) + return std::make_shared(getSysData(*rRefDevice.GetOwnerWindow()), rData ); + else if( rRefDevice.IsVirtual() ) + return std::make_shared(getSysData(static_cast(rRefDevice)), rData ); + } + + return cairo::SurfaceSharedPtr(); +} + +css::uno::Any X11SalGraphics::GetNativeSurfaceHandle(cairo::SurfaceSharedPtr& rSurface, const basegfx::B2ISize& /*rSize*/) const +{ + cairo::X11Surface& rXlibSurface=dynamic_cast(*rSurface); + css::uno::Sequence< css::uno::Any > args{ + css::uno::Any(false), // do not call XFreePixmap on it + css::uno::Any(sal_Int64(rXlibSurface.getPixmap()->mhDrawable)), + css::uno::Any(sal_Int32( rXlibSurface.getDepth() )) + }; + return css::uno::Any(args); +} + +#endif // ENABLE_CAIRO_CANVAS + +SalGeometryProvider *X11SalGraphics::GetGeometryProvider() const +{ + if (m_pFrame) + return static_cast< SalGeometryProvider * >(m_pFrame); + else + return static_cast< SalGeometryProvider * >(m_pVDev); +} + +cairo_t* X11SalGraphics::getCairoContext() +{ + return maX11Common.getCairoContext(); +} + +void X11SalGraphics::releaseCairoContext(cairo_t* cr) +{ + X11Common::releaseCairoContext(cr); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/unx/generic/gdi/salgdi2.cxx b/vcl/unx/generic/gdi/salgdi2.cxx new file mode 100644 index 000000000..f26048ae1 --- /dev/null +++ b/vcl/unx/generic/gdi/salgdi2.cxx @@ -0,0 +1,89 @@ +/* -*- 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 + +#include + +#include +#include +#include +#include + +extern "C" +{ + static Bool GraphicsExposePredicate( Display*, XEvent* pEvent, const XPointer pFrameWindow ) + { + Bool bRet = False; + if( (pEvent->type == GraphicsExpose || pEvent->type == NoExpose) && + pEvent->xnoexpose.drawable == reinterpret_cast(pFrameWindow) ) + { + bRet = True; + } + return bRet; + } +} + +void X11SalGraphics::YieldGraphicsExpose() +{ + // get frame if necessary + SalFrame* pFrame = m_pFrame; + Display* pDisplay = GetXDisplay(); + ::Window aWindow = GetDrawable(); + if( ! pFrame ) + { + for (auto pSalFrame : vcl_sal::getSalDisplay(GetGenericUnixSalData())->getFrames() ) + { + const SystemEnvData* pEnvData = pSalFrame->GetSystemData(); + if( Drawable(pEnvData->GetWindowHandle(pSalFrame)) == aWindow ) + { + pFrame = pSalFrame; + break; + } + } + if( ! pFrame ) + return; + } + + XEvent aEvent; + while( XCheckTypedWindowEvent( pDisplay, aWindow, Expose, &aEvent ) ) + { + SalPaintEvent aPEvt( aEvent.xexpose.x, aEvent.xexpose.y, aEvent.xexpose.width+1, aEvent.xexpose.height+1 ); + pFrame->CallCallback( SalEvent::Paint, &aPEvt ); + } + + do + { + if( ! GetDisplay()->XIfEventWithTimeout( &aEvent, reinterpret_cast(aWindow), GraphicsExposePredicate ) ) + // this should not happen at all; still sometimes it happens + break; + + if( aEvent.type == NoExpose ) + break; + + if( pFrame ) + { + SalPaintEvent aPEvt( aEvent.xgraphicsexpose.x, aEvent.xgraphicsexpose.y, aEvent.xgraphicsexpose.width+1, aEvent.xgraphicsexpose.height+1 ); + pFrame->CallCallback( SalEvent::Paint, &aPEvt ); + } + } while( aEvent.xgraphicsexpose.count != 0 ); +} + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/unx/generic/gdi/salvd.cxx b/vcl/unx/generic/gdi/salvd.cxx new file mode 100644 index 000000000..f5e4449c6 --- /dev/null +++ b/vcl/unx/generic/gdi/salvd.cxx @@ -0,0 +1,222 @@ +/* -*- 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 + +#include +#include + +#include +#include +#include +#include +#include + +#include +#include +#if HAVE_FEATURE_SKIA +#include +#endif + +std::unique_ptr X11SalInstance::CreateX11VirtualDevice(const SalGraphics& rGraphics, + tools::Long &nDX, tools::Long &nDY, DeviceFormat eFormat, const SystemGraphicsData *pData, + std::unique_ptr pNewGraphics) +{ + assert(pNewGraphics); +#if HAVE_FEATURE_SKIA + if (SkiaHelper::isVCLSkiaEnabled()) + return std::unique_ptr(new X11SkiaSalVirtualDevice(rGraphics, nDX, nDY, pData, std::move(pNewGraphics))); + else +#endif + return std::unique_ptr(new X11SalVirtualDevice(rGraphics, nDX, nDY, eFormat, pData, std::move(pNewGraphics))); +} + +std::unique_ptr X11SalInstance::CreateVirtualDevice(SalGraphics& rGraphics, + tools::Long &nDX, tools::Long &nDY, DeviceFormat eFormat, const SystemGraphicsData *pData) +{ + return CreateX11VirtualDevice(rGraphics, nDX, nDY, eFormat, pData, std::make_unique()); +} + +void X11SalGraphics::Init( X11SalVirtualDevice *pDevice, cairo_surface_t* pPreExistingTarget, SalColormap* pColormap, + bool bDeleteColormap ) +{ + SalDisplay *pDisplay = pDevice->GetDisplay(); + m_nXScreen = pDevice->GetXScreenNumber(); + + int nVisualDepth = pDisplay->GetColormap( m_nXScreen ).GetVisual().GetDepth(); + int nDeviceDepth = pDevice->GetDepth(); + + if( pColormap ) + { + maX11Common.m_pColormap = pColormap; + if( bDeleteColormap ) + m_pDeleteColormap.reset(pColormap); + } + else if( nDeviceDepth == nVisualDepth ) + maX11Common.m_pColormap = &pDisplay->GetColormap( m_nXScreen ); + else if( nDeviceDepth == 1 ) + { + m_pDeleteColormap.reset(new SalColormap()); + maX11Common.m_pColormap = m_pDeleteColormap.get(); + } + + m_pVDev = pDevice; + m_pFrame = nullptr; + + bWindow_ = pDisplay->IsDisplay(); + bVirDev_ = true; + + SetDrawable(pDevice->GetDrawable(), pPreExistingTarget, m_nXScreen); + mxImpl->Init(); +} + +X11SalVirtualDevice::X11SalVirtualDevice(const SalGraphics& rGraphics, tools::Long &nDX, tools::Long &nDY, + DeviceFormat /*eFormat*/, const SystemGraphicsData *pData, + std::unique_ptr pNewGraphics) : + pGraphics_(std::move(pNewGraphics)), + m_nXScreen(0), + bGraphics_(false) +{ + SalColormap* pColormap = nullptr; + bool bDeleteColormap = false; + + sal_uInt16 nBitCount = rGraphics.GetBitCount(); + pDisplay_ = vcl_sal::getSalDisplay(GetGenericUnixSalData()); + nDepth_ = nBitCount; + + if( pData && pData->hDrawable != None ) + { + ::Window aRoot; + int x, y; + unsigned int w = 0, h = 0, bw, d; + Display* pDisp = pDisplay_->GetDisplay(); + XGetGeometry( pDisp, pData->hDrawable, + &aRoot, &x, &y, &w, &h, &bw, &d ); + int nScreen = 0; + while( nScreen < ScreenCount( pDisp ) ) + { + if( RootWindow( pDisp, nScreen ) == aRoot ) + break; + nScreen++; + } + nDX_ = static_cast(w); + nDY_ = static_cast(h); + nDX = nDX_; + nDY = nDY_; + m_nXScreen = SalX11Screen( nScreen ); + hDrawable_ = pData->hDrawable; + bExternPixmap_ = true; + } + else + { + nDX_ = nDX; + nDY_ = nDY; + m_nXScreen = static_cast(rGraphics).GetScreenNumber(); + hDrawable_ = limitXCreatePixmap( GetXDisplay(), + pDisplay_->GetDrawable( m_nXScreen ), + nDX_, nDY_, + GetDepth() ); + bExternPixmap_ = false; + } + + XRenderPictFormat* pXRenderFormat = pData ? static_cast(pData->pXRenderFormat) : nullptr; + if( pXRenderFormat ) + { + pGraphics_->SetXRenderFormat( pXRenderFormat ); + if( pXRenderFormat->colormap ) + pColormap = new SalColormap( pDisplay_, pXRenderFormat->colormap, m_nXScreen ); + else + pColormap = new SalColormap( nBitCount ); + bDeleteColormap = true; + } + else if( nBitCount != pDisplay_->GetVisual( m_nXScreen ).GetDepth() ) + { + pColormap = new SalColormap( nBitCount ); + bDeleteColormap = true; + } + + pGraphics_->SetLayout( SalLayoutFlags::NONE ); // by default no! mirroring for VirtualDevices, can be enabled with EnableRTL() + + // tdf#127529 see SvpSalInstance::CreateVirtualDevice for the rare case of a non-null pPreExistingTarget + cairo_surface_t* pPreExistingTarget = pData ? static_cast(pData->pSurface) : nullptr; + + pGraphics_->Init( this, pPreExistingTarget, pColormap, bDeleteColormap ); +} + +X11SalVirtualDevice::~X11SalVirtualDevice() +{ + pGraphics_.reset(); + + if( GetDrawable() && !bExternPixmap_ ) + XFreePixmap( GetXDisplay(), GetDrawable() ); +} + +SalGraphics* X11SalVirtualDevice::AcquireGraphics() +{ + if( bGraphics_ ) + return nullptr; + + if( pGraphics_ ) + bGraphics_ = true; + + return pGraphics_.get(); +} + +void X11SalVirtualDevice::ReleaseGraphics( SalGraphics* ) +{ bGraphics_ = false; } + +bool X11SalVirtualDevice::SetSize( tools::Long nDX, tools::Long nDY ) +{ + if( bExternPixmap_ ) + return false; + + if( !nDX ) nDX = 1; + if( !nDY ) nDY = 1; + + Pixmap h = limitXCreatePixmap( GetXDisplay(), + pDisplay_->GetDrawable( m_nXScreen ), + nDX, nDY, nDepth_ ); + + if( !h ) + { + if( !GetDrawable() ) + { + hDrawable_ = limitXCreatePixmap( GetXDisplay(), + pDisplay_->GetDrawable( m_nXScreen ), + 1, 1, nDepth_ ); + nDX_ = 1; + nDY_ = 1; + } + return false; + } + + if( GetDrawable() ) + XFreePixmap( GetXDisplay(), GetDrawable() ); + hDrawable_ = h; + + nDX_ = nDX; + nDY_ = nDY; + + if( pGraphics_ ) + pGraphics_->Init( this ); + + return true; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/unx/generic/gdi/x11cairotextrender.cxx b/vcl/unx/generic/gdi/x11cairotextrender.cxx new file mode 100644 index 000000000..6bbbbc1bf --- /dev/null +++ b/vcl/unx/generic/gdi/x11cairotextrender.cxx @@ -0,0 +1,66 @@ +/* -*- 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 + +#include +#include +#include +#include +#include + +X11CairoTextRender::X11CairoTextRender(X11SalGraphics& rParent) + : mrParent(rParent) +{ +} + +cairo_t* X11CairoTextRender::getCairoContext() +{ + return mrParent.getCairoContext(); +} + +void X11CairoTextRender::getSurfaceOffset( double& nDX, double& nDY ) +{ + nDX = 0; + nDY = 0; +} + +void X11CairoTextRender::clipRegion(cairo_t* cr) +{ + Region pClipRegion = mrParent.mpClipRegion; + if( pClipRegion && !XEmptyRegion( pClipRegion ) ) + { + for (tools::Long i = 0; i < pClipRegion->numRects; ++i) + { + cairo_rectangle(cr, + pClipRegion->rects[i].x1, + pClipRegion->rects[i].y1, + pClipRegion->rects[i].x2 - pClipRegion->rects[i].x1, + pClipRegion->rects[i].y2 - pClipRegion->rects[i].y1); + } + cairo_clip(cr); + } +} + +void X11CairoTextRender::releaseCairoContext(cairo_t* cr) +{ + X11SalGraphics::releaseCairoContext(cr); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/unx/generic/gdi/xrender_peer.cxx b/vcl/unx/generic/gdi/xrender_peer.cxx new file mode 100644 index 000000000..961f4cd3a --- /dev/null +++ b/vcl/unx/generic/gdi/xrender_peer.cxx @@ -0,0 +1,48 @@ +/* -*- 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 + +#include + +XRenderPeer::XRenderPeer() + : mpDisplay( vcl_sal::getSalDisplay(GetGenericUnixSalData())->GetDisplay() ) + , mpStandardFormatA8( nullptr ) +{ + InitRenderLib(); +} + +XRenderPeer& XRenderPeer::GetInstance() +{ + static XRenderPeer aPeer; + return aPeer; +} + +void XRenderPeer::InitRenderLib() +{ + int nDummy; + // needed to initialize libXrender internals + XRenderQueryExtension( mpDisplay, &nDummy, &nDummy ); + + // the 8bit alpha mask format must be there + XRenderPictFormat aPictFormat={0,0,8,{0,0,0,0,0,0,0,0xFF},0}; + mpStandardFormatA8 = XRenderFindFormat( mpDisplay, PictFormatAlphaMask|PictFormatDepth, &aPictFormat, 0 ); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ -- cgit v1.2.3