<commit_before>/*
 * 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
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.tom_roush.pdfbox.rendering;

import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.DashPathEffect;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.PointF;
import android.graphics.RectF;
import android.graphics.Region;
import android.util.Log;

import java.io.IOException;
import java.util.HashMap;
import java.util.Map;

import com.tom_roush.harmony.awt.geom.AffineTransform;
import com.tom_roush.pdfbox.contentstream.PDFGraphicsStreamEngine;
import com.tom_roush.pdfbox.cos.COSArray;
import com.tom_roush.pdfbox.cos.COSBase;
import com.tom_roush.pdfbox.cos.COSDictionary;
import com.tom_roush.pdfbox.cos.COSName;
import com.tom_roush.pdfbox.cos.COSNumber;
import com.tom_roush.pdfbox.pdmodel.common.PDRectangle;
import com.tom_roush.pdfbox.pdmodel.common.function.PDFunction;
import com.tom_roush.pdfbox.pdmodel.font.PDCIDFontType0;
import com.tom_roush.pdfbox.pdmodel.font.PDCIDFontType2;
import com.tom_roush.pdfbox.pdmodel.font.PDFont;
import com.tom_roush.pdfbox.pdmodel.font.PDTrueTypeFont;
import com.tom_roush.pdfbox.pdmodel.font.PDType0Font;
import com.tom_roush.pdfbox.pdmodel.font.PDType1CFont;
import com.tom_roush.pdfbox.pdmodel.font.PDType1Font;
import com.tom_roush.pdfbox.pdmodel.graphics.PDLineDashPattern;
import com.tom_roush.pdfbox.pdmodel.graphics.color.PDColor;
import com.tom_roush.pdfbox.pdmodel.graphics.color.PDColorSpace;
import com.tom_roush.pdfbox.pdmodel.graphics.color.PDDeviceGray;
import com.tom_roush.pdfbox.pdmodel.graphics.form.PDTransparencyGroup;
import com.tom_roush.pdfbox.pdmodel.graphics.image.PDImage;
import com.tom_roush.pdfbox.pdmodel.graphics.shading.PDShading;
import com.tom_roush.pdfbox.pdmodel.graphics.state.PDGraphicsState;
import com.tom_roush.pdfbox.pdmodel.graphics.state.PDSoftMask;
import com.tom_roush.pdfbox.pdmodel.graphics.state.RenderingMode;
import com.tom_roush.pdfbox.pdmodel.interactive.annotation.PDAnnotation;
import com.tom_roush.pdfbox.pdmodel.interactive.annotation.PDAnnotationLink;
import com.tom_roush.pdfbox.pdmodel.interactive.annotation.PDAnnotationMarkup;
import com.tom_roush.pdfbox.pdmodel.interactive.annotation.PDBorderStyleDictionary;
import com.tom_roush.pdfbox.util.Matrix;
import com.tom_roush.pdfbox.util.Vector;

/**
 * Paints a page in a PDF document to a Canvas context. May be subclassed to provide custom
 * rendering.
 *
 * <p>If you want to do custom graphics processing rather than Canvas rendering, then you should
 * subclass PDFGraphicsStreamEngine instead. Subclassing PageDrawer is only suitable for cases
 * where the goal is to render onto a Canvas surface.
 *
 * @author <NAME>
 */
public class PageDrawer extends PDFGraphicsStreamEngine
{
    // parent document renderer - note: this is needed for not-yet-implemented resource caching
    private final PDFRenderer renderer;

    // the graphics device to draw to, xform is the initial transform of the device (i.e. DPI)
    Paint paint;
    Canvas canvas;
    private AffineTransform xform;

    // the page box to draw (usually the crop box but may be another)
    private PDRectangle pageSize;

    // clipping winding rule used for the clipping path
    private Path.FillType clipWindingRule = null;
    private Path linePath = new Path();

    // last clipping path
    private Region lastClip;

    // buffered clipping area for text being drawn
    private Region textClippingArea;

    // glyph cache
    private final Map<PDFont, Glyph2D> fontGlyph2D = new HashMap<PDFont, Glyph2D>();

    private PointF currentPoint = new PointF();

