Skip to content

Commit

Permalink
release v2.0.9
Browse files Browse the repository at this point in the history
  • Loading branch information
jorisschellekens committed Aug 29, 2021
1 parent 5b2ab0f commit bb2b70c
Show file tree
Hide file tree
Showing 318 changed files with 388,425 additions and 3,951 deletions.
3,405 changes: 0 additions & 3,405 deletions EXAMPLES.md

This file was deleted.

14 changes: 10 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,18 @@

## 0. About borb

`borb` is a pure python library to read, write and manipulate PDF documents. It represents a PDF document as a JSON-like datastructure of nested lists, dictionaries and primitives (numbers, string, booleans, etc)
`borb` is a pure python library to read, write and manipulate PDF documents.
It represents a PDF document as a JSON-like datastructure of nested lists, dictionaries and primitives (numbers, string, booleans, etc)

This is currently a one-man project, so the focus will always be to support those use-cases that are more common in favor of those that are rare.

## 1. About the Examples

Most examples double as tests, you can find them in the 'tests' directory.
The examples can be found in a separate repository.
This ensures the `borb` repository stays relatively small, whilst still providing a thorough knowledgebase of code-samples, screenshots and explanatory text.

[Check out the examples repository here!](https://github.com/jorisschellekens/borb-examples)

They include;

- Reading a PDF and extracting meta-information
Expand All @@ -36,7 +41,8 @@ They include;

### 1.1 Hello World

To give you an immediate idea of the way `borb` works, this is the classic `Hello World` example, in `borb`:
To give you an immediate idea of the way `borb` works,
this is the classic `Hello World` example, in `borb`:

```python
from pathlib import Path
Expand Down Expand Up @@ -79,7 +85,7 @@ These activities include:
- Serving PDFs on the fly in the cloud or in a web application
- Shipping `borb` with a closed source product

Contact sales for more info.
[Contact sales](https://borbpdf.com/) for more information.

## 3. Acknowledgements

Expand Down
4 changes: 3 additions & 1 deletion borb/io/read/image/read_jbig2_image_transformer.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,9 @@ def transform(
"""

# use PIL to read image bytes
assert isinstance(object_to_transform, Stream)
assert isinstance(
object_to_transform, Stream
), "Image must be represented by Stream data"
try:
tmp = Image.open(io.BytesIO(object_to_transform["Bytes"]))
tmp.getpixel(
Expand Down
4 changes: 3 additions & 1 deletion borb/io/read/image/read_jpeg_image_transformer.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,9 @@ def transform(
"""

# use PIL to read image bytes
assert isinstance(object_to_transform, Stream)
assert isinstance(
object_to_transform, Stream
), "Image must be represented by Stream data"
raw_byte_array = object_to_transform["Bytes"]
try:
tmp = Image.open(io.BytesIO(object_to_transform["Bytes"]))
Expand Down
2 changes: 1 addition & 1 deletion borb/io/write/ascii_art/ascii_logo.txt
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
borb version 2.0.8
borb version 2.0.9
Joris Schellekens
2 changes: 1 addition & 1 deletion borb/io/write/page/write_page_transformer.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ def transform(
# mark some keys as non-referencable
for k in ["ArtBox", "BleedBox", "CropBox", "MediaBox", "TrimBox"]:
if k in object_to_transform:
object_to_transform[k].set_can_be_referenced(False)
object_to_transform[k].set_can_be_referenced(False) # type: ignore [attr-defined]

# delegate to super
super(WritePageTransformer, self).transform(object_to_transform, context)
2 changes: 1 addition & 1 deletion borb/io/write/page/write_pages_transformer.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ def transform(
), "A WriteTransformerState must be defined in order to write Pages Dictionary objects."

# \Kids can be written immediately
object_to_transform[Name("Kids")].set_can_be_referenced(False)
object_to_transform[Name("Kids")].set_can_be_referenced(False) # type: ignore [attr-defined]

# queue writing of \Page objects
queue: typing.List[AnyPDFType] = []
Expand Down
14 changes: 14 additions & 0 deletions borb/io/write/write_pdf_transformer.py
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,20 @@ def transform(
"borb"
)

# set OutputIntents
object_to_transform["XRef"]["Trailer"]["Root"][Name("OutputIntents")] = List()
rgb_output_intent: Dictionary = Dictionary()
rgb_output_intent[Name("Type")] = Name("OutputIntent")
rgb_output_intent[Name("S")] = Name("GTS_PDFA1")
rgb_output_intent[Name("OutputConditionIdentifier")] = String("sRGB")
rgb_output_intent[Name("RegistryName")] = String("http://www.color.org")
rgb_output_intent[Name("Info")] = String(
"Creator:HP Manufacturer:IEC Model:sRGB"
)
object_to_transform["XRef"]["Trailer"]["Root"][Name("OutputIntents")].append(
rgb_output_intent
)

# transform XREF
self.get_root_transformer().transform(object_to_transform["XRef"], context)

Expand Down
2 changes: 1 addition & 1 deletion borb/pdf/canvas/canvas_stream_processor.py
Original file line number Diff line number Diff line change
Expand Up @@ -236,7 +236,7 @@ def read(
canvas_tokenizer = HighLevelTokenizer(io_source)

# process content
operand_stk = []
operand_stk: typing.List[AnyPDFType] = []
instruction_number: int = 0
time_per_operator: typing.Dict[str, float] = {}
calls_per_operator: typing.Dict[str, int] = {}
Expand Down
1 change: 1 addition & 0 deletions borb/pdf/canvas/color/color.py
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,7 @@ def __init__(self, hex_string: str):
r: float = 0
g: float = 0
b: float = 0
a: float = 0
if len(hex_string) == 6:
a = 255
r = int(hex_string[0:2], 16)
Expand Down
43 changes: 43 additions & 0 deletions borb/pdf/canvas/layout/forms/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
"""
This file is part of the borb (R) project.
Copyright (c) 2020-2040 borb Group NV
Authors: Joris Schellekens, et al.
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License version 3
as published by the Free Software Foundation with the addition of the
following permission added to Section 15 as permitted in Section 7(a):
FOR ANY PART OF THE COVERED WORK IN WHICH THE COPYRIGHT IS OWNED BY
BORB GROUP. BORB GROUP DISCLAIMS THE WARRANTY OF NON INFRINGEMENT
OF THIRD PARTY RIGHTS
This program is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
or FITNESS FOR A PARTICULAR PURPOSE.
See the GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program; if not, see http://www.gnu.org/licenses or write to
the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
Boston, MA, 02110-1301 USA.
The interactive user interfaces in modified source and object code versions
of this program must display Appropriate Legal Notices, as required under
Section 5 of the GNU Affero General Public License.
In accordance with Section 7(b) of the GNU Affero General Public License,
a covered work must retain the producer line in every PDF that is created
or manipulated using borb.
You can be released from the requirements of the license by purchasing
a commercial license. Buying such a license is mandatory as soon as you
develop commercial activities involving the borb software without
disclosing the source code of your own applications.
These activities include: offering paid services to customers as an ASP,
serving PDFs on the fly in a web application, shipping borb with a closed
source product.
For more information, please contact borb Software Corp. at this
address: [email protected]
"""
98 changes: 54 additions & 44 deletions borb/pdf/canvas/layout/forms/check_box.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
"""
This implementation of FormField represents a text field.
"""
import copy
import zlib

import typing
Expand Down Expand Up @@ -41,11 +42,17 @@ def __init__(
self._font_size = font_size
self._font_color = font_color
self._field_name: typing.Optional[str] = field_name
self._widget_dictionary: typing.Optional[Dictionary] = None

def _do_layout(self, page: "Page", layout_box: Rectangle) -> Rectangle:
def _init_widget_dictionary(self, page: Page, layout_box: Rectangle) -> None:

if self._widget_dictionary is not None:
return

# init page and font resources
assert self._font_size is not None
font_resource_name: Name = self._get_font_resource_name(
StandardType1Font("Helvetica"), page
StandardType1Font("Zapfdingbats"), page
)

# widget resource dictionary
Expand All @@ -56,31 +63,23 @@ def _do_layout(self, page: "Page", layout_box: Rectangle) -> Rectangle:
widget_off_appearance: Stream = Stream()
widget_off_appearance[Name("Type")] = Name("XObject")
widget_off_appearance[Name("Subtype")] = Name("Form")
widget_off_appearance[Name("BBox")] = List().set_can_be_referenced(False)
widget_off_appearance[Name("BBox")] = List().set_can_be_referenced(False) # type: ignore [attr-defined]
widget_off_appearance["BBox"].append(bDecimal(0))
widget_off_appearance["BBox"].append(bDecimal(0))
widget_off_appearance["BBox"].append(bDecimal(layout_box.width))
widget_off_appearance["BBox"].append(bDecimal(self._font_size))
bts = b"/Tx BMC EMC"
bts = bytes(
"/Tx BMC q 0 0 0 rg BT /%s 12 Tf 0 0 Td (8) Tj ET Q EMC"
% font_resource_name,
"latin1",
)
widget_off_appearance[Name("DecodedBytes")] = bts
widget_off_appearance[Name("Bytes")] = zlib.compress(bts, 9)
widget_off_appearance[Name("Filter")] = Name("FlateDecode")
widget_off_appearance[Name("Length")] = bDecimal(len(bts))

# widget "Yes" appearance
widget_yes_appearance: Stream = Stream()
widget_yes_appearance[Name("Type")] = Name("XObject")
widget_yes_appearance[Name("Subtype")] = Name("Form")
widget_yes_appearance[Name("BBox")] = List().set_can_be_referenced(False)
widget_yes_appearance["BBox"].append(bDecimal(0))
widget_yes_appearance["BBox"].append(bDecimal(0))
widget_yes_appearance["BBox"].append(bDecimal(layout_box.width))
widget_yes_appearance["BBox"].append(bDecimal(self._font_size))
bts = b"/Tx BMC EMC"
widget_yes_appearance[Name("DecodedBytes")] = bts
widget_yes_appearance[Name("Bytes")] = zlib.compress(bts, 9)
widget_yes_appearance[Name("Filter")] = Name("FlateDecode")
widget_yes_appearance[Name("Length")] = bDecimal(len(bts))
widget_yes_appearance: Stream = copy.deepcopy(widget_off_appearance)

# widget normal appearance
widget_normal_appearance: Dictionary = Dictionary()
Expand All @@ -92,38 +91,34 @@ def _do_layout(self, page: "Page", layout_box: Rectangle) -> Rectangle:
widget_appearance_dictionary[Name("N")] = widget_normal_appearance

# get Catalog
catalog: Dictionary = page.get_root()["XRef"]["Trailer"]["Root"]

widget_appearance_characteristics_dictionary: Dictionary = Dictionary()
widget_appearance_characteristics_dictionary[Name("CA")] = String(
"8"
) # clever little hack
catalog: Dictionary = page.get_root()["XRef"]["Trailer"]["Root"] # type: ignore [attr-defined]

# widget dictionary
widget_dictionary: Dictionary = Dictionary()
widget_dictionary[Name("Type")] = Name("Annot")
widget_dictionary[Name("Subtype")] = Name("Widget")
widget_dictionary[Name("F")] = bDecimal(4)
widget_dictionary[Name("Rect")] = List().set_can_be_referenced(False)
widget_dictionary["Rect"].append(bDecimal(layout_box.x))
widget_dictionary["Rect"].append(
self._widget_dictionary = Dictionary()
self._widget_dictionary[Name("Type")] = Name("Annot")
self._widget_dictionary[Name("Subtype")] = Name("Widget")
self._widget_dictionary[Name("F")] = bDecimal(4)
self._widget_dictionary[Name("Rect")] = List().set_can_be_referenced(False) # type: ignore [attr-defined]
self._widget_dictionary["Rect"].append(bDecimal(layout_box.x))
self._widget_dictionary["Rect"].append(
bDecimal(layout_box.y + layout_box.height - self._font_size - 2)
)
widget_dictionary["Rect"].append(bDecimal(layout_box.x + layout_box.width))
widget_dictionary["Rect"].append(bDecimal(layout_box.y + layout_box.height))
widget_dictionary[Name("FT")] = Name("Btn")
widget_dictionary[Name("P")] = catalog
widget_dictionary[
self._widget_dictionary["Rect"].append(
bDecimal(layout_box.x + layout_box.width)
)
self._widget_dictionary["Rect"].append(
bDecimal(layout_box.y + layout_box.height)
)
self._widget_dictionary[Name("FT")] = Name("Btn")
self._widget_dictionary[Name("P")] = catalog
self._widget_dictionary[
Name("T")
] = self._field_name or self._get_auto_generated_field_name(page)
widget_dictionary[Name("V")] = Name("Off")
widget_dictionary[Name("DV")] = Name("Off")
widget_dictionary[Name("AS")] = Name("Off")
widget_dictionary[Name("DR")] = widget_resources
widget_dictionary[Name("MK")] = widget_appearance_characteristics_dictionary
self._widget_dictionary[Name("V")] = Name("Yes")
self._widget_dictionary[Name("DR")] = widget_resources

font_color_rgb: RGBColor = self._font_color.to_rgb()
widget_dictionary[Name("DA")] = String(
self._widget_dictionary[Name("DA")] = String(
"%f %f %f rg /%s %f Tf"
% (
font_color_rgb.red,
Expand All @@ -133,28 +128,43 @@ def _do_layout(self, page: "Page", layout_box: Rectangle) -> Rectangle:
self._font_size,
)
)
widget_dictionary[Name("AP")] = widget_appearance_dictionary
self._widget_dictionary[Name("AP")] = widget_appearance_dictionary

# append field to page /Annots
if "Annots" not in page:
page[Name("Annots")] = List()
page["Annots"].append(widget_dictionary)
page["Annots"].append(self._widget_dictionary)

# append field to catalog
if "AcroForm" not in catalog:
catalog[Name("AcroForm")] = Dictionary()
catalog["AcroForm"][Name("Fields")] = List()
catalog["AcroForm"][Name("DR")] = widget_resources
catalog["AcroForm"][Name("NeedAppearances")] = Boolean(True)
catalog["AcroForm"]["Fields"].append(widget_dictionary)
catalog["AcroForm"]["Fields"].append(self._widget_dictionary)

def _do_layout(self, page: "Page", layout_box: Rectangle) -> Rectangle:

# determine layout rectangle
assert self._font_size is not None
layout_rect = Rectangle(
layout_box.x,
layout_box.y + layout_box.height - self._font_size,
max(layout_box.width, Decimal(64)),
self._font_size + Decimal(10),
)

# init self._widget_dictionary
self._init_widget_dictionary(page, layout_rect)

# set location
assert self._widget_dictionary is not None
self._widget_dictionary["Rect"][0] = bDecimal(layout_box.x)
self._widget_dictionary["Rect"][1] = bDecimal(
layout_box.y + layout_box.height - self._font_size
)
self._widget_dictionary["Rect"][2] = bDecimal(layout_box.x + layout_box.width)
self._widget_dictionary["Rect"][3] = bDecimal(layout_box.y + layout_box.height)

# return Rectangle
return layout_rect
9 changes: 9 additions & 0 deletions borb/pdf/canvas/layout/forms/country_drop_down_list.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-

"""
This implementation of FormField represents a drop down list that is pre-filled with countries.
"""
import typing

from borb.io.read.types import Decimal
Expand All @@ -6,6 +12,9 @@


class CountryDropDownList(DropDownList):
"""
This implementation of FormField represents a drop down list that is pre-filled with countries.
"""

COUNTRIES: typing.List[str] = [
"Afghanistan",
Expand Down
Loading

0 comments on commit bb2b70c

Please sign in to comment.