drawinglayer/source/processor2d/cairopixelprocessor2d.cxx  |  489 +++++++++----
 include/drawinglayer/processor2d/cairopixelprocessor2d.hxx |   12 
 2 files changed, 359 insertions(+), 142 deletions(-)

New commits:
commit 6c61231e8c4b0a47fd626745b27bfd42777b90db
Author:     Armin Le Grand (Collabora) <armin.le.gr...@me.com>
AuthorDate: Tue Jan 28 21:02:25 2025 +0100
Commit:     Xisco Fauli <xiscofa...@libreoffice.org>
CommitDate: Fri Feb 14 09:49:24 2025 +0100

    tdf#164403 CairoSDPR: adapt hairline width
    
    If Cairo Coordinate Limit Workaround is Active do
    also adapt hairline width to 1.0 since we (have to)
    fallback to render in view coordinates.
    
    Change-Id: I2216c1728355b5e76ee04f699193a896f9c4fd4b
    Reviewed-on: https://gerrit.libreoffice.org/c/core/+/180860
    Tested-by: Jenkins
    Reviewed-by: Armin Le Grand <armin.le.gr...@me.com>
    Signed-off-by: Xisco Fauli <xiscofa...@libreoffice.org>
    Reviewed-on: https://gerrit.libreoffice.org/c/core/+/181550