    /**
     * Constructor.
     *
     * @param parameters Parameters for page drawing.
     * @throws IOException If there is an error loading properties from the file.
     */
    public PageDrawer(PageDrawerParameters parameters) throws IOException
    {
        super(parameters.getPage());
        this.renderer = parameters.getRenderer();
    }

    /**
     * Returns the parent renderer.
     */
    public final PDFRenderer getRenderer()
    {
        return renderer;
    }

    /**
     * Returns the underlying Canvas. May be null if drawPage has not yet been called.
     */
    protected final Canvas getCanvas()
    {
        return canvas;
    }

    /**
     * Returns the current line path. This is reset to empty after each fill/stroke.
     */
    protected final Path getLinePath()
    {
        return linePath;
    }

    /**
     * Sets high-quality rendering hints on the current Graphics2D.
     */
    private void setRenderingHints()
    {
//        graphics.setRenderingHint(RenderingHints.KEY_INTERPOLATION,
//                                  RenderingHints.VALUE_INTERPOLATION_BICUBIC);
//        graphics.setRenderingHint(RenderingHints.KEY_RENDERING,
//                                  RenderingHints.VALUE_RENDER_QUALITY);
        paint.setAntiAlias(true);
    }

    /**
     * Draws the page to the requested canvas.
     *
     * @param p The paint.
     * @param c The canvas to draw onto.
     * @param pageSize The size of the page to draw.
     * @throws IOException If there is an IO error while drawing the page.
     */
    public void drawPage(Paint p, Canvas c, PDRectangle pageSize) throws IOException
    {
        paint = p;
        canvas = c;
        xform = new AffineTransform(canvas.getMatrix());
        this.pageSize = pageSize;

        setRenderingHints();

        canvas.translate(0, pageSize.getHeight());
        canvas.scale(1, -1);

        paint.setStrokeCap(Paint.Cap.BUTT);
        paint.setStrokeJoin(Paint.Join.MITER);
        paint.setStrokeWidth(1.0f); // FIXME: PdfBox-Android: create set stroke method?

        // adjust for non-(0,0) crop box
        canvas.translate(-pageSize.getLowerLeftX(), -pageSize.getLowerLeftY());
        canvas.save();

        processPage(getPage());

        for (PDAnnotation annotation : getPage().getAnnotations())
        {
            showAnnotation(annotation);
        }

//		graphics = null;
    }

    /**
     * Draws the pattern stream to the requested context.
     *
     * @param g The graphics context to draw onto.
     * @param pattern The tiling pattern to be used.
     * @param colorSpace color space for this tiling.
     * @param color color for this tiling.
     * @param patternMatrix the pattern matrix
     * @throws IOException If there is an IO error while drawing the page.
     */
//    void drawTilingPattern(Graphics2D g, PDTilingPattern pattern, PDColorSpace colorSpace,
//                                  PDColor color, Matrix patternMatrix) throws IOException
//    {
//        Graphics2D oldGraphics = graphics;
//        graphics = g;
//
//        Path oldLinePath = linePath;
//        linePath = new GeneralPath();
//
//        Area oldLastClip = lastClip;
//        lastClip = null;
//
//        setRenderingHints();
//        processTilingPattern(pattern, color, colorSpace, patternMatrix);
//
//        graphics = oldGraphics;
//        linePath = oldLinePath;
//        lastClip = oldLastClip;
//    } TODO: PdfBox-Android

    /**
     * Returns an AWT paint for the given PDColor.
     */
//    protected Paint getPaint(PDColor color) throws IOException
//    {
//        PDColorSpace colorSpace = color.getColorSpace();
//        if (!(colorSpace instanceof PDPattern))
//        {
//            float[] rgb = colorSpace.toRGB(color.getComponents());
//            return new Color(rgb[0], rgb[1], rgb[2]);
//        }
//        else
//        {
//            PDPattern patternSpace = (PDPattern)colorSpace;
//            PDAbstractPattern pattern = patternSpace.getPattern(color);
//            if (pattern instanceof PDTilingPattern)
//            {
//                PDTilingPattern tilingPattern = (PDTilingPattern) pattern;
//
//                if (tilingPattern.getPaintType() == PDTilingPattern.PAINT_COLORED)
//                {
//                    // colored tiling pattern
//                    return new TilingPaint(this, tilingPattern, xform);
//                }
//                else
//                {
//                    // uncolored tiling pattern
//                    return new TilingPaint(this, tilingPattern,
//                            patternSpace.getUnderlyingColorSpace(), color, xform);
//                }
//            }
//            else
//            {
//                PDShadingPattern shadingPattern = (PDShadingPattern)pattern;
//                PDShading shading = shadingPattern.getShading();
//                if (shading == null)
//                {
//                    LOG.error("shadingPattern is null, will be filled with transparency");
//                    return new Color(0,0,0,0);
//                }
//                return shading.toPaint(Matrix.concatenate(getInitialMatrix(),
//					shadingPattern.getMatrix()));
//            }
//        }
//    } TODO: PdfBox-Android

