如何使用borb(PDF库)python仅旋转表格?

问题描述 投票:0回答:1

我正在使用borb(Python 中的 PDF 库)。我只需要旋转桌子。 (表格内的文本也应根据表格旋转而旋转)

  • 举例来说,如果我将表格向左旋转(即)(90° left_side),表格内的文本也应该旋转。 (右旋转也是如此)
  • 简而言之,只需旋转 Content

这是我的代码.py

from decimal import Decimal
from pathlib import Path

from borb.pdf.canvas.layout.image.image import Image
from borb.pdf.canvas.layout.layout_element import Alignment
from borb.pdf.canvas.layout.page_layout.multi_column_layout import SingleColumnLayout
from borb.pdf.canvas.layout.page_layout.page_layout import PageLayout
from borb.pdf.canvas.color.color import HexColor
from borb.pdf.canvas.layout.table.fixed_column_width_table import FixedColumnWidthTable
from borb.pdf.canvas.layout.table.flexible_column_width_table import FlexibleColumnWidthTable
from borb.pdf.canvas.layout.table.table import TableCell
from borb.pdf.canvas.layout.text.chunk_of_text import ChunkOfText
from borb.pdf.canvas.layout.text.paragraph import Paragraph
from borb.pdf.document.document import Document
from borb.pdf.page.page import Page
from borb.pdf.pdf import PDF




def main():
    # define theme color

    # create new Document
    doc: Document = Document()

    # create new Page
    page: Page = Page()
    doc.add_page(page)

    # set PageLayout
    layout: PageLayout = SingleColumnLayout(page, horizontal_margin=Decimal(25), vertical_margin=Decimal(25))


    layout.add(Paragraph("Welcome"))


    layout.add(
        FixedColumnWidthTable( number_of_columns=1, number_of_rows=2)
            .add(Paragraph("Testing_Line1"))
            .add(Paragraph("Testing_Line2")))




    with open("output.pdf", "wb") as out_file_handle:
        PDF.dumps(out_file_handle, doc)


if __name__ == "__main__":
    main()
python python-3.x pdf borb
1个回答
2
投票

免责声明:我是

borb

的作者

