// Copyright 2020 The PDFium Authors // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. // Original code copyright 2014 Foxit Software Inc. http://www.foxitsoftware.com #include "core/fpdfapi/render/cpdf_rendertiling.h" #include #include #include "core/fpdfapi/page/cpdf_form.h" #include "core/fpdfapi/page/cpdf_pageimagecache.h" #include "core/fpdfapi/page/cpdf_tilingpattern.h" #include "core/fpdfapi/parser/cpdf_document.h" #include "core/fpdfapi/render/cpdf_rendercontext.h" #include "core/fpdfapi/render/cpdf_renderoptions.h" #include "core/fpdfapi/render/cpdf_renderstatus.h" #include "core/fxcrt/fx_safe_types.h" #include "core/fxge/cfx_defaultrenderdevice.h" namespace { RetainPtr DrawPatternBitmap( CPDF_Document* pDoc, CPDF_PageImageCache* pCache, CPDF_TilingPattern* pPattern, CPDF_Form* pPatternForm, const CFX_Matrix& mtObject2Device, int width, int height, const CPDF_RenderOptions::Options& draw_options) { auto pBitmap = pdfium::MakeRetain(); if (!pBitmap->Create(width, height, pPattern->colored() ? FXDIB_Format::kArgb : FXDIB_Format::k8bppMask)) { return nullptr; } CFX_DefaultRenderDevice bitmap_device; bitmap_device.AttachWithBackdropAndGroupKnockout( pBitmap, /*pBackdropBitmap=*/nullptr, /*bGroupKnockout=*/true); CFX_FloatRect cell_bbox = pPattern->pattern_to_form().TransformRect(pPattern->bbox()); cell_bbox = mtObject2Device.TransformRect(cell_bbox); CFX_FloatRect bitmap_rect(0.0f, 0.0f, width, height); CFX_Matrix mtAdjust; mtAdjust.MatchRect(bitmap_rect, cell_bbox); CFX_Matrix mtPattern2Bitmap = mtObject2Device * mtAdjust; CPDF_RenderOptions options; if (!pPattern->colored()) options.SetColorMode(CPDF_RenderOptions::kAlpha); options.GetOptions() = draw_options; options.GetOptions().bForceHalftone = true; CPDF_RenderContext context(pDoc, nullptr, pCache); context.AppendLayer(pPatternForm, mtPattern2Bitmap); context.Render(&bitmap_device, nullptr, &options, nullptr); #if defined(_SKIA_SUPPORT_) if (CFX_DefaultRenderDevice::SkiaIsDefaultRenderer()) pBitmap->UnPreMultiply(); #endif // defined(_SKIA_SUPPORT_) return pBitmap; } } // namespace // static RetainPtr CPDF_RenderTiling::Draw( CPDF_RenderStatus* pRenderStatus, CPDF_PageObject* pPageObj, CPDF_TilingPattern* pPattern, CPDF_Form* pPatternForm, const CFX_Matrix& mtObj2Device, const FX_RECT& clip_box, bool bStroke) { const CFX_Matrix mtPattern2Device = pPattern->pattern_to_form() * mtObj2Device; CFX_FloatRect cell_bbox = mtPattern2Device.TransformRect(pPattern->bbox()); float ceil_height = std::ceil(cell_bbox.Height()); float ceil_width = std::ceil(cell_bbox.Width()); // Validate the float will fit into the int when the conversion is done. if (!pdfium::base::IsValueInRangeForNumericType(ceil_height) || !pdfium::base::IsValueInRangeForNumericType(ceil_width)) { return nullptr; } int width = static_cast(ceil_width); int height = static_cast(ceil_height); if (width <= 0) width = 1; if (height <= 0) height = 1; CFX_FloatRect clip_box_p = mtPattern2Device.GetInverse().TransformRect(CFX_FloatRect(clip_box)); int min_col = static_cast( ceil((clip_box_p.left - pPattern->bbox().right) / pPattern->x_step())); int max_col = static_cast( floor((clip_box_p.right - pPattern->bbox().left) / pPattern->x_step())); int min_row = static_cast( ceil((clip_box_p.bottom - pPattern->bbox().top) / pPattern->y_step())); int max_row = static_cast( floor((clip_box_p.top - pPattern->bbox().bottom) / pPattern->y_step())); // Make sure we can fit the needed width * height into an int. if (height > std::numeric_limits::max() / width) return nullptr; CFX_RenderDevice* pDevice = pRenderStatus->GetRenderDevice(); CPDF_RenderContext* pContext = pRenderStatus->GetContext(); const CPDF_RenderOptions& options = pRenderStatus->GetRenderOptions(); if (width > clip_box.Width() || height > clip_box.Height() || width * height > clip_box.Width() * clip_box.Height()) { std::unique_ptr pStates; if (!pPattern->colored()) pStates = CPDF_RenderStatus::CloneObjStates(pPageObj, bStroke); RetainPtr pFormResource = pPatternForm->GetDict()->GetDictFor("Resources"); for (int col = min_col; col <= max_col; col++) { for (int row = min_row; row <= max_row; row++) { CFX_PointF original = mtPattern2Device.Transform( CFX_PointF(col * pPattern->x_step(), row * pPattern->y_step())); CFX_Matrix matrix = mtObj2Device; matrix.Translate(original.x - mtPattern2Device.e, original.y - mtPattern2Device.f); CFX_RenderDevice::StateRestorer restorer2(pDevice); CPDF_RenderStatus status(pContext, pDevice); status.SetOptions(options); status.SetTransparency(pPatternForm->GetTransparency()); status.SetFormResource(pFormResource); status.SetDropObjects(pRenderStatus->GetDropObjects()); status.Initialize(pRenderStatus, pStates.get()); status.RenderObjectList(pPatternForm, matrix); } } return nullptr; } bool bAligned = pPattern->bbox().left == 0 && pPattern->bbox().bottom == 0 && pPattern->bbox().right == pPattern->x_step() && pPattern->bbox().top == pPattern->y_step() && (mtPattern2Device.IsScaled() || mtPattern2Device.Is90Rotated()); if (bAligned) { int orig_x = FXSYS_roundf(mtPattern2Device.e); int orig_y = FXSYS_roundf(mtPattern2Device.f); min_col = (clip_box.left - orig_x) / width; if (clip_box.left < orig_x) min_col--; max_col = (clip_box.right - orig_x) / width; if (clip_box.right <= orig_x) max_col--; min_row = (clip_box.top - orig_y) / height; if (clip_box.top < orig_y) min_row--; max_row = (clip_box.bottom - orig_y) / height; if (clip_box.bottom <= orig_y) max_row--; } float left_offset = cell_bbox.left - mtPattern2Device.e; float top_offset = cell_bbox.bottom - mtPattern2Device.f; RetainPtr pPatternBitmap; if (width * height < 16) { RetainPtr pEnlargedBitmap = DrawPatternBitmap( pContext->GetDocument(), pContext->GetPageCache(), pPattern, pPatternForm, mtObj2Device, 8, 8, options.GetOptions()); pPatternBitmap = pEnlargedBitmap->StretchTo( width, height, FXDIB_ResampleOptions(), nullptr); } else { pPatternBitmap = DrawPatternBitmap( pContext->GetDocument(), pContext->GetPageCache(), pPattern, pPatternForm, mtObj2Device, width, height, options.GetOptions()); } if (!pPatternBitmap) return nullptr; if (options.ColorModeIs(CPDF_RenderOptions::kGray)) pPatternBitmap->ConvertColorScale(0, 0xffffff); FX_ARGB fill_argb = pRenderStatus->GetFillArgb(pPageObj); int clip_width = clip_box.right - clip_box.left; int clip_height = clip_box.bottom - clip_box.top; auto pScreen = pdfium::MakeRetain(); if (!pScreen->Create(clip_width, clip_height, FXDIB_Format::kArgb)) return nullptr; pdfium::span src_buf = pPatternBitmap->GetBuffer(); for (int col = min_col; col <= max_col; col++) { for (int row = min_row; row <= max_row; row++) { int start_x; int start_y; if (bAligned) { start_x = FXSYS_roundf(mtPattern2Device.e) + col * width - clip_box.left; start_y = FXSYS_roundf(mtPattern2Device.f) + row * height - clip_box.top; } else { CFX_PointF original = mtPattern2Device.Transform( CFX_PointF(col * pPattern->x_step(), row * pPattern->y_step())); FX_SAFE_INT32 safeStartX = FXSYS_roundf(original.x + left_offset); FX_SAFE_INT32 safeStartY = FXSYS_roundf(original.y + top_offset); safeStartX -= clip_box.left; safeStartY -= clip_box.top; if (!safeStartX.IsValid() || !safeStartY.IsValid()) return nullptr; start_x = safeStartX.ValueOrDie(); start_y = safeStartY.ValueOrDie(); } if (width == 1 && height == 1) { if (start_x < 0 || start_x >= clip_box.Width() || start_y < 0 || start_y >= clip_box.Height()) { continue; } uint32_t* dest_buf = reinterpret_cast( pScreen->GetWritableScanline(start_y).subspan(start_x * 4).data()); if (pPattern->colored()) { const auto* src_buf32 = reinterpret_cast(src_buf.data()); *dest_buf = *src_buf32; } else { *dest_buf = (*(src_buf.data()) << 24) | (fill_argb & 0xffffff); } } else { if (pPattern->colored()) { pScreen->CompositeBitmap(start_x, start_y, width, height, pPatternBitmap, 0, 0, BlendMode::kNormal, nullptr, false); } else { pScreen->CompositeMask(start_x, start_y, width, height, pPatternBitmap, fill_argb, 0, 0, BlendMode::kNormal, nullptr, false); } } } } return pScreen; }