    // returns an integer for color that Android understands from the PDColor
    // TODO: alpha?
    private int getColor(PDColor color) throws IOException {
        PDColorSpace colorSpace = color.getColorSpace();
        float[] floats = colorSpace.toRGB(color.getComponents());
        int r = Math.round(floats[0] * 255);
        int g = Math.round(floats[1] * 255);
        int b = Math.round(floats[2] * 255);
        return Color.rgb(r, g, b);
    }

    // sets the clipping path using caching for performance, we track lastClip manually because
    // Graphics2D#getClip() returns a new object instead of the same one passed to setClip
    private void setClip()
    {
        Region clippingPath = getGraphicsState().getCurrentClippingPath();
        if (clippingPath != lastClip)
        {
            canvas.clipPath(clippingPath.getBoundaryPath());
            lastClip = clippingPath;
        }
    }

    @Override
    public void beginText() throws IOException
    {
        setClip();
        beginTextClip();
    }

    @Override
    public void endText() throws IOException
    {
        endTextClip();
    }

    /**
     * Begin buffering the text clipping path, if any.<commit_msg>Fix Canvas clipping causing rendering issues (#299)<commit_after>/*
 * 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
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.tom_roush.pdfbox.rendering;

import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.DashPathEffect;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.PointF;
import android.graphics.RectF;
import android.graphics.Region;
import android.util.Log;

import java.io.IOException;
import java.util.HashMap;
import java.util.Map;

import com.tom_roush.harmony.awt.geom.AffineTransform;
import com.tom_roush.pdfbox.contentstream.PDFGraphicsStreamEngine;
import com.tom_roush.pdfbox.cos.COSArray;
import com.tom_roush.pdfbox.cos.COSBase;
import com.tom_roush.pdfbox.cos.COSDictionary;
import com.tom_roush.pdfbox.cos.COSName;
import com.tom_roush.pdfbox.cos.COSNumber;
import com.tom_roush.pdfbox.pdmodel.common.PDRectangle;
import com.tom_roush.pdfbox.pdmodel.common.function.PDFunction;
import com.tom_roush.pdfbox.pdmodel.font.PDCIDFontType0;
import com.tom_roush.pdfbox.pdmodel.font.PDCIDFontType2;
import com.tom_roush.pdfbox.pdmodel.font.PDFont;
import com.tom_roush.pdfbox.pdmodel.font.PDTrueTypeFont;
import com.tom_roush.pdfbox.pdmodel.font.PDType0Font;
import com.tom_roush.pdfbox.pdmodel.font.PDType1CFont;
import com.tom_roush.pdfbox.pdmodel.font.PDType1Font;
import com.tom_roush.pdfbox.pdmodel.graphics.PDLineDashPattern;
import com.tom_roush.pdfbox.pdmodel.graphics.color.PDColor;
import com.tom_roush.pdfbox.pdmodel.graphics.color.PDColorSpace;
import com.tom_roush.pdfbox.pdmodel.graphics.color.PDDeviceGray;
import com.tom_roush.pdfbox.pdmodel.graphics.form.PDTransparencyGroup;
import com.tom_roush.pdfbox.pdmodel.graphics.image.PDImage;
import com.tom_roush.pdfbox.pdmodel.graphics.shading.PDShading;
import com.tom_roush.pdfbox.pdmodel.graphics.state.PDGraphicsState;
import com.tom_roush.pdfbox.pdmodel.graphics.state.PDSoftMask;
import com.tom_roush.pdfbox.pdmodel.graphics.state.RenderingMode;
import com.tom_roush.pdfbox.pdmodel.interactive.annotation.PDAnnotation;
import com.tom_roush.pdfbox.pdmodel.interactive.annotation.PDAnnotationLink;
import com.tom_roush.pdfbox.pdmodel.interactive.annotation.PDAnnotationMarkup;
import com.tom_roush.pdfbox.pdmodel.interactive.annotation.PDBorderStyleDictionary;
import com.tom_roush.pdfbox.util.Matrix;
import com.tom_roush.pdfbox.util.Vector;

/**
 * Paints a page in a PDF document to a Canvas context. May be subclassed to provide custom
 * rendering.
 *
 * <p>If you want to do custom graphics processing rather than Canvas rendering, then you should
 * subclass PDFGraphicsStreamEngine instead. Subclassing PageDrawer is only suitable for cases
 * where the goal is to render onto a Canvas surface.
 *
 * @author <NAME>
 */
public class PageDrawer extends PDFGraphicsStreamEngine
{
    // parent document renderer - note: this is needed for not-yet-implemented resource caching
    private final PDFRenderer renderer;