我有以下代码(它尝试一般旋转任何

LayoutElement

import math
import typing
from decimal import Decimal

from borb.pdf.canvas.geometry.rectangle import Rectangle
from borb.pdf.canvas.layout.layout_element import LayoutElement


def rotate_point(angle_in_degrees: Decimal,
                 point: typing.Tuple[Decimal, Decimal]) -> typing.Tuple[Decimal, Decimal]:
    # convert angle to radians
    angle_in_radians: Decimal = Decimal(math.radians(angle_in_degrees))

    # perform rotation
    x: Decimal = point[0]
    y: Decimal = point[1]
    x_prime: Decimal = x * Decimal(math.cos(angle_in_radians)) - y * Decimal(math.sin(angle_in_radians))
    y_prime: Decimal = x * Decimal(math.sin(angle_in_radians)) + y * Decimal(math.cos(angle_in_radians))

    # return
    return x_prime, y_prime


def dimensions_of_rotated_rectangle(r: Rectangle,
                                    angle_in_degrees: Decimal) -> Rectangle:
    ZERO: Decimal = Decimal(0)
    W: Decimal = r.get_width()
    H: Decimal = r.get_height()
    p0: typing.Tuple[Decimal, Decimal] = rotate_point(angle_in_degrees=angle_in_degrees, point=(ZERO, ZERO))
    p1: typing.Tuple[Decimal, Decimal] = rotate_point(angle_in_degrees=angle_in_degrees, point=(ZERO, H))
    p2: typing.Tuple[Decimal, Decimal] = rotate_point(angle_in_degrees=angle_in_degrees, point=(W, H))
    p3: typing.Tuple[Decimal, Decimal] = rotate_point(angle_in_degrees=angle_in_degrees, point=(W, ZERO))

    # calculate width and height
    w_prime: Decimal = max([p0[0], p1[0], p2[0], p3[0]]) - min([p0[0], p1[0], p2[0], p3[0]])
    h_prime: Decimal = max([p0[1], p1[1], p2[1], p3[1]]) - min([p0[1], p1[1], p2[1], p3[1]])

    # return
    return Rectangle(ZERO, ZERO, w_prime, h_prime)

def delta_of_rotated_rectangle(r: Rectangle,
                               angle_in_degrees: Decimal) -> typing.Tuple[Decimal, Decimal]:
    ZERO: Decimal = Decimal(0)
    W: Decimal = r.get_width()
    H: Decimal = r.get_height()
    p0: typing.Tuple[Decimal, Decimal] = rotate_point(angle_in_degrees=angle_in_degrees, point=(ZERO, ZERO))
    p1: typing.Tuple[Decimal, Decimal] = rotate_point(angle_in_degrees=angle_in_degrees, point=(ZERO, H))
    p2: typing.Tuple[Decimal, Decimal] = rotate_point(angle_in_degrees=angle_in_degrees, point=(W, H))
    p3: typing.Tuple[Decimal, Decimal] = rotate_point(angle_in_degrees=angle_in_degrees, point=(W, ZERO))

    return (-min([p0[0], p1[0], p2[0], p3[0]]),
            -min([p0[1], p1[1], p2[1], p3[1]]))

def largest_inscribed_rectangle(angle_in_degrees: Decimal,
                                r: Rectangle) -> Rectangle:
    max_area: typing.Optional[Decimal] = None
    max_w: typing.Optional[Decimal] = None
    max_h: typing.Optional[Decimal] = None
    for w in range(int(r.get_width() // 2), int(r.get_width()) + 1):
        for h in range(int(r.get_height() // 2), int(r.get_height()) + 1):
            r2: Rectangle = dimensions_of_rotated_rectangle(Rectangle(Decimal(0),
                                                                      Decimal(0),
                                                                      Decimal(w),
                                                                      Decimal(h)),
                                                            angle_in_degrees=angle_in_degrees)
            if r2.get_width() > r.get_width():
                continue
            if r2.get_height() > r.get_height():
                continue
            area: Decimal = r2.get_width() * r2.get_height()
            if max_area is None or area > max_area:
                max_area = area
                max_w = Decimal(w)
                max_h = Decimal(h)

    assert max_w is not None
    assert max_h is not None

    # return
    return Rectangle(Decimal(0),
                     Decimal(0),
                     max_w,
                     max_h)


class RotatedLayoutElement(LayoutElement):

    def __init__(self, angle_in_degrees: Decimal, layout_element: LayoutElement):
        super().__init__()
        self._angle_in_degrees: Decimal = angle_in_degrees
        self._layout_element: LayoutElement = layout_element
        self._prev_content_box: typing.Optional[Rectangle] = None
    #
    # PRIVATE
    #

    def _get_content_box(self, available_space: Rectangle) -> Rectangle:
        r0: Rectangle = largest_inscribed_rectangle(r=available_space,
                                                    angle_in_degrees=self._angle_in_degrees)
        self._prev_inner_content_box = self._layout_element.get_layout_box(r0)
        r2: Rectangle = dimensions_of_rotated_rectangle(self._prev_inner_content_box, self._angle_in_degrees)
        self._prev_content_box = Rectangle(available_space.get_x(),
                                           available_space.get_y() + available_space.get_height() - r2.get_height(),
                                           r2.get_width(),
                                           r2.get_height())
        return self._prev_content_box

    def _paint_content_box(self, page: "Page", content_box: Rectangle) -> None:

        # we are going to do unholy things to the graphics state
        # best to store it before the madness begins
        page.append_to_content_stream(" q ")

        # rotate
        page.append_to_content_stream(f"{round(math.cos(math.radians(self._angle_in_degrees)), 2)} "
                                      f"{round(math.sin(math.radians(self._angle_in_degrees)), 2)} "
                                      f"{round(-math.sin(math.radians(self._angle_in_degrees)), 2)} "
                                      f"{round(math.cos(math.radians(self._angle_in_degrees)), 2)} "
                                      f"0 0 cm ")

        # translate to counteract translation by rotation
        # ensuring the bounding rectangle fits inside the target rectangle
        tx, ty = rotate_point(angle_in_degrees=-self._angle_in_degrees,
                              point=delta_of_rotated_rectangle(self._prev_inner_content_box,
                                                               angle_in_degrees=self._angle_in_degrees))
        page.append_to_content_stream(f"1 0 0 1 {round(tx, 2)} {round(ty, 2)} cm ")

        # translate to point
        tx, ty = rotate_point(point=(content_box.get_x(),
                                     content_box.get_y()), angle_in_degrees=-self._angle_in_degrees)
        page.append_to_content_stream(f"1 0 0 1 {round(tx, 2)} {round(ty, 2)} cm ")

        # paint
        self._layout_element.paint(page, Rectangle(Decimal(0),
                                                   Decimal(0),
                                                   self._prev_inner_content_box.get_width(),
                                                   self._prev_inner_content_box.get_height()))

        # restore graphics state
        page.append_to_content_stream(" Q ")

复制/粘贴该内容,存储为

rotated_layout_element.py

现在您可以使用它来执行以下操作:

from decimal import Decimal

from borb.pdf import Document
from borb.pdf import PDF
from borb.pdf import Page
from borb.pdf import PageLayout
from borb.pdf import Paragraph
from borb.pdf import SingleColumnLayout
from borb.pdf import TableUtil

from rotated_layout_element import RotatedLayoutElement


def main():

    d: Document = Document()

    p: Page = Page()
    d.add_page(p)

    l: PageLayout = SingleColumnLayout(p)

    l.add(Paragraph("27 degrees"))
    l.add(
            RotatedLayoutElement(
                layout_element=TableUtil.from_2d_array([["Lorem", "Ipsum", "Dolor", "Sit", "Amet"],
                                                        [1,2,3,4,5],
                                                        [4,5,6,7,8]]),
                angle_in_degrees=Decimal(27),
            )
        )


    with open("output.pdf", "wb") as fh:
        PDF.dumps(fh, d)


if __name__ == "__main__":
    main()

生成以下 PDF:

enter image description here

© www.soinside.com 2019 - 2024. All rights reserved.