diff --git a/drawinglayer/source/processor2d/cairopixelprocessor2d.cxx 
b/drawinglayer/source/processor2d/cairopixelprocessor2d.cxx
index a7ae62e85911..36bb4bb1b3b9 100644
--- a/drawinglayer/source/processor2d/cairopixelprocessor2d.cxx
+++ b/drawinglayer/source/processor2d/cairopixelprocessor2d.cxx
@@ -65,7 +65,8 @@ using namespace com::sun::star;
 namespace
 {
 void impl_cairo_set_hairline(cairo_t* pRT,
-                             const drawinglayer::geometry::ViewInformation2D& 
rViewInformation)
+                             const drawinglayer::geometry::ViewInformation2D& 
rViewInformation,
+                             bool bCairoCoordinateLimitWorkaroundActive)
 {
 #if CAIRO_VERSION >= CAIRO_VERSION_ENCODE(1, 18, 0)
     void* addr(dlsym(nullptr, "cairo_set_hairline"));
@@ -75,11 +76,19 @@ void impl_cairo_set_hairline(cairo_t* pRT,
         return;
     }
 #endif
-    // avoid cairo_device_to_user_distance, see note on that below
-    const double fPx(
-        (rViewInformation.getInverseObjectToViewTransformation() * 
basegfx::B2DVector(1.0, 0.0))
-            .getLength());
-    cairo_set_line_width(pRT, fPx);
+    if (bCairoCoordinateLimitWorkaroundActive)
+    {
+        // we have to render in view coordiantes, set line width to 1.0
+        cairo_set_line_width(pRT, 1.0);
+    }
+    else
+    {
+        // avoid cairo_device_to_user_distance, see note on that below
+        const double fPx(
+            (rViewInformation.getInverseObjectToViewTransformation() * 
basegfx::B2DVector(1.0, 0.0))
+                .getLength());
+        cairo_set_line_width(pRT, fPx);
+    }
 }
 
 void addB2DPolygonToPathGeometry(cairo_t* pRT, const basegfx::B2DPolygon& 
rPolygon)
@@ -1269,7 +1278,8 @@ void CairoPixelProcessor2D::paintBitmapAlpha(const 
BitmapEx& rBitmapEx,
     if (bRenderTransformationBounds)
     {
         cairo_set_source_rgba(mpRT, 1, 0, 0, 0.8);
-        impl_cairo_set_hairline(mpRT, getViewInformation2D());
+        impl_cairo_set_hairline(mpRT, getViewInformation2D(),
+                                isCairoCoordinateLimitWorkaroundActive());
         cairo_rectangle(mpRT, 0, 0, 1, 1);
         cairo_stroke(mpRT);
     }
@@ -1404,7 +1414,7 @@ void 
CairoPixelProcessor2D::processPolygonHairlinePrimitive2D(
                          aHairlineColor.getBlue());
 
     // set LineWidth, use Cairo's special cairo_set_hairline
-    impl_cairo_set_hairline(mpRT, getViewInformation2D());
+    impl_cairo_set_hairline(mpRT, getViewInformation2D(), 
isCairoCoordinateLimitWorkaroundActive());
 
     if (isCairoCoordinateLimitWorkaroundActive())
     {
@@ -2429,7 +2439,8 @@ void CairoPixelProcessor2D::processFillGraphicPrimitive2D(
     if (bRenderTransformationBounds)
     {
         cairo_set_source_rgba(mpRT, 0, 1, 0, 0.8);
-        impl_cairo_set_hairline(mpRT, getViewInformation2D());
+        impl_cairo_set_hairline(mpRT, getViewInformation2D(),
+                                isCairoCoordinateLimitWorkaroundActive());
         // full object
         cairo_rectangle(mpRT, 0, 0, 1, 1);
         // outline of pattern root image
commit b2aa20a855b4cdd2ff32752c71bd6dd412ddea0b
Author:     Armin Le Grand (Collabora) <armin.le.gr...@me.com>
AuthorDate: Mon Jan 20 19:59:07 2025 +0100
Commit:     Xisco Fauli <xiscofa...@libreoffice.org>
CommitDate: Fri Feb 14 09:49:17 2025 +0100

    tdf#164403 CairoSDPR: Solve Cairo's 24.8 coordinate problem
    
    Thus Cairo is offering a full-fledged double precision API but
    internally uses a 24.8 fixed-float format (see https://gitlab.
    freedesktop.org/cairo/cairo/-/issues/252, thanks Caolan for
    finding that). This seems to be the case for linux and windows
    implementations. Due to the API being on double precision this
    is an implementation detail, but it has to be taken into
    account when using cairo. It is not officially documented what
    is a shame - it should be somewhere in the readmes/
    documentation at a prominent place.
    
    24.8 means coordinates are limited to +/- 2^23 (usually minus
    one) and [0/256 .. 255/256] places after comma. This is
    unfortunately not limited to the view coordinates - that
    would be acceptable, discrete/pixel areas of that size should
    be fine, BUT used with transformations. Thus when having any
    coordinates beyond that limit - and with double precision and
    transformations we have that here - cairo just 'cuts' the
    coordinates to it's min/max 24.8 format. This is not acceptable
    for e.g. a cad system, and even leads to problems in something
    like an office suite as we see. It implies that using cairo in
    a safe way means to basically use linear algebra yourself in
    your app and use cairo only without transformations and with
    discrete view coordinates (pixels). AND to know about that
    limitation in the 1st place ...
    
    Well, cairo works well and that problem happens until now
    in one place in writer below page 490. Let's be pragmatic.
    Key is to *detect* the situation. Always doing own
    transformations is possible, but not nice: I *could* always
    transform the polygon to screen coordinates, but that will
    kill the PolyPolygon buffering mechanism (in chart we have
    PolyPolygons with 80.000 entries :-); These are held in
    cairo_path_t form at the original PolyPolygon to not need
    to transform them every time, but let Cairo do that. The
    idea is that when Cairo impl may use HW on the system to
    do that (HW can do that nowadays) while rendering the
    PolyPolygon from the object model would have to be
    converted just once to Cairo form and used multiple times...
    
    Cairo *should* be fair, document this and allow you to test/
    ask if this limit exists in the version you are using, but
    it does not. Thus I suppose to detect that situation as
    'cheap' as possible and react to it - e.g. in writer when
    below page 490. But not only - it might be possibe to
    construct draw/impress pages (free definable) that also reach
    that limit.
    
    With detecting as cheap as possible I mean: Naturally you
    would start to test your geometry to be painted if it is
    overlapping or outside that range defined by 24.8, but
    that would already involve transforming all geometry by
    world and view transformations. I think it should be okay
    to just take the target area (starting at 0,0 and having a
    size in pixels) and transforming that back to logical world
    coordinates. Then this needs to be checked only if view
    transformation changes, not object transformation. Usually
    view transformation is setup and all/many objects are
    painted to it using object transformations. Detection
    should then only be needed when view transform changes.
    
    Thus I added a test (see
    checkCoordinateLimitWorkaroundNeededForUsedCairo()). This
    is esecuted once per office runtime. If it detects that
    the problem is there it sets a flag. Depending on this
    the view coordinates are then transformed to logic
    coordinates and checked if these are completely inside
    the coordinate area that ciaro *can* correctly handle.
    This has to be done once per CariroSDPR since (currently)
    ViewTransformation is not changed (has to be adapted if
    this should happen one day). Based on that the single
    helpers on the renderer have to be adapted: Simplest
    is just to render using ViewCoordinates - and do
    transform object geometry using basegfx/our own stuff
    that does that correctly (with ease :-).
    
    Done that, checked, works AFAICT. What a bad surprise,
    but I can work around it...
    
    Change-Id: Ic33d6ccfe9854af396d0ac9f14c76eb25bb9e4e2
    Reviewed-on: https://gerrit.libreoffice.org/c/core/+/180529
    Reviewed-by: Armin Le Grand <armin.le.gr...@me.com>
    Tested-by: Jenkins
    Signed-off-by: Xisco Fauli <xiscofa...@libreoffice.org>
    Reviewed-on: https://gerrit.libreoffice.org/c/core/+/181549

diff --git a/drawinglayer/source/processor2d/cairopixelprocessor2d.cxx 
b/drawinglayer/source/processor2d/cairopixelprocessor2d.cxx
index 588afe4b158f..a7ae62e85911 100644
--- a/drawinglayer/source/processor2d/cairopixelprocessor2d.cxx
+++ b/drawinglayer/source/processor2d/cairopixelprocessor2d.cxx
@@ -860,6 +860,71 @@ basegfx::B2DRange getDiscreteViewRange(cairo_t* pRT)
     return basegfx::B2DRange(basegfx::B2DPoint(clip_x1, clip_y1),
                              basegfx::B2DPoint(clip_x2, clip_y2));
 }
+
+bool checkCoordinateLimitWorkaroundNeededForUsedCairo()
+{
+    // setup surface and render context
+    cairo_surface_t* pSurface(cairo_image_surface_create(CAIRO_FORMAT_RGB24, 
8, 8));
+    if (!pSurface)
+        // got no surface -> be pessimistic
+        return true;
+
+    cairo_t* pRender(cairo_create(pSurface));
+    if (!pRender)
+    {
+        // got no render -> be pessimistic
+        cairo_surface_destroy(pSurface);
+        return true;
+    }
+
+    // set basic values
+    cairo_set_antialias(pRender, CAIRO_ANTIALIAS_NONE);
+    cairo_set_fill_rule(pRender, CAIRO_FILL_RULE_EVEN_ODD);
+    cairo_set_operator(pRender, CAIRO_OPERATOR_OVER);
+    cairo_set_source_rgb(pRender, 1.0, 0.0, 0.0);
+
+    // create a to-be rendered area centered at the fNumCairoMax
+    // spot and 8x8 discrete units in size
+    constexpr double fNumCairoMax(1 << 23);
+    const basegfx::B2DPoint aCenter(fNumCairoMax, fNumCairoMax);
+    const basegfx::B2DPoint aOffset(4, 4);
+    const basegfx::B2DRange aObject(aCenter - aOffset, aCenter + aOffset);
+
+    // create transformation to render that to an aerea with
+    // range(0, 0, 8, 8) and set as transformation
+    const basegfx::B2DHomMatrix 
aObjectToView(basegfx::utils::createSourceRangeTargetRangeTransform(
+        aObject, basegfx::B2DRange(0, 0, 8, 8)));
+    cairo_matrix_t aMatrix;
+    cairo_matrix_init(&aMatrix, aObjectToView.a(), aObjectToView.b(), 
aObjectToView.c(),
+                      aObjectToView.d(), aObjectToView.e(), aObjectToView.f());
+    cairo_set_matrix(pRender, &aMatrix);
+
+    // get/create the path for an object exactly filling that area
+    cairo_new_path(pRender);
+    basegfx::B2DPolyPolygon 
aObjectPolygon(basegfx::utils::createPolygonFromRect(aObject));
+    CairoPathHelper aPathHelper(aObjectPolygon);
+    cairo_append_path(pRender, aPathHelper.getCairoPath());
+
+    // render it and flush since we want to immediately inspect result
+    cairo_fill(pRender);
+    cairo_surface_flush(pSurface);
+
+    // get access to pixel data
+    const sal_uInt32 nStride(cairo_image_surface_get_stride(pSurface));
+    sal_uInt8* pStartPixelData(cairo_image_surface_get_data(pSurface));
+
+    // extract red value for pixels at (1,1) and (7,7)
+    sal_uInt8 aRedAt_1_1((pStartPixelData + (nStride * 1) + 1)[SVP_CAIRO_RED]);
+    sal_uInt8 aRedAt_6_6((pStartPixelData + (nStride * 6) + 6)[SVP_CAIRO_RED]);
+
+    // cleanup
+    cairo_destroy(pRender);
+    cairo_surface_destroy(pSurface);
+
+    // if cairo works or has no 24.8 internal format all pixels
+    // have to be red (255), thus workaround is needed if !=
+    return aRedAt_1_1 != aRedAt_6_6;
+}
 }
 
 namespace drawinglayer::processor2d
@@ -876,6 +941,7 @@ CairoPixelProcessor2D::CairoPixelProcessor2D(const 
geometry::ViewInformation2D&
     , mbRenderDecoratedTextDirect(
           
officecfg::Office::Common::Drawinglayer::RenderDecoratedTextDirect::get())
     , mnClipRecursionCount(0)
+    , mbCairoCoordinateLimitWorkaroundActive(false)
 {
     if (nWidthPixel <= 0 || nHeightPixel <= 0)
         // no size, invalid
@@ -900,6 +966,9 @@ CairoPixelProcessor2D::CairoPixelProcessor2D(const 
geometry::ViewInformation2D&
                                                                     : 
CAIRO_ANTIALIAS_NONE);
     cairo_set_fill_rule(mpRT, CAIRO_FILL_RULE_EVEN_ODD);
     cairo_set_operator(mpRT, CAIRO_OPERATOR_OVER);
+
+    // evaluate if CairoCoordinateLimitWorkaround is needed
+    evaluateCairoCoordinateLimitWorkaround();
 }
 
 CairoPixelProcessor2D::CairoPixelProcessor2D(const 
geometry::ViewInformation2D& rViewInformation,
@@ -915,59 +984,63 @@ CairoPixelProcessor2D::CairoPixelProcessor2D(const 
geometry::ViewInformation2D&
     , mbRenderDecoratedTextDirect(
           
officecfg::Office::Common::Drawinglayer::RenderDecoratedTextDirect::get())
     , mnClipRecursionCount(0)
+    , mbCairoCoordinateLimitWorkaroundActive(false)
 {
-    if (pTarget)
-    {
-        bool bClipNeeded(false);
+    // no target, nothing to initialize
+    if (nullptr == pTarget)
+        return;
 
-        if (0 != nOffsetPixelX || 0 != nOffsetPixelY || 0 != nWidthPixel || 0 
!= nHeightPixel)
+    bool bClipNeeded(false);
+
+    if (0 != nOffsetPixelX || 0 != nOffsetPixelY || 0 != nWidthPixel || 0 != 
nHeightPixel)
+    {
+        if (0 != nOffsetPixelX || 0 != nOffsetPixelY)
         {
-            if (0 != nOffsetPixelX || 0 != nOffsetPixelY)
+            // if offset is used we need initial clip
+            bClipNeeded = true;
+        }
+        else
+        {
+            // no offset used, compare to real pixel size
+            const tools::Long 
nRealPixelWidth(cairo_image_surface_get_width(pTarget));
+            const tools::Long 
nRealPixelHeight(cairo_image_surface_get_height(pTarget));
+
+            if (nRealPixelWidth != nWidthPixel || nRealPixelHeight != 
nHeightPixel)
             {
-                // if offset is used we need initial clip
+                // if size differs we need initial clip
                 bClipNeeded = true;
             }
-            else
-            {
-                // no offset used, compare to real pixel size
-                const tools::Long 
nRealPixelWidth(cairo_image_surface_get_width(pTarget));
-                const tools::Long 
nRealPixelHeight(cairo_image_surface_get_height(pTarget));
-
-                if (nRealPixelWidth != nWidthPixel || nRealPixelHeight != 
nHeightPixel)
-                {
-                    // if size differs we need initial clip
-                    bClipNeeded = true;
-                }
-            }
         }
+    }
 
-        if (bClipNeeded)
-        {
-            // optional: if the possibility to add an initial clip relative
-            // to the real pixel dimensions of the target surface is used,
-            // apply it here using that nice existing method of cairo
-            mpOwnedSurface = cairo_surface_create_for_rectangle(
-                pTarget, nOffsetPixelX, nOffsetPixelY, nWidthPixel, 
nHeightPixel);
+    if (bClipNeeded)
+    {
+        // optional: if the possibility to add an initial clip relative
+        // to the real pixel dimensions of the target surface is used,
+        // apply it here using that nice existing method of cairo
+        mpOwnedSurface = cairo_surface_create_for_rectangle(pTarget, 
nOffsetPixelX, nOffsetPixelY,
+                                                            nWidthPixel, 
nHeightPixel);
 
-            if (nullptr != mpOwnedSurface)
-                mpRT = cairo_create(mpOwnedSurface);
-        }
-        else
-        {
-            // create RenderTarget for full target
-            mpRT = cairo_create(pTarget);
-        }
+        if (nullptr != mpOwnedSurface)
+            mpRT = cairo_create(mpOwnedSurface);
+    }
+    else
+    {
+        // create RenderTarget for full target
+        mpRT = cairo_create(pTarget);
+    }
 
-        if (nullptr != mpRT)
-        {
-            // initialize some basic used values/settings
-            cairo_set_antialias(mpRT, rViewInformation.getUseAntiAliasing()
-                                          ? CAIRO_ANTIALIAS_DEFAULT
-                                          : CAIRO_ANTIALIAS_NONE);
-            cairo_set_fill_rule(mpRT, CAIRO_FILL_RULE_EVEN_ODD);
-            cairo_set_operator(mpRT, CAIRO_OPERATOR_OVER);
-        }
+    if (nullptr != mpRT)
+    {
+        // initialize some basic used values/settings
+        cairo_set_antialias(mpRT, rViewInformation.getUseAntiAliasing() ? 
CAIRO_ANTIALIAS_DEFAULT
+                                                                        : 
CAIRO_ANTIALIAS_NONE);
+        cairo_set_fill_rule(mpRT, CAIRO_FILL_RULE_EVEN_ODD);
+        cairo_set_operator(mpRT, CAIRO_OPERATOR_OVER);
     }
+
+    // evaluate if CairoCoordinateLimitWorkaround is needed
+    evaluateCairoCoordinateLimitWorkaround();
 }
 
 CairoPixelProcessor2D::~CairoPixelProcessor2D()
@@ -1324,16 +1397,6 @@ void 
CairoPixelProcessor2D::processPolygonHairlinePrimitive2D(
 
     cairo_save(mpRT);
 
-    // set linear transformation
-    cairo_matrix_t aMatrix;
-    const double fAAOffset(getViewInformation2D().getUseAntiAliasing() ? 0.5 : 
0.0);
-    const basegfx::B2DHomMatrix& rObjectToView(
-        getViewInformation2D().getObjectToViewTransformation());
-    cairo_matrix_init(&aMatrix, rObjectToView.a(), rObjectToView.b(), 
rObjectToView.c(),
-                      rObjectToView.d(), rObjectToView.e() + fAAOffset,
-                      rObjectToView.f() + fAAOffset);
-    cairo_set_matrix(mpRT, &aMatrix);
-
     // determine & set color
     const basegfx::BColor aHairlineColor(
         
maBColorModifierStack.getModifiedColor(rPolygonHairlinePrimitive2D.getBColor()));
@@ -1343,11 +1406,40 @@ void 
CairoPixelProcessor2D::processPolygonHairlinePrimitive2D(
     // set LineWidth, use Cairo's special cairo_set_hairline
     impl_cairo_set_hairline(mpRT, getViewInformation2D());
 
-    // get PathGeometry & paint it
-    cairo_new_path(mpRT);
-    getOrCreatePathGeometry(mpRT, rPolygon, getViewInformation2D(),
-                            getViewInformation2D().getUseAntiAliasing());
-    cairo_stroke(mpRT);
+    if (isCairoCoordinateLimitWorkaroundActive())
+    {
+        // need to fallback to paint in view coordinates, unfortunately
+        // need to transform self (cairo will do it wrong in this coordinate
+        // space), so no need to try to buffer
+        cairo_new_path(mpRT);
+        basegfx::B2DPolygon aAdaptedPolygon(rPolygon);
+        const double fAAOffset(getViewInformation2D().getUseAntiAliasing() ? 
0.5 : 0.0);
+        
aAdaptedPolygon.transform(basegfx::utils::createTranslateB2DHomMatrix(fAAOffset,
 fAAOffset)
+                                  * 
getViewInformation2D().getObjectToViewTransformation());
+        cairo_identity_matrix(mpRT);
+        addB2DPolygonToPathGeometry(mpRT, aAdaptedPolygon);
+        cairo_stroke(mpRT);
+    }
+    else
+    {
+        // set linear transformation. use own, prepared, re-usable
+        // ObjectToViewTransformation and PolyPoylgon data and let
+        // cairo do the transformations
+        cairo_matrix_t aMatrix;
+        const basegfx::B2DHomMatrix& rObjectToView(
+            getViewInformation2D().getObjectToViewTransformation());
+        const double fAAOffset(getViewInformation2D().getUseAntiAliasing() ? 
0.5 : 0.0);
+        cairo_matrix_init(&aMatrix, rObjectToView.a(), rObjectToView.b(), 
rObjectToView.c(),
+                          rObjectToView.d(), rObjectToView.e() + fAAOffset,
+                          rObjectToView.f() + fAAOffset);
+        cairo_set_matrix(mpRT, &aMatrix);
+
+        // get PathGeometry & paint it
+        cairo_new_path(mpRT);
+        getOrCreatePathGeometry(mpRT, rPolygon, getViewInformation2D(),
+                                getViewInformation2D().getUseAntiAliasing());
+        cairo_stroke(mpRT);
+    }
 
     cairo_restore(mpRT);
 }
@@ -1379,14 +1471,6 @@ void CairoPixelProcessor2D::paintPolyPoylgonRGBA(const 
basegfx::B2DPolyPolygon&
 
     cairo_save(mpRT);
 
-    // set linear transformation
-    cairo_matrix_t aMatrix;
-    const basegfx::B2DHomMatrix& rObjectToView(
-        getViewInformation2D().getObjectToViewTransformation());
-    cairo_matrix_init(&aMatrix, rObjectToView.a(), rObjectToView.b(), 
rObjectToView.c(),
-                      rObjectToView.d(), rObjectToView.e(), rObjectToView.f());
-    cairo_set_matrix(mpRT, &aMatrix);
-
     // determine & set color
     const basegfx::BColor 
aFillColor(maBColorModifierStack.getModifiedColor(rColor));
 
@@ -1397,10 +1481,36 @@ void CairoPixelProcessor2D::paintPolyPoylgonRGBA(const 
basegfx::B2DPolyPolygon&
         cairo_set_source_rgb(mpRT, aFillColor.getRed(), aFillColor.getGreen(),
                              aFillColor.getBlue());
 
-    // get PathGeometry & paint it
-    cairo_new_path(mpRT);
-    getOrCreateFillGeometry(mpRT, rPolyPolygon);
-    cairo_fill(mpRT);
+    if (isCairoCoordinateLimitWorkaroundActive())
+    {
+        // need to fallback to paint in view coordinates, unfortunately
+        // need to transform self (cairo will do it wrong in this coordinate
+        // space), so no need to try to buffer
+        cairo_new_path(mpRT);
+        basegfx::B2DPolyPolygon aAdaptedPolyPolygon(rPolyPolygon);
+        
aAdaptedPolyPolygon.transform(getViewInformation2D().getObjectToViewTransformation());
+        cairo_identity_matrix(mpRT);
+        for (const auto& rPolygon : aAdaptedPolyPolygon)
+            addB2DPolygonToPathGeometry(mpRT, rPolygon);
+        cairo_fill(mpRT);
+    }
+    else
+    {
+        // set linear transformation. use own, prepared, re-usable
+        // ObjectToViewTransformation and PolyPoylgon data and let
+        // cairo do the transformations
+        cairo_matrix_t aMatrix;
+        const basegfx::B2DHomMatrix& rObjectToView(
+            getViewInformation2D().getObjectToViewTransformation());
+        cairo_matrix_init(&aMatrix, rObjectToView.a(), rObjectToView.b(), 
rObjectToView.c(),
+                          rObjectToView.d(), rObjectToView.e(), 
rObjectToView.f());
+        cairo_set_matrix(mpRT, &aMatrix);
+
+        // get PathGeometry & paint it
+        cairo_new_path(mpRT);
+        getOrCreateFillGeometry(mpRT, rPolyPolygon);
+        cairo_fill(mpRT);
+    }
 
     cairo_restore(mpRT);
 }
@@ -1678,24 +1788,41 @@ void CairoPixelProcessor2D::processMaskPrimitive2D(
 
     cairo_save(mpRT);
 
-    // set linear transformation for applying mask. use no fAAOffset for mask
-    cairo_matrix_t aMatrix;
-    const basegfx::B2DHomMatrix& rObjectToView(
-        getViewInformation2D().getObjectToViewTransformation());
-    cairo_matrix_init(&aMatrix, rObjectToView.a(), rObjectToView.b(), 
rObjectToView.c(),
-                      rObjectToView.d(), rObjectToView.e(), rObjectToView.f());
-    cairo_set_matrix(mpRT, &aMatrix);
+    if (isCairoCoordinateLimitWorkaroundActive())
+    {
+        // need to fallback to paint in view coordinates, unfortunately
+        // need to transform self (cairo will do it wrong in this coordinate
+        // space), so no need to try to buffer
+        cairo_new_path(mpRT);
+        basegfx::B2DPolyPolygon aAdaptedPolyPolygon(rMask);
+        
aAdaptedPolyPolygon.transform(getViewInformation2D().getObjectToViewTransformation());
+        for (const auto& rPolygon : aAdaptedPolyPolygon)
+            addB2DPolygonToPathGeometry(mpRT, rPolygon);
 
-    // create path geometry and put mask as path
-    cairo_new_path(mpRT);
-    getOrCreateFillGeometry(mpRT, rMask);
+        // clip to this mask
+        cairo_clip(mpRT);
+    }
+    else
+    {
+        // set linear transformation for applying mask. use no fAAOffset for 
mask
+        cairo_matrix_t aMatrix;
+        const basegfx::B2DHomMatrix& rObjectToView(
+            getViewInformation2D().getObjectToViewTransformation());
+        cairo_matrix_init(&aMatrix, rObjectToView.a(), rObjectToView.b(), 
rObjectToView.c(),
+                          rObjectToView.d(), rObjectToView.e(), 
rObjectToView.f());
+        cairo_set_matrix(mpRT, &aMatrix);
 
-    // clip to this mask
-    cairo_clip(mpRT);
+        // create path geometry and put mask as path
+        cairo_new_path(mpRT);
+        getOrCreateFillGeometry(mpRT, rMask);
 
-    // reset transformation to not have it set when processing
-    // child content below (was only used to set clip path)
-    cairo_identity_matrix(mpRT);
+        // clip to this mask
+        cairo_clip(mpRT);
+
+        // reset transformation to not have it set when processing
+        // child content below (was only used to set clip path)
+        cairo_identity_matrix(mpRT);
+    }
 
     // process sub-content (that shall be masked)
     mnClipRecursionCount++;
@@ -1957,14 +2084,6 @@ void 
CairoPixelProcessor2D::processPolygonStrokePrimitive2D(
 
     cairo_save(mpRT);
 
-    // set linear transformation
-    cairo_matrix_t aMatrix;
-    const double fAAOffset(getViewInformation2D().getUseAntiAliasing() ? 0.5 : 
0.0);
-    cairo_matrix_init(&aMatrix, rObjectToView.a(), rObjectToView.b(), 
rObjectToView.c(),
-                      rObjectToView.d(), rObjectToView.e() + fAAOffset,
-                      rObjectToView.f() + fAAOffset);
-    cairo_set_matrix(mpRT, &aMatrix);
-
     // setup line attributes
     cairo_line_join_t eCairoLineJoin = CAIRO_LINE_JOIN_MITER;
     switch (rLineAttribute.getLineJoin())
@@ -2015,33 +2134,77 @@ void 
CairoPixelProcessor2D::processPolygonStrokePrimitive2D(
         aLineColor.setRed(0.5);
     cairo_set_source_rgb(mpRT, aLineColor.getRed(), aLineColor.getGreen(), 
aLineColor.getBlue());
 
-    // process/set LineWidth
-    const double fObjectLineWidth(
-        bHairline ? 
(getViewInformation2D().getInverseObjectToViewTransformation()
-                     * basegfx::B2DVector(1.0, 0.0))
-                        .getLength()
-                  : rLineAttribute.getWidth());
-    cairo_set_line_width(mpRT, fObjectLineWidth);
-
     // check stroke
     const attribute::StrokeAttribute& rStrokeAttribute(
         rPolygonStrokeCandidate.getStrokeAttribute());
     const bool bDashUsed(!rStrokeAttribute.isDefault()
                          && !rStrokeAttribute.getDotDashArray().empty()
                          && 0.0 < rStrokeAttribute.getFullDotDashLen());
-    if (bDashUsed)
-    {
-        const std::vector<double>& rStroke = 
rStrokeAttribute.getDotDashArray();
-        cairo_set_dash(mpRT, rStroke.data(), rStroke.size(), 0.0);
+    if (isCairoCoordinateLimitWorkaroundActive())
+    {
+        // need to fallback to paint in view coordinates, unfortunately
+        // need to transform self (cairo will do it wrong in this coordinate
+        // space), so no need to try to buffer
+        cairo_new_path(mpRT);
+        basegfx::B2DPolygon aAdaptedPolygon(rPolygon);
+        const double fAAOffset(getViewInformation2D().getUseAntiAliasing() ? 
0.5 : 0.0);
+        
aAdaptedPolygon.transform(basegfx::utils::createTranslateB2DHomMatrix(fAAOffset,
 fAAOffset)
+                                  * 
getViewInformation2D().getObjectToViewTransformation());
+        cairo_identity_matrix(mpRT);
+        addB2DPolygonToPathGeometry(mpRT, aAdaptedPolygon);
+
+        // process/set LineWidth
+        const double fObjectLineWidth(bHairline
+                                          ? 1.0
+                                          : 
(getViewInformation2D().getObjectToViewTransformation()
+                                             * 
basegfx::B2DVector(rLineAttribute.getWidth(), 0.0))
+                                                .getLength());
+        cairo_set_line_width(mpRT, fObjectLineWidth);
+
+        if (bDashUsed)
+        {
+            std::vector<double> aStroke(rStrokeAttribute.getDotDashArray());
+            for (auto& rCandidate : aStroke)
+                rCandidate = 
(getViewInformation2D().getObjectToViewTransformation()
+                              * basegfx::B2DVector(rCandidate, 0.0))
+                                 .getLength();
+            cairo_set_dash(mpRT, aStroke.data(), aStroke.size(), 0.0);
+        }
+
+        cairo_stroke(mpRT);
     }
+    else
+    {
+        // set linear transformation
+        cairo_matrix_t aMatrix;
+        const double fAAOffset(getViewInformation2D().getUseAntiAliasing() ? 
0.5 : 0.0);
+        cairo_matrix_init(&aMatrix, rObjectToView.a(), rObjectToView.b(), 
rObjectToView.c(),
+                          rObjectToView.d(), rObjectToView.e() + fAAOffset,
+                          rObjectToView.f() + fAAOffset);
+        cairo_set_matrix(mpRT, &aMatrix);
 
-    // create path geometry and put mask as path
-    cairo_new_path(mpRT);
-    getOrCreatePathGeometry(mpRT, rPolygon, getViewInformation2D(),
-                            bHairline && 
getViewInformation2D().getUseAntiAliasing());
+        // create path geometry and put mask as path
+        cairo_new_path(mpRT);
+        getOrCreatePathGeometry(mpRT, rPolygon, getViewInformation2D(),
+                                bHairline && 
getViewInformation2D().getUseAntiAliasing());
 
-    // render
-    cairo_stroke(mpRT);
+        // process/set LineWidth
+        const double fObjectLineWidth(
+            bHairline ? 
(getViewInformation2D().getInverseObjectToViewTransformation()
+                         * basegfx::B2DVector(1.0, 0.0))
+                            .getLength()
+                      : rLineAttribute.getWidth());
+        cairo_set_line_width(mpRT, fObjectLineWidth);
+
+        if (bDashUsed)
+        {
+            const std::vector<double>& rStroke = 
rStrokeAttribute.getDotDashArray();
+            cairo_set_dash(mpRT, rStroke.data(), rStroke.size(), 0.0);
+        }
+
+        // render
+        cairo_stroke(mpRT);
+    }
 
     cairo_restore(mpRT);
 }
@@ -2057,16 +2220,11 @@ void 
CairoPixelProcessor2D::processLineRectanglePrimitive2D(
 
     cairo_save(mpRT);
 
-    cairo_matrix_t aMatrix;
+    // work in view coordinates
     const double fAAOffset(getViewInformation2D().getUseAntiAliasing() ? 0.5 : 
0.0);
-    const basegfx::B2DHomMatrix& rObjectToView(
-        getViewInformation2D().getObjectToViewTransformation());
-    cairo_matrix_init(&aMatrix, rObjectToView.a(), rObjectToView.b(), 
rObjectToView.c(),
-                      rObjectToView.d(), rObjectToView.e() + fAAOffset,
-                      rObjectToView.f() + fAAOffset);
-
-    // set linear transformation
-    cairo_set_matrix(mpRT, &aMatrix);
+    basegfx::B2DRange aRange(rLineRectanglePrimitive2D.getB2DRange());
+    aRange.transform(getViewInformation2D().getObjectToViewTransformation());
+    cairo_identity_matrix(mpRT);
 
     const basegfx::BColor aHairlineColor(
         
maBColorModifierStack.getModifiedColor(rLineRectanglePrimitive2D.getBColor()));
@@ -2078,9 +2236,8 @@ void 
CairoPixelProcessor2D::processLineRectanglePrimitive2D(
                                         .getLength());
     cairo_set_line_width(mpRT, fDiscreteLineWidth);
 
-    const basegfx::B2DRange& rRange(rLineRectanglePrimitive2D.getB2DRange());
-    cairo_rectangle(mpRT, rRange.getMinX(), rRange.getMinY(), 
rRange.getWidth(),
-                    rRange.getHeight());
+    cairo_rectangle(mpRT, aRange.getMinX() + fAAOffset, aRange.getMinY() + 
fAAOffset,
+                    aRange.getWidth(), aRange.getHeight());
     cairo_stroke(mpRT);
 
     cairo_restore(mpRT);
@@ -2097,22 +2254,17 @@ void 
CairoPixelProcessor2D::processFilledRectanglePrimitive2D(
 
     cairo_save(mpRT);
 
-    cairo_matrix_t aMatrix;
-    const basegfx::B2DHomMatrix& rObjectToView(
-        getViewInformation2D().getObjectToViewTransformation());
-    cairo_matrix_init(&aMatrix, rObjectToView.a(), rObjectToView.b(), 
rObjectToView.c(),
-                      rObjectToView.d(), rObjectToView.e(), rObjectToView.f());
-
-    // set linear transformation
-    cairo_set_matrix(mpRT, &aMatrix);
+    // work in view coordinates
+    basegfx::B2DRange aRange(rFilledRectanglePrimitive2D.getB2DRange());
+    aRange.transform(getViewInformation2D().getObjectToViewTransformation());
+    cairo_identity_matrix(mpRT);
 
     const basegfx::BColor aFillColor(
         
maBColorModifierStack.getModifiedColor(rFilledRectanglePrimitive2D.getBColor()));
     cairo_set_source_rgb(mpRT, aFillColor.getRed(), aFillColor.getGreen(), 
aFillColor.getBlue());
 
-    const basegfx::B2DRange& rRange(rFilledRectanglePrimitive2D.getB2DRange());
-    cairo_rectangle(mpRT, rRange.getMinX(), rRange.getMinY(), 
rRange.getWidth(),
-                    rRange.getHeight());
+    cairo_rectangle(mpRT, aRange.getMinX(), aRange.getMinY(), 
aRange.getWidth(),
+                    aRange.getHeight());
     cairo_fill(mpRT);
 
     cairo_restore(mpRT);
@@ -2132,6 +2284,7 @@ void CairoPixelProcessor2D::processSingleLinePrimitive2D(
         getViewInformation2D().getObjectToViewTransformation());
     const basegfx::B2DPoint aStart(rObjectToView * 
rSingleLinePrimitive2D.getStart());
     const basegfx::B2DPoint aEnd(rObjectToView * 
rSingleLinePrimitive2D.getEnd());
+    cairo_identity_matrix(mpRT);
 
     cairo_set_line_width(mpRT, 1.0f);
 
@@ -3717,6 +3870,47 @@ void CairoPixelProcessor2D::processControlPrimitive2D(
     process(rControlPrimitive);
 }
 
+void CairoPixelProcessor2D::evaluateCairoCoordinateLimitWorkaround()
+{
+    static bool bAlreadyCheckedIfNeeded(false);
+    static bool bIsNeeded(false);
+
+    if (!bAlreadyCheckedIfNeeded)
+    {
+        // check once for office runtime: is workarund needed?
+        bAlreadyCheckedIfNeeded = true;
+        bIsNeeded = checkCoordinateLimitWorkaroundNeededForUsedCairo();
+    }
+
+    if (!bIsNeeded)
+    {
+        // we have a working cairo, so workarund is not needed
+        // and mbCairoCoordinateLimitWorkaroundActive can stay false
+        return;
+    }
+
+    // get discrete size (pixels)
+    basegfx::B2DRange aLogicViewRange(getDiscreteViewRange(mpRT));
+
+    // transform to world coordinates -> logic view range
+    basegfx::B2DHomMatrix 
aInvViewTrans(getViewInformation2D().getViewTransformation());
+    aInvViewTrans.invert();
+    aLogicViewRange.transform(aInvViewTrans);
+
+    // create 1<<23 CairoCoordinate limit from 24.8 internal format
+    // and a range fitting to it (just once, this is static)
+    constexpr double fNumCairoMax(1 << 23);
+    static const basegfx::B2DRange aNumericalCairoLimit(-fNumCairoMax, 
-fNumCairoMax,
+                                                        fNumCairoMax - 1.0, 
fNumCairoMax - 1.0);
+
+    if (!aLogicViewRange.isEmpty() && 
!aNumericalCairoLimit.isInside(aLogicViewRange))
+    {
+        // aLogicViewRange is not completely inside region covered by
+        // 24.8 cairo format, thus workaround is needed, set flag
+        mbCairoCoordinateLimitWorkaroundActive = true;
+    }
+}
+
 void CairoPixelProcessor2D::processBasePrimitive2D(const 
primitive2d::BasePrimitive2D& rCandidate)
 {
     const cairo_status_t aStart(cairo_status(mpRT));
diff --git a/include/drawinglayer/processor2d/cairopixelprocessor2d.hxx 
b/include/drawinglayer/processor2d/cairopixelprocessor2d.hxx
index fc67ed78932b..3d20f300a972 100644
--- a/include/drawinglayer/processor2d/cairopixelprocessor2d.hxx
+++ b/include/drawinglayer/processor2d/cairopixelprocessor2d.hxx
@@ -86,6 +86,9 @@ class UNLESS_MERGELIBS(DRAWINGLAYER_DLLPUBLIC) 
CairoPixelProcessor2D final : pub
     // see comment there
     sal_uInt16 mnClipRecursionCount;
 
+    // calculated result of if we are in outsideCairoCoordinateLimits mode
+    bool mbCairoCoordinateLimitWorkaroundActive;
+
     // helpers for direct paints
     void paintPolyPoylgonRGBA(const basegfx::B2DPolyPolygon& rPolyPolygon,
                               const basegfx::BColor& rColor, double 
fTransparency = 0.0);
@@ -179,6 +182,9 @@ class UNLESS_MERGELIBS(DRAWINGLAYER_DLLPUBLIC) 
CairoPixelProcessor2D final : pub
     void processFillGradientPrimitive2D_square_rect(
         const primitive2d::FillGradientPrimitive2D& rFillGradientPrimitive2D);
 
+    // check if CairoCoordinateLimitWorkaround is needed
+    void evaluateCairoCoordinateLimitWorkaround();
+
 protected:
     bool hasError() const { return cairo_status(mpRT) != CAIRO_STATUS_SUCCESS; 
}
     bool hasRenderTarget() const { return nullptr != mpRT; }
@@ -186,6 +192,12 @@ protected:
 public:
     bool valid() const { return hasRenderTarget() && !hasError(); }
 
+    // read access to CairoCoordinateLimitWorkaround mechanism
+    bool isCairoCoordinateLimitWorkaroundActive() const
+    {
+        return mbCairoCoordinateLimitWorkaroundActive;
+    }
+
     // constructor to create a CairoPixelProcessor2D which
     // allocates and owns a cairo surface of given size. You
     // should check the result using valid()

Reply via email to