    // the graphics device to draw to, xform is the initial transform of the device (i.e. DPI)
    Paint paint;
    Canvas canvas;
    private AffineTransform xform;

    // the page box to draw (usually the crop box but may be another)
    private PDRectangle pageSize;

    // clipping winding rule used for the clipping path
    private Path.FillType clipWindingRule = null;
    private Path linePath = new Path();

    // last clipping path
    private Region lastClip;

    // buffered clipping area for text being drawn
    private Region textClippingArea;

    // glyph cache
    private final Map<PDFont, Glyph2D> fontGlyph2D = new HashMap<PDFont, Glyph2D>();

    private PointF currentPoint = new PointF();

    /**
     * Constructor.
     *
     * @param parameters Parameters for page drawing.
     * @throws IOException If there is an error loading properties from the file.
     */
    public PageDrawer(PageDrawerParameters parameters) throws IOException
    {
        super(parameters.getPage());
        this.renderer = parameters.getRenderer();
    }

    /**
     * Returns the parent renderer.
     */
    public final PDFRenderer getRenderer()
    {
        return renderer;
    }

    /**
     * Returns the underlying Canvas. May be null if drawPage has not yet been called.
     */
    protected final Canvas getCanvas()
    {
        return canvas;
    }

    /**
     * Returns the current line path. This is reset to empty after each fill/stroke.
     */
    protected final Path getLinePath()
    {
        return linePath;
    }

    /**
     * Sets high-quality rendering hints on the current Graphics2D.
     */
    private void setRenderingHints()
    {
//        graphics.setRenderingHint(RenderingHints.KEY_INTERPOLATION,
//                                  RenderingHints.VALUE_INTERPOLATION_BICUBIC);
//        graphics.setRenderingHint(RenderingHints.KEY_RENDERING,
//                                  RenderingHints.VALUE_RENDER_QUALITY);
        paint.setAntiAlias(true);
    }

    /**
     * Draws the page to the requested canvas.
     *
     * @param p The paint.
     * @param c The canvas to draw onto.
     * @param pageSize The size of the page to draw.
     * @throws IOException If there is an IO error while drawing the page.
     */
    public void drawPage(Paint p, Canvas c, PDRectangle pageSize) throws IOException
    {
        paint = p;
        canvas = c;
        xform = new AffineTransform(canvas.getMatrix());
        this.pageSize = pageSize;

        setRenderingHints();

        canvas.translate(0, pageSize.getHeight());
        canvas.scale(1, -1);

        paint.setStrokeCap(Paint.Cap.BUTT);
        paint.setStrokeJoin(Paint.Join.MITER);
        paint.setStrokeWidth(1.0f); // FIXME: PdfBox-Android: create set stroke method?

        // adjust for non-(0,0) crop box
        canvas.translate(-pageSize.getLowerLeftX(), -pageSize.getLowerLeftY());
        canvas.save();

        processPage(getPage());

        for (PDAnnotation annotation : getPage().getAnnotations())
        {
            showAnnotation(annotation);
        }

//		graphics = null;
    }

    /**
     * Draws the pattern stream to the requested context.
     *
     * @param g The graphics context to draw onto.
     * @param pattern The tiling pattern to be used.
     * @param colorSpace color space for this tiling.
     * @param color color for this tiling.
     * @param patternMatrix the pattern matrix
     * @throws IOException If there is an IO error while drawing the page.
     */
//    void drawTilingPattern(Graphics2D g, PDTilingPattern pattern, PDColorSpace colorSpace,
//                                  PDColor color, Matrix patternMatrix) throws IOException
//    {
//        Graphics2D oldGraphics = graphics;
//        graphics = g;
//
//        Path oldLinePath = linePath;
//        linePath = new GeneralPath();
//
//        Area oldLastClip = lastClip;
//        lastClip = null;
//
//        setRenderingHints();
//        processTilingPattern(pattern, color, colorSpace, patternMatrix);
//
//        graphics = oldGraphics;
//        linePath = oldLinePath;
//        lastClip = oldLastClip;
//    } TODO: PdfBox-Android

    /**
     * Returns an AWT paint for the given PDColor.
     */
//    protected Paint getPaint(PDColor color) throws IOException
//    {
//        PDColorSpace colorSpace = color.getColorSpace();
//        if (!(colorSpace instanceof PDPattern))
//        {
//            float[] rgb = colorSpace.toRGB(color.getComponents());
//            return new Color(rgb[0], rgb[1], rgb[2]);
//        }
//        else
//        {
//            PDPattern patternSpace = (PDPattern)colorSpace;
//            PDAbstractPattern pattern = patternSpace.getPattern(color);
//            if (pattern instanceof PDTilingPattern)
//            {
//                PDTilingPattern tilingPattern = (PDTilingPattern) pattern;
//
//                if (tilingPattern.getPaintType() == PDTilingPattern.PAINT_COLORED)
//                {
//                    // colored tiling pattern
//                    return new TilingPaint(this, tilingPattern, xform);
//                }
//                else
//                {
//                    // uncolored tiling pattern
//                    return new TilingPaint(this, tilingPattern,
//                            patternSpace.getUnderlyingColorSpace(), color, xform);
//                }
//            }
//            else
//            {
//                PDShadingPattern shadingPattern = (PDShadingPattern)pattern;
//                PDShading shading = shadingPattern.getShading();
//                if (shading == null)
//                {
//                    LOG.error("shadingPattern is null, will be filled with transparency");
//                    return new Color(0,0,0,0);
//                }
//                return shading.toPaint(Matrix.concatenate(getInitialMatrix(),
//					shadingPattern.getMatrix()));
//            }
//        }
//    } TODO: PdfBox-Android

    // returns an integer for color that Android understands from the PDColor
    // TODO: alpha?
    private int getColor(PDColor color) throws IOException {
        PDColorSpace colorSpace = color.getColorSpace();
        float[] floats = colorSpace.toRGB(color.getComponents());
        int r = Math.round(floats[0] * 255);
        int g = Math.round(floats[1] * 255);
        int b = Math.round(floats[2] * 255);
        return Color.rgb(r, g, b);
    }

    // sets the clipping path using caching for performance, we track lastClip manually because
    // Graphics2D#getClip() returns a new object instead of the same one passed to setClip
    private void setClip()
    {
        Region clippingPath = getGraphicsState().getCurrentClippingPath();
        if (clippingPath != lastClip)
        {
//            canvas.clipPath(clippingPath.getBoundaryPath()); TODO: PdfBox-Android
            lastClip = clippingPath;
        }
    }

    @Override
    public void beginText() throws IOException
    {
        setClip();
        beginTextClip();
    }

    @Override
    public void endText() throws IOException
    {
        endTextClip();
    }

    /**
     * Begin buffering the text clipping path, if any.
{'fraction_non_alphanumeric': 0.09506640500205377, 'fraction_numerical': 0.003970608370225001, 'mean_word_length': 2.58273381294964, 'pattern_counts': {'":': 0, '<': 11, '<?xml version=': 0, '>': 11, 'https://': 0, 'lorem ipsum': 0, 'www.': 2, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 6, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '6889955', 'n_tokens_mistral': 6297, 'n_tokens_neox': 6138, 'n_words': 1681}