aboutsummaryrefslogtreecommitdiff
path: root/.venv/lib/python3.12/site-packages/pypdf
diff options
context:
space:
mode:
authorS. Solomon Darnell2025-03-28 21:52:21 -0500
committerS. Solomon Darnell2025-03-28 21:52:21 -0500
commit4a52a71956a8d46fcb7294ac71734504bb09bcc2 (patch)
treeee3dc5af3b6313e921cd920906356f5d4febc4ed /.venv/lib/python3.12/site-packages/pypdf
parentcc961e04ba734dd72309fb548a2f97d67d578813 (diff)
downloadgn-ai-master.tar.gz
two version of R2R are hereHEADmaster
Diffstat (limited to '.venv/lib/python3.12/site-packages/pypdf')
-rw-r--r--.venv/lib/python3.12/site-packages/pypdf/__init__.py49
-rw-r--r--.venv/lib/python3.12/site-packages/pypdf/_cmap.py520
-rw-r--r--.venv/lib/python3.12/site-packages/pypdf/_codecs/__init__.py61
-rw-r--r--.venv/lib/python3.12/site-packages/pypdf/_codecs/adobe_glyphs.py13970
-rw-r--r--.venv/lib/python3.12/site-packages/pypdf/_codecs/pdfdoc.py264
-rw-r--r--.venv/lib/python3.12/site-packages/pypdf/_codecs/std.py258
-rw-r--r--.venv/lib/python3.12/site-packages/pypdf/_codecs/symbol.py260
-rw-r--r--.venv/lib/python3.12/site-packages/pypdf/_codecs/zapfding.py261
-rw-r--r--.venv/lib/python3.12/site-packages/pypdf/_crypt_providers/__init__.py86
-rw-r--r--.venv/lib/python3.12/site-packages/pypdf/_crypt_providers/_base.py38
-rw-r--r--.venv/lib/python3.12/site-packages/pypdf/_crypt_providers/_cryptography.py118
-rw-r--r--.venv/lib/python3.12/site-packages/pypdf/_crypt_providers/_fallback.py93
-rw-r--r--.venv/lib/python3.12/site-packages/pypdf/_crypt_providers/_pycryptodome.py97
-rw-r--r--.venv/lib/python3.12/site-packages/pypdf/_doc_common.py1365
-rw-r--r--.venv/lib/python3.12/site-packages/pypdf/_encryption.py1168
-rw-r--r--.venv/lib/python3.12/site-packages/pypdf/_merger.py678
-rw-r--r--.venv/lib/python3.12/site-packages/pypdf/_page.py2458
-rw-r--r--.venv/lib/python3.12/site-packages/pypdf/_page_labels.py280
-rw-r--r--.venv/lib/python3.12/site-packages/pypdf/_protocols.py89
-rw-r--r--.venv/lib/python3.12/site-packages/pypdf/_reader.py1159
-rw-r--r--.venv/lib/python3.12/site-packages/pypdf/_text_extraction/__init__.py285
-rw-r--r--.venv/lib/python3.12/site-packages/pypdf/_text_extraction/_layout_mode/__init__.py16
-rw-r--r--.venv/lib/python3.12/site-packages/pypdf/_text_extraction/_layout_mode/_fixed_width_page.py381
-rw-r--r--.venv/lib/python3.12/site-packages/pypdf/_text_extraction/_layout_mode/_font.py112
-rw-r--r--.venv/lib/python3.12/site-packages/pypdf/_text_extraction/_layout_mode/_font_widths.py208
-rw-r--r--.venv/lib/python3.12/site-packages/pypdf/_text_extraction/_layout_mode/_text_state_manager.py213
-rw-r--r--.venv/lib/python3.12/site-packages/pypdf/_text_extraction/_layout_mode/_text_state_params.py127
-rw-r--r--.venv/lib/python3.12/site-packages/pypdf/_utils.py683
-rw-r--r--.venv/lib/python3.12/site-packages/pypdf/_version.py1
-rw-r--r--.venv/lib/python3.12/site-packages/pypdf/_writer.py3047
-rw-r--r--.venv/lib/python3.12/site-packages/pypdf/_xobj_image_helpers.py307
-rw-r--r--.venv/lib/python3.12/site-packages/pypdf/annotations/__init__.py45
-rw-r--r--.venv/lib/python3.12/site-packages/pypdf/annotations/_base.py27
-rw-r--r--.venv/lib/python3.12/site-packages/pypdf/annotations/_markup_annotations.py308
-rw-r--r--.venv/lib/python3.12/site-packages/pypdf/annotations/_non_markup_annotations.py109
-rw-r--r--.venv/lib/python3.12/site-packages/pypdf/constants.py762
-rw-r--r--.venv/lib/python3.12/site-packages/pypdf/errors.py62
-rw-r--r--.venv/lib/python3.12/site-packages/pypdf/filters.py910
-rw-r--r--.venv/lib/python3.12/site-packages/pypdf/generic/__init__.py464
-rw-r--r--.venv/lib/python3.12/site-packages/pypdf/generic/_base.py721
-rw-r--r--.venv/lib/python3.12/site-packages/pypdf/generic/_data_structures.py1616
-rw-r--r--.venv/lib/python3.12/site-packages/pypdf/generic/_fit.py168
-rw-r--r--.venv/lib/python3.12/site-packages/pypdf/generic/_image_inline.py235
-rw-r--r--.venv/lib/python3.12/site-packages/pypdf/generic/_outline.py33
-rw-r--r--.venv/lib/python3.12/site-packages/pypdf/generic/_rectangle.py132
-rw-r--r--.venv/lib/python3.12/site-packages/pypdf/generic/_utils.py180
-rw-r--r--.venv/lib/python3.12/site-packages/pypdf/generic/_viewerpref.py164
-rw-r--r--.venv/lib/python3.12/site-packages/pypdf/pagerange.py192
-rw-r--r--.venv/lib/python3.12/site-packages/pypdf/papersizes.py51
-rw-r--r--.venv/lib/python3.12/site-packages/pypdf/py.typed0
-rw-r--r--.venv/lib/python3.12/site-packages/pypdf/types.py83
-rw-r--r--.venv/lib/python3.12/site-packages/pypdf/xmp.py392
52 files changed, 35306 insertions, 0 deletions
diff --git a/.venv/lib/python3.12/site-packages/pypdf/__init__.py b/.venv/lib/python3.12/site-packages/pypdf/__init__.py
new file mode 100644
index 00000000..6a02b60d
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/pypdf/__init__.py
@@ -0,0 +1,49 @@
+"""
+pypdf is a free and open-source pure-python PDF library capable of splitting,
+merging, cropping, and transforming the pages of PDF files. It can also add
+custom data, viewing options, and passwords to PDF files. pypdf can retrieve
+text and metadata from PDFs as well.
+
+You can read the full docs at https://pypdf.readthedocs.io/.
+"""
+
+from ._crypt_providers import crypt_provider
+from ._doc_common import DocumentInformation
+from ._encryption import PasswordType
+from ._merger import PdfMerger
+from ._page import PageObject, Transformation, mult
+from ._reader import PdfReader
+from ._version import __version__
+from ._writer import ObjectDeletionFlag, PdfWriter
+from .constants import ImageType
+from .pagerange import PageRange, parse_filename_page_ranges
+from .papersizes import PaperSize
+
+try:
+ import PIL
+
+ pil_version = PIL.__version__
+except ImportError:
+ pil_version = "none"
+
+_debug_versions = (
+ f"pypdf=={__version__}, crypt_provider={crypt_provider}, PIL={pil_version}"
+)
+
+__all__ = [
+ "__version__",
+ "_debug_versions",
+ "ImageType",
+ "mult",
+ "PageRange",
+ "PaperSize",
+ "DocumentInformation",
+ "ObjectDeletionFlag",
+ "parse_filename_page_ranges",
+ "PdfMerger",
+ "PdfReader",
+ "PdfWriter",
+ "Transformation",
+ "PageObject",
+ "PasswordType",
+]
diff --git a/.venv/lib/python3.12/site-packages/pypdf/_cmap.py b/.venv/lib/python3.12/site-packages/pypdf/_cmap.py
new file mode 100644
index 00000000..9a2d10a6
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/pypdf/_cmap.py
@@ -0,0 +1,520 @@
+from binascii import unhexlify
+from math import ceil
+from typing import Any, Dict, List, Tuple, Union, cast
+
+from ._codecs import adobe_glyphs, charset_encoding
+from ._utils import b_, logger_error, logger_warning
+from .generic import (
+ DecodedStreamObject,
+ DictionaryObject,
+ IndirectObject,
+ NullObject,
+ StreamObject,
+)
+
+
+# code freely inspired from @twiggy ; see #711
+def build_char_map(
+ font_name: str, space_width: float, obj: DictionaryObject
+) -> Tuple[str, float, Union[str, Dict[int, str]], Dict[Any, Any], DictionaryObject]:
+ """
+ Determine information about a font.
+
+ Args:
+ font_name: font name as a string
+ space_width: default space width if no data is found.
+ obj: XObject or Page where you can find a /Resource dictionary
+
+ Returns:
+ Font sub-type, space_width criteria (50% of width), encoding, map character-map, font-dictionary.
+ The font-dictionary itself is suitable for the curious.
+ """
+ ft: DictionaryObject = obj["/Resources"]["/Font"][font_name] # type: ignore
+ font_subtype, font_halfspace, font_encoding, font_map = build_char_map_from_dict(
+ space_width, ft
+ )
+ return font_subtype, font_halfspace, font_encoding, font_map, ft
+
+
+def build_char_map_from_dict(
+ space_width: float, ft: DictionaryObject
+) -> Tuple[str, float, Union[str, Dict[int, str]], Dict[Any, Any]]:
+ """
+ Determine information about a font.
+
+ Args:
+ space_width: default space with if no data found
+ (normally half the width of a character).
+ ft: Font Dictionary
+
+ Returns:
+ Font sub-type, space_width criteria(50% of width), encoding, map character-map.
+ The font-dictionary itself is suitable for the curious.
+ """
+ font_type: str = cast(str, ft["/Subtype"])
+
+ space_code = 32
+ encoding, space_code = parse_encoding(ft, space_code)
+ map_dict, space_code, int_entry = parse_to_unicode(ft, space_code)
+
+ # encoding can be either a string for decode
+ # (on 1,2 or a variable number of bytes) of a char table (for 1 byte only for me)
+ # if empty string, it means it is than encoding field is not present and
+ # we have to select the good encoding from cmap input data
+ if encoding == "":
+ if -1 not in map_dict or map_dict[-1] == 1:
+ # I have not been able to find any rule for no /Encoding nor /ToUnicode
+ # One example shows /Symbol,bold I consider 8 bits encoding default
+ encoding = "charmap"
+ else:
+ encoding = "utf-16-be"
+ # apply rule from PDF ref 1.7 §5.9.1, 1st bullet :
+ # if cmap not empty encoding should be discarded
+ # (here transformed into identity for those characters)
+ # if encoding is an str it is expected to be a identity translation
+ elif isinstance(encoding, dict):
+ for x in int_entry:
+ if x <= 255:
+ encoding[x] = chr(x)
+ try:
+ # override space_width with new params
+ space_width = _default_fonts_space_width[cast(str, ft["/BaseFont"])]
+ except Exception:
+ pass
+ # I consider the space_code is available on one byte
+ if isinstance(space_code, str):
+ try: # one byte
+ sp = space_code.encode("charmap")[0]
+ except Exception:
+ sp = space_code.encode("utf-16-be")
+ sp = sp[0] + 256 * sp[1]
+ else:
+ sp = space_code
+ sp_width = compute_space_width(ft, sp, space_width)
+
+ return (
+ font_type,
+ float(sp_width / 2),
+ encoding,
+ # https://github.com/python/mypy/issues/4374
+ map_dict,
+ )
+
+
+# used when missing data, e.g. font def missing
+unknown_char_map: Tuple[str, float, Union[str, Dict[int, str]], Dict[Any, Any]] = (
+ "Unknown",
+ 9999,
+ dict(zip(range(256), ["�"] * 256)),
+ {},
+)
+
+
+_predefined_cmap: Dict[str, str] = {
+ "/Identity-H": "utf-16-be",
+ "/Identity-V": "utf-16-be",
+ "/GB-EUC-H": "gbk",
+ "/GB-EUC-V": "gbk",
+ "/GBpc-EUC-H": "gb2312",
+ "/GBpc-EUC-V": "gb2312",
+ "/GBK-EUC-H": "gbk",
+ "/GBK-EUC-V": "gbk",
+ "/GBK2K-H": "gb18030",
+ "/GBK2K-V": "gb18030",
+ "/ETen-B5-H": "cp950",
+ "/ETen-B5-V": "cp950",
+ "/ETenms-B5-H": "cp950",
+ "/ETenms-B5-V": "cp950",
+ "/UniCNS-UTF16-H": "utf-16-be",
+ "/UniCNS-UTF16-V": "utf-16-be",
+ # UCS2 in code
+}
+
+# manually extracted from http://mirrors.ctan.org/fonts/adobe/afm/Adobe-Core35_AFMs-229.tar.gz
+_default_fonts_space_width: Dict[str, int] = {
+ "/Courier": 600,
+ "/Courier-Bold": 600,
+ "/Courier-BoldOblique": 600,
+ "/Courier-Oblique": 600,
+ "/Helvetica": 278,
+ "/Helvetica-Bold": 278,
+ "/Helvetica-BoldOblique": 278,
+ "/Helvetica-Oblique": 278,
+ "/Helvetica-Narrow": 228,
+ "/Helvetica-NarrowBold": 228,
+ "/Helvetica-NarrowBoldOblique": 228,
+ "/Helvetica-NarrowOblique": 228,
+ "/Times-Roman": 250,
+ "/Times-Bold": 250,
+ "/Times-BoldItalic": 250,
+ "/Times-Italic": 250,
+ "/Symbol": 250,
+ "/ZapfDingbats": 278,
+}
+
+
+def parse_encoding(
+ ft: DictionaryObject, space_code: int
+) -> Tuple[Union[str, Dict[int, str]], int]:
+ encoding: Union[str, List[str], Dict[int, str]] = []
+ if "/Encoding" not in ft:
+ try:
+ if "/BaseFont" in ft and cast(str, ft["/BaseFont"]) in charset_encoding:
+ encoding = dict(
+ zip(range(256), charset_encoding[cast(str, ft["/BaseFont"])])
+ )
+ else:
+ encoding = "charmap"
+ return encoding, _default_fonts_space_width[cast(str, ft["/BaseFont"])]
+ except Exception:
+ if cast(str, ft["/Subtype"]) == "/Type1":
+ return "charmap", space_code
+ else:
+ return "", space_code
+ enc: Union(str, DictionaryObject) = ft["/Encoding"].get_object() # type: ignore
+ if isinstance(enc, str):
+ try:
+ # already done : enc = NameObject.unnumber(enc.encode()).decode()
+ # for #xx decoding
+ if enc in charset_encoding:
+ encoding = charset_encoding[enc].copy()
+ elif enc in _predefined_cmap:
+ encoding = _predefined_cmap[enc]
+ elif "-UCS2-" in enc:
+ encoding = "utf-16-be"
+ else:
+ raise Exception("not found")
+ except Exception:
+ logger_error(f"Advanced encoding {enc} not implemented yet", __name__)
+ encoding = enc
+ elif isinstance(enc, DictionaryObject) and "/BaseEncoding" in enc:
+ try:
+ encoding = charset_encoding[cast(str, enc["/BaseEncoding"])].copy()
+ except Exception:
+ logger_error(
+ f"Advanced encoding {encoding} not implemented yet",
+ __name__,
+ )
+ encoding = charset_encoding["/StandardCoding"].copy()
+ else:
+ encoding = charset_encoding["/StandardCoding"].copy()
+ if "/Differences" in enc:
+ x: int = 0
+ o: Union[int, str]
+ for o in cast(DictionaryObject, cast(DictionaryObject, enc)["/Differences"]):
+ if isinstance(o, int):
+ x = o
+ else: # isinstance(o,str):
+ try:
+ encoding[x] = adobe_glyphs[o] # type: ignore
+ except Exception:
+ encoding[x] = o # type: ignore
+ if o == " ":
+ space_code = x
+ x += 1
+ if isinstance(encoding, list):
+ encoding = dict(zip(range(256), encoding))
+ return encoding, space_code
+
+
+def parse_to_unicode(
+ ft: DictionaryObject, space_code: int
+) -> Tuple[Dict[Any, Any], int, List[int]]:
+ # will store all translation code
+ # and map_dict[-1] we will have the number of bytes to convert
+ map_dict: Dict[Any, Any] = {}
+
+ # will provide the list of cmap keys as int to correct encoding
+ int_entry: List[int] = []
+
+ if "/ToUnicode" not in ft:
+ if ft.get("/Subtype", "") == "/Type1":
+ return type1_alternative(ft, map_dict, space_code, int_entry)
+ else:
+ return {}, space_code, []
+ process_rg: bool = False
+ process_char: bool = False
+ multiline_rg: Union[
+ None, Tuple[int, int]
+ ] = None # tuple = (current_char, remaining size) ; cf #1285 for example of file
+ cm = prepare_cm(ft)
+ for line in cm.split(b"\n"):
+ process_rg, process_char, multiline_rg = process_cm_line(
+ line.strip(b" \t"),
+ process_rg,
+ process_char,
+ multiline_rg,
+ map_dict,
+ int_entry,
+ )
+
+ for a, value in map_dict.items():
+ if value == " ":
+ space_code = a
+ return map_dict, space_code, int_entry
+
+
+def prepare_cm(ft: DictionaryObject) -> bytes:
+ tu = ft["/ToUnicode"]
+ cm: bytes
+ if isinstance(tu, StreamObject):
+ cm = b_(cast(DecodedStreamObject, ft["/ToUnicode"]).get_data())
+ elif isinstance(tu, str) and tu.startswith("/Identity"):
+ # the full range 0000-FFFF will be processed
+ cm = b"beginbfrange\n<0000> <0001> <0000>\nendbfrange"
+ if isinstance(cm, str):
+ cm = cm.encode()
+ # we need to prepare cm before due to missing return line in pdf printed
+ # to pdf from word
+ cm = (
+ cm.strip()
+ .replace(b"beginbfchar", b"\nbeginbfchar\n")
+ .replace(b"endbfchar", b"\nendbfchar\n")
+ .replace(b"beginbfrange", b"\nbeginbfrange\n")
+ .replace(b"endbfrange", b"\nendbfrange\n")
+ .replace(b"<<", b"\n{\n") # text between << and >> not used but
+ .replace(b">>", b"\n}\n") # some solution to find it back
+ )
+ ll = cm.split(b"<")
+ for i in range(len(ll)):
+ j = ll[i].find(b">")
+ if j >= 0:
+ if j == 0:
+ # string is empty: stash a placeholder here (see below)
+ # see https://github.com/py-pdf/pypdf/issues/1111
+ content = b"."
+ else:
+ content = ll[i][:j].replace(b" ", b"")
+ ll[i] = content + b" " + ll[i][j + 1 :]
+ cm = (
+ (b" ".join(ll))
+ .replace(b"[", b" [ ")
+ .replace(b"]", b" ]\n ")
+ .replace(b"\r", b"\n")
+ )
+ return cm
+
+
+def process_cm_line(
+ line: bytes,
+ process_rg: bool,
+ process_char: bool,
+ multiline_rg: Union[None, Tuple[int, int]],
+ map_dict: Dict[Any, Any],
+ int_entry: List[int],
+) -> Tuple[bool, bool, Union[None, Tuple[int, int]]]:
+ if line == b"" or line[0] == 37: # 37 = %
+ return process_rg, process_char, multiline_rg
+ line = line.replace(b"\t", b" ")
+ if b"beginbfrange" in line:
+ process_rg = True
+ elif b"endbfrange" in line:
+ process_rg = False
+ elif b"beginbfchar" in line:
+ process_char = True
+ elif b"endbfchar" in line:
+ process_char = False
+ elif process_rg:
+ multiline_rg = parse_bfrange(line, map_dict, int_entry, multiline_rg)
+ elif process_char:
+ parse_bfchar(line, map_dict, int_entry)
+ return process_rg, process_char, multiline_rg
+
+
+def parse_bfrange(
+ line: bytes,
+ map_dict: Dict[Any, Any],
+ int_entry: List[int],
+ multiline_rg: Union[None, Tuple[int, int]],
+) -> Union[None, Tuple[int, int]]:
+ lst = [x for x in line.split(b" ") if x]
+ closure_found = False
+ if multiline_rg is not None:
+ fmt = b"%%0%dX" % (map_dict[-1] * 2)
+ a = multiline_rg[0] # a, b not in the current line
+ b = multiline_rg[1]
+ for sq in lst[0:]:
+ if sq == b"]":
+ closure_found = True
+ break
+ map_dict[
+ unhexlify(fmt % a).decode(
+ "charmap" if map_dict[-1] == 1 else "utf-16-be",
+ "surrogatepass",
+ )
+ ] = unhexlify(sq).decode("utf-16-be", "surrogatepass")
+ int_entry.append(a)
+ a += 1
+ else:
+ a = int(lst[0], 16)
+ b = int(lst[1], 16)
+ nbi = max(len(lst[0]), len(lst[1]))
+ map_dict[-1] = ceil(nbi / 2)
+ fmt = b"%%0%dX" % (map_dict[-1] * 2)
+ if lst[2] == b"[":
+ for sq in lst[3:]:
+ if sq == b"]":
+ closure_found = True
+ break
+ map_dict[
+ unhexlify(fmt % a).decode(
+ "charmap" if map_dict[-1] == 1 else "utf-16-be",
+ "surrogatepass",
+ )
+ ] = unhexlify(sq).decode("utf-16-be", "surrogatepass")
+ int_entry.append(a)
+ a += 1
+ else: # case without list
+ c = int(lst[2], 16)
+ fmt2 = b"%%0%dX" % max(4, len(lst[2]))
+ closure_found = True
+ while a <= b:
+ map_dict[
+ unhexlify(fmt % a).decode(
+ "charmap" if map_dict[-1] == 1 else "utf-16-be",
+ "surrogatepass",
+ )
+ ] = unhexlify(fmt2 % c).decode("utf-16-be", "surrogatepass")
+ int_entry.append(a)
+ a += 1
+ c += 1
+ return None if closure_found else (a, b)
+
+
+def parse_bfchar(line: bytes, map_dict: Dict[Any, Any], int_entry: List[int]) -> None:
+ lst = [x for x in line.split(b" ") if x]
+ map_dict[-1] = len(lst[0]) // 2
+ while len(lst) > 1:
+ map_to = ""
+ # placeholder (see above) means empty string
+ if lst[1] != b".":
+ map_to = unhexlify(lst[1]).decode(
+ "charmap" if len(lst[1]) < 4 else "utf-16-be", "surrogatepass"
+ ) # join is here as some cases where the code was split
+ map_dict[
+ unhexlify(lst[0]).decode(
+ "charmap" if map_dict[-1] == 1 else "utf-16-be", "surrogatepass"
+ )
+ ] = map_to
+ int_entry.append(int(lst[0], 16))
+ lst = lst[2:]
+
+
+def compute_space_width(
+ ft: DictionaryObject, space_code: int, space_width: float
+) -> float:
+ sp_width: float = space_width * 2.0 # default value
+ w = []
+ w1 = {}
+ st: int = 0
+ if "/DescendantFonts" in ft: # ft["/Subtype"].startswith("/CIDFontType"):
+ ft1 = ft["/DescendantFonts"][0].get_object() # type: ignore
+ try:
+ w1[-1] = cast(float, ft1["/DW"])
+ except Exception:
+ w1[-1] = 1000.0
+ if "/W" in ft1:
+ w = list(ft1["/W"])
+ else:
+ w = []
+ while len(w) > 0:
+ st = w[0] if isinstance(w[0], int) else w[0].get_object()
+ second = w[1].get_object()
+ if isinstance(second, int):
+ for x in range(st, second):
+ w1[x] = w[2]
+ w = w[3:]
+ elif isinstance(second, list):
+ for y in second:
+ w1[st] = y
+ st += 1
+ w = w[2:]
+ else:
+ logger_warning(
+ "unknown widths : \n" + (ft1["/W"]).__repr__(),
+ __name__,
+ )
+ break
+ try:
+ sp_width = w1[space_code]
+ except Exception:
+ sp_width = (
+ w1[-1] / 2.0
+ ) # if using default we consider space will be only half size
+ elif "/Widths" in ft:
+ w = list(ft["/Widths"]) # type: ignore
+ try:
+ st = cast(int, ft["/FirstChar"])
+ en: int = cast(int, ft["/LastChar"])
+ if st > space_code or en < space_code:
+ raise Exception("Not in range")
+ if w[space_code - st] == 0:
+ raise Exception("null width")
+ sp_width = w[space_code - st]
+ except Exception:
+ if "/FontDescriptor" in ft and "/MissingWidth" in cast(
+ DictionaryObject, ft["/FontDescriptor"]
+ ):
+ sp_width = ft["/FontDescriptor"]["/MissingWidth"] # type: ignore
+ else:
+ # will consider width of char as avg(width)/2
+ m = 0
+ cpt = 0
+ for x in w:
+ if x > 0:
+ m += x
+ cpt += 1
+ sp_width = m / max(1, cpt) / 2
+
+ if isinstance(sp_width, IndirectObject):
+ # According to
+ # 'Table 122 - Entries common to all font descriptors (continued)'
+ # the MissingWidth should be a number, but according to #2286 it can
+ # be an indirect object
+ obj = sp_width.get_object()
+ if obj is None or isinstance(obj, NullObject):
+ return 0.0
+ return obj # type: ignore
+
+ return sp_width
+
+
+def type1_alternative(
+ ft: DictionaryObject,
+ map_dict: Dict[Any, Any],
+ space_code: int,
+ int_entry: List[int],
+) -> Tuple[Dict[Any, Any], int, List[int]]:
+ if "/FontDescriptor" not in ft:
+ return map_dict, space_code, int_entry
+ ft_desc = cast(DictionaryObject, ft["/FontDescriptor"]).get("/FontFile")
+ if ft_desc is None:
+ return map_dict, space_code, int_entry
+ txt = ft_desc.get_object().get_data()
+ txt = txt.split(b"eexec\n")[0] # only clear part
+ txt = txt.split(b"/Encoding")[1] # to get the encoding part
+ lines = txt.replace(b"\r", b"\n").split(b"\n")
+ for li in lines:
+ if li.startswith(b"dup"):
+ words = [_w for _w in li.split(b" ") if _w != b""]
+ if len(words) > 3 and words[3] != b"put":
+ continue
+ try:
+ i = int(words[1])
+ except ValueError: # pragma: no cover
+ continue
+ try:
+ v = adobe_glyphs[words[2].decode()]
+ except KeyError:
+ if words[2].startswith(b"/uni"):
+ try:
+ v = chr(int(words[2][4:], 16))
+ except ValueError: # pragma: no cover
+ continue
+ else:
+ continue
+ if words[2].decode() == b" ":
+ space_code = i
+ map_dict[chr(i)] = v
+ int_entry.append(i)
+ return map_dict, space_code, int_entry
diff --git a/.venv/lib/python3.12/site-packages/pypdf/_codecs/__init__.py b/.venv/lib/python3.12/site-packages/pypdf/_codecs/__init__.py
new file mode 100644
index 00000000..70d8e666
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/pypdf/_codecs/__init__.py
@@ -0,0 +1,61 @@
+from typing import Dict, List
+
+from .adobe_glyphs import adobe_glyphs
+from .pdfdoc import _pdfdoc_encoding
+from .std import _std_encoding
+from .symbol import _symbol_encoding
+from .zapfding import _zapfding_encoding
+
+
+def fill_from_encoding(enc: str) -> List[str]:
+ lst: List[str] = []
+ for x in range(256):
+ try:
+ lst += (bytes((x,)).decode(enc),)
+ except Exception:
+ lst += (chr(x),)
+ return lst
+
+
+def rev_encoding(enc: List[str]) -> Dict[str, int]:
+ rev: Dict[str, int] = {}
+ for i in range(256):
+ char = enc[i]
+ if char == "\u0000":
+ continue
+ assert char not in rev, f"{char} at {i} already at {rev[char]}"
+ rev[char] = i
+ return rev
+
+
+_win_encoding = fill_from_encoding("cp1252")
+_mac_encoding = fill_from_encoding("mac_roman")
+
+
+_win_encoding_rev: Dict[str, int] = rev_encoding(_win_encoding)
+_mac_encoding_rev: Dict[str, int] = rev_encoding(_mac_encoding)
+_symbol_encoding_rev: Dict[str, int] = rev_encoding(_symbol_encoding)
+_zapfding_encoding_rev: Dict[str, int] = rev_encoding(_zapfding_encoding)
+_pdfdoc_encoding_rev: Dict[str, int] = rev_encoding(_pdfdoc_encoding)
+
+
+charset_encoding: Dict[str, List[str]] = {
+ "/StandardCoding": _std_encoding,
+ "/WinAnsiEncoding": _win_encoding,
+ "/MacRomanEncoding": _mac_encoding,
+ "/PDFDocEncoding": _pdfdoc_encoding,
+ "/Symbol": _symbol_encoding,
+ "/ZapfDingbats": _zapfding_encoding,
+}
+
+__all__ = [
+ "adobe_glyphs",
+ "_std_encoding",
+ "_symbol_encoding",
+ "_zapfding_encoding",
+ "_pdfdoc_encoding",
+ "_pdfdoc_encoding_rev",
+ "_win_encoding",
+ "_mac_encoding",
+ "charset_encoding",
+]
diff --git a/.venv/lib/python3.12/site-packages/pypdf/_codecs/adobe_glyphs.py b/.venv/lib/python3.12/site-packages/pypdf/_codecs/adobe_glyphs.py
new file mode 100644
index 00000000..19e2a99c
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/pypdf/_codecs/adobe_glyphs.py
@@ -0,0 +1,13970 @@
+# https://raw.githubusercontent.com/adobe-type-tools/agl-aglfn/master/glyphlist.txt
+
+# converted manually to python
+# Extended with data from GlyphNameFormatter:
+# https://github.com/LettError/glyphNameFormatter
+
+# -----------------------------------------------------------
+# Copyright 2002-2019 Adobe (http://www.adobe.com/).
+#
+# Redistribution and use in source and binary forms, with or
+# without modification, are permitted provided that the
+# following conditions are met:
+#
+# Redistributions of source code must retain the above
+# copyright notice, this list of conditions and the following
+# disclaimer.
+#
+# Redistributions in binary form must reproduce the above
+# copyright notice, this list of conditions and the following
+# disclaimer in the documentation and/or other materials
+# provided with the distribution.
+#
+# Neither the name of Adobe nor the names of its contributors
+# may be used to endorse or promote products derived from this
+# software without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+# CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+# INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+# MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+# NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+# OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+# -----------------------------------------------------------
+# Name: Adobe Glyph List
+# Table version: 2.0
+# Date: September 20, 2002
+# URL: https://github.com/adobe-type-tools/agl-aglfn
+#
+# Format: two semicolon-delimited fields:
+# (1) glyph name--upper/lowercase letters and digits
+# (2) Unicode scalar value--four uppercase hexadecimal digits
+#
+adobe_glyphs = {
+ "/A": "\u0041",
+ "/AA": "\uA732",
+ "/AE": "\u00C6",
+ "/AEacute": "\u01FC",
+ "/AEmacron": "\u01E2",
+ "/AEsmall": "\uF7E6",
+ "/AO": "\uA734",
+ "/AU": "\uA736",
+ "/AV": "\uA738",
+ "/AVhorizontalbar": "\uA73A",
+ "/AY": "\uA73C",
+ "/Aacute": "\u00C1",
+ "/Aacutesmall": "\uF7E1",
+ "/Abreve": "\u0102",
+ "/Abreveacute": "\u1EAE",
+ "/Abrevecyr": "\u04D0",
+ "/Abrevecyrillic": "\u04D0",
+ "/Abrevedotbelow": "\u1EB6",
+ "/Abrevegrave": "\u1EB0",
+ "/Abrevehoi": "\u1EB2",
+ "/Abrevehookabove": "\u1EB2",
+ "/Abrevetilde": "\u1EB4",
+ "/Acaron": "\u01CD",
+ "/Acircle": "\u24B6",
+ "/Acircleblack": "\u1F150",
+ "/Acircumflex": "\u00C2",
+ "/Acircumflexacute": "\u1EA4",
+ "/Acircumflexdotbelow": "\u1EAC",
+ "/Acircumflexgrave": "\u1EA6",
+ "/Acircumflexhoi": "\u1EA8",
+ "/Acircumflexhookabove": "\u1EA8",
+ "/Acircumflexsmall": "\uF7E2",
+ "/Acircumflextilde": "\u1EAA",
+ "/Acute": "\uF6C9",
+ "/Acutesmall": "\uF7B4",
+ "/Acyr": "\u0410",
+ "/Acyrillic": "\u0410",
+ "/Adblgrave": "\u0200",
+ "/Adieresis": "\u00C4",
+ "/Adieresiscyr": "\u04D2",
+ "/Adieresiscyrillic": "\u04D2",
+ "/Adieresismacron": "\u01DE",
+ "/Adieresissmall": "\uF7E4",
+ "/Adot": "\u0226",
+ "/Adotbelow": "\u1EA0",
+ "/Adotmacron": "\u01E0",
+ "/Agrave": "\u00C0",
+ "/Agravedbl": "\u0200",
+ "/Agravesmall": "\uF7E0",
+ "/Ahoi": "\u1EA2",
+ "/Ahookabove": "\u1EA2",
+ "/Aiecyr": "\u04D4",
+ "/Aiecyrillic": "\u04D4",
+ "/Ainvertedbreve": "\u0202",
+ "/Akbar": "\uFDF3",
+ "/Alayhe": "\uFDF7",
+ "/Allah": "\uFDF2",
+ "/Alpha": "\u0391",
+ "/Alphaacute": "\u1FBB",
+ "/Alphaasper": "\u1F09",
+ "/Alphaasperacute": "\u1F0D",
+ "/Alphaasperacuteiotasub": "\u1F8D",
+ "/Alphaaspergrave": "\u1F0B",
+ "/Alphaaspergraveiotasub": "\u1F8B",
+ "/Alphaasperiotasub": "\u1F89",
+ "/Alphaaspertilde": "\u1F0F",
+ "/Alphaaspertildeiotasub": "\u1F8F",
+ "/Alphabreve": "\u1FB8",
+ "/Alphagrave": "\u1FBA",
+ "/Alphaiotasub": "\u1FBC",
+ "/Alphalenis": "\u1F08",
+ "/Alphalenisacute": "\u1F0C",
+ "/Alphalenisacuteiotasub": "\u1F8C",
+ "/Alphalenisgrave": "\u1F0A",
+ "/Alphalenisgraveiotasub": "\u1F8A",
+ "/Alphalenisiotasub": "\u1F88",
+ "/Alphalenistilde": "\u1F0E",
+ "/Alphalenistildeiotasub": "\u1F8E",
+ "/Alphatonos": "\u0386",
+ "/Alphawithmacron": "\u1FB9",
+ "/Amacron": "\u0100",
+ "/Amonospace": "\uFF21",
+ "/Aogonek": "\u0104",
+ "/Aparens": "\u1F110",
+ "/Aring": "\u00C5",
+ "/Aringacute": "\u01FA",
+ "/Aringbelow": "\u1E00",
+ "/Aringsmall": "\uF7E5",
+ "/Asmall": "\uF761",
+ "/Asquare": "\u1F130",
+ "/Asquareblack": "\u1F170",
+ "/Astroke": "\u023A",
+ "/Atilde": "\u00C3",
+ "/Atildesmall": "\uF7E3",
+ "/Aturned": "\u2C6F",
+ "/Ayahend": "\u06DD",
+ "/Aybarmenian": "\u0531",
+ "/B": "\u0042",
+ "/Bcircle": "\u24B7",
+ "/Bcircleblack": "\u1F151",
+ "/Bdot": "\u1E02",
+ "/Bdotaccent": "\u1E02",
+ "/Bdotbelow": "\u1E04",
+ "/Becyr": "\u0411",
+ "/Becyrillic": "\u0411",
+ "/Benarmenian": "\u0532",
+ "/Beta": "\u0392",
+ "/Bflourish": "\uA796",
+ "/Bhook": "\u0181",
+ "/BismillahArRahmanArRaheem": "\uFDFD",
+ "/Blinebelow": "\u1E06",
+ "/Bmonospace": "\uFF22",
+ "/Bparens": "\u1F111",
+ "/Brevesmall": "\uF6F4",
+ "/Bscript": "\u212C",
+ "/Bsmall": "\uF762",
+ "/Bsquare": "\u1F131",
+ "/Bsquareblack": "\u1F171",
+ "/Bstroke": "\u0243",
+ "/Btopbar": "\u0182",
+ "/C": "\u0043",
+ "/CDcircle": "\u1F12D",
+ "/Caarmenian": "\u053E",
+ "/Cacute": "\u0106",
+ "/Caron": "\uF6CA",
+ "/Caronsmall": "\uF6F5",
+ "/Cbar": "\uA792",
+ "/Ccaron": "\u010C",
+ "/Ccedilla": "\u00C7",
+ "/Ccedillaacute": "\u1E08",
+ "/Ccedillasmall": "\uF7E7",
+ "/Ccircle": "\u24B8",
+ "/Ccircleblack": "\u1F152",
+ "/Ccircumflex": "\u0108",
+ "/Cdblstruck": "\u2102",
+ "/Cdot": "\u010A",
+ "/Cdotaccent": "\u010A",
+ "/Cdotreversed": "\uA73E",
+ "/Cedillasmall": "\uF7B8",
+ "/Cfraktur": "\u212D",
+ "/Chaarmenian": "\u0549",
+ "/Cheabkhasiancyrillic": "\u04BC",
+ "/Cheabkhcyr": "\u04BC",
+ "/Cheabkhtailcyr": "\u04BE",
+ "/Checyr": "\u0427",
+ "/Checyrillic": "\u0427",
+ "/Chedescenderabkhasiancyrillic": "\u04BE",
+ "/Chedescendercyrillic": "\u04B6",
+ "/Chedieresiscyr": "\u04F4",
+ "/Chedieresiscyrillic": "\u04F4",
+ "/Cheharmenian": "\u0543",
+ "/Chekhakascyr": "\u04CB",
+ "/Chekhakassiancyrillic": "\u04CB",
+ "/Chetailcyr": "\u04B6",
+ "/Chevertcyr": "\u04B8",
+ "/Cheverticalstrokecyrillic": "\u04B8",
+ "/Chi": "\u03A7",
+ "/Chook": "\u0187",
+ "/Circumflexsmall": "\uF6F6",
+ "/Citaliccircle": "\u1F12B",
+ "/Cmonospace": "\uFF23",
+ "/Coarmenian": "\u0551",
+ "/Con": "\uA76E",
+ "/Cparens": "\u1F112",
+ "/Csmall": "\uF763",
+ "/Csquare": "\u1F132",
+ "/Csquareblack": "\u1F172",
+ "/Cstretched": "\u0297",
+ "/Cstroke": "\u023B",
+ "/Cuatrillo": "\uA72C",
+ "/Cuatrillocomma": "\uA72E",
+ "/D": "\u0044",
+ "/DZ": "\u01F1",
+ "/DZcaron": "\u01C4",
+ "/Daarmenian": "\u0534",
+ "/Dafrican": "\u0189",
+ "/Dcaron": "\u010E",
+ "/Dcedilla": "\u1E10",
+ "/Dchecyr": "\u052C",
+ "/Dcircle": "\u24B9",
+ "/Dcircleblack": "\u1F153",
+ "/Dcircumflexbelow": "\u1E12",
+ "/Dcroat": "\u0110",
+ "/Ddblstruckitalic": "\u2145",
+ "/Ddot": "\u1E0A",
+ "/Ddotaccent": "\u1E0A",
+ "/Ddotbelow": "\u1E0C",
+ "/Decyr": "\u0414",
+ "/Decyrillic": "\u0414",
+ "/Deicoptic": "\u03EE",
+ "/Dekomicyr": "\u0500",
+ "/Delta": "\u2206",
+ "/Deltagreek": "\u0394",
+ "/Dhook": "\u018A",
+ "/Dieresis": "\uF6CB",
+ "/DieresisAcute": "\uF6CC",
+ "/DieresisGrave": "\uF6CD",
+ "/Dieresissmall": "\uF7A8",
+ "/Digamma": "\u03DC",
+ "/Digammagreek": "\u03DC",
+ "/Digammapamphylian": "\u0376",
+ "/Dinsular": "\uA779",
+ "/Djecyr": "\u0402",
+ "/Djecyrillic": "\u0402",
+ "/Djekomicyr": "\u0502",
+ "/Dlinebelow": "\u1E0E",
+ "/Dmonospace": "\uFF24",
+ "/Dotaccentsmall": "\uF6F7",
+ "/Dparens": "\u1F113",
+ "/Dslash": "\u0110",
+ "/Dsmall": "\uF764",
+ "/Dsquare": "\u1F133",
+ "/Dsquareblack": "\u1F173",
+ "/Dtopbar": "\u018B",
+ "/Dz": "\u01F2",
+ "/Dzcaron": "\u01C5",
+ "/Dzeabkhasiancyrillic": "\u04E0",
+ "/Dzeabkhcyr": "\u04E0",
+ "/Dzecyr": "\u0405",
+ "/Dzecyrillic": "\u0405",
+ "/Dzhecyr": "\u040F",
+ "/Dzhecyrillic": "\u040F",
+ "/Dzjekomicyr": "\u0506",
+ "/Dzzhecyr": "\u052A",
+ "/E": "\u0045",
+ "/Eacute": "\u00C9",
+ "/Eacutesmall": "\uF7E9",
+ "/Ebreve": "\u0114",
+ "/Ecaron": "\u011A",
+ "/Ecedilla": "\u0228",
+ "/Ecedillabreve": "\u1E1C",
+ "/Echarmenian": "\u0535",
+ "/Ecircle": "\u24BA",
+ "/Ecircleblack": "\u1F154",
+ "/Ecircumflex": "\u00CA",
+ "/Ecircumflexacute": "\u1EBE",
+ "/Ecircumflexbelow": "\u1E18",
+ "/Ecircumflexdotbelow": "\u1EC6",
+ "/Ecircumflexgrave": "\u1EC0",
+ "/Ecircumflexhoi": "\u1EC2",
+ "/Ecircumflexhookabove": "\u1EC2",
+ "/Ecircumflexsmall": "\uF7EA",
+ "/Ecircumflextilde": "\u1EC4",
+ "/Ecyrillic": "\u0404",
+ "/Edblgrave": "\u0204",
+ "/Edieresis": "\u00CB",
+ "/Edieresissmall": "\uF7EB",
+ "/Edot": "\u0116",
+ "/Edotaccent": "\u0116",
+ "/Edotbelow": "\u1EB8",
+ "/Efcyr": "\u0424",
+ "/Efcyrillic": "\u0424",
+ "/Egrave": "\u00C8",
+ "/Egravedbl": "\u0204",
+ "/Egravesmall": "\uF7E8",
+ "/Egyptain": "\uA724",
+ "/Egyptalef": "\uA722",
+ "/Eharmenian": "\u0537",
+ "/Ehoi": "\u1EBA",
+ "/Ehookabove": "\u1EBA",
+ "/Eightroman": "\u2167",
+ "/Einvertedbreve": "\u0206",
+ "/Eiotifiedcyr": "\u0464",
+ "/Eiotifiedcyrillic": "\u0464",
+ "/Elcyr": "\u041B",
+ "/Elcyrillic": "\u041B",
+ "/Elevenroman": "\u216A",
+ "/Elhookcyr": "\u0512",
+ "/Elmiddlehookcyr": "\u0520",
+ "/Elsharptailcyr": "\u04C5",
+ "/Eltailcyr": "\u052E",
+ "/Emacron": "\u0112",
+ "/Emacronacute": "\u1E16",
+ "/Emacrongrave": "\u1E14",
+ "/Emcyr": "\u041C",
+ "/Emcyrillic": "\u041C",
+ "/Emonospace": "\uFF25",
+ "/Emsharptailcyr": "\u04CD",
+ "/Encyr": "\u041D",
+ "/Encyrillic": "\u041D",
+ "/Endescendercyrillic": "\u04A2",
+ "/Eng": "\u014A",
+ "/Engecyr": "\u04A4",
+ "/Enghecyrillic": "\u04A4",
+ "/Enhookcyr": "\u04C7",
+ "/Enhookcyrillic": "\u04C7",
+ "/Enhookleftcyr": "\u0528",
+ "/Enmiddlehookcyr": "\u0522",
+ "/Ensharptailcyr": "\u04C9",
+ "/Entailcyr": "\u04A2",
+ "/Eogonek": "\u0118",
+ "/Eopen": "\u0190",
+ "/Eparens": "\u1F114",
+ "/Epsilon": "\u0395",
+ "/Epsilonacute": "\u1FC9",
+ "/Epsilonasper": "\u1F19",
+ "/Epsilonasperacute": "\u1F1D",
+ "/Epsilonaspergrave": "\u1F1B",
+ "/Epsilongrave": "\u1FC8",
+ "/Epsilonlenis": "\u1F18",
+ "/Epsilonlenisacute": "\u1F1C",
+ "/Epsilonlenisgrave": "\u1F1A",
+ "/Epsilontonos": "\u0388",
+ "/Ercyr": "\u0420",
+ "/Ercyrillic": "\u0420",
+ "/Ereversed": "\u018E",
+ "/Ereversedcyr": "\u042D",
+ "/Ereversedcyrillic": "\u042D",
+ "/Ereverseddieresiscyr": "\u04EC",
+ "/Ereversedopen": "\uA7AB",
+ "/Ertickcyr": "\u048E",
+ "/Escript": "\u2130",
+ "/Escyr": "\u0421",
+ "/Escyrillic": "\u0421",
+ "/Esdescendercyrillic": "\u04AA",
+ "/Esh": "\u01A9",
+ "/Esmall": "\uF765",
+ "/Esmallturned": "\u2C7B",
+ "/Esquare": "\u1F134",
+ "/Esquareblack": "\u1F174",
+ "/Estailcyr": "\u04AA",
+ "/Estroke": "\u0246",
+ "/Et": "\uA76A",
+ "/Eta": "\u0397",
+ "/Etaacute": "\u1FCB",
+ "/Etaasper": "\u1F29",
+ "/Etaasperacute": "\u1F2D",
+ "/Etaasperacuteiotasub": "\u1F9D",
+ "/Etaaspergrave": "\u1F2B",
+ "/Etaaspergraveiotasub": "\u1F9B",
+ "/Etaasperiotasub": "\u1F99",
+ "/Etaaspertilde": "\u1F2F",
+ "/Etaaspertildeiotasub": "\u1F9F",
+ "/Etagrave": "\u1FCA",
+ "/Etaiotasub": "\u1FCC",
+ "/Etalenis": "\u1F28",
+ "/Etalenisacute": "\u1F2C",
+ "/Etalenisacuteiotasub": "\u1F9C",
+ "/Etalenisgrave": "\u1F2A",
+ "/Etalenisgraveiotasub": "\u1F9A",
+ "/Etalenisiotasub": "\u1F98",
+ "/Etalenistilde": "\u1F2E",
+ "/Etalenistildeiotasub": "\u1F9E",
+ "/Etarmenian": "\u0538",
+ "/Etatonos": "\u0389",
+ "/Eth": "\u00D0",
+ "/Ethsmall": "\uF7F0",
+ "/Etilde": "\u1EBC",
+ "/Etildebelow": "\u1E1A",
+ "/Eukrcyr": "\u0404",
+ "/Euro": "\u20AC",
+ "/Ezh": "\u01B7",
+ "/Ezhcaron": "\u01EE",
+ "/Ezhreversed": "\u01B8",
+ "/F": "\u0046",
+ "/Fcircle": "\u24BB",
+ "/Fcircleblack": "\u1F155",
+ "/Fdot": "\u1E1E",
+ "/Fdotaccent": "\u1E1E",
+ "/Feharmenian": "\u0556",
+ "/Feicoptic": "\u03E4",
+ "/Fhook": "\u0191",
+ "/Finsular": "\uA77B",
+ "/Fitacyr": "\u0472",
+ "/Fitacyrillic": "\u0472",
+ "/Fiveroman": "\u2164",
+ "/Fmonospace": "\uFF26",
+ "/Fourroman": "\u2163",
+ "/Fparens": "\u1F115",
+ "/Fscript": "\u2131",
+ "/Fsmall": "\uF766",
+ "/Fsquare": "\u1F135",
+ "/Fsquareblack": "\u1F175",
+ "/Fstroke": "\uA798",
+ "/Fturned": "\u2132",
+ "/G": "\u0047",
+ "/GBsquare": "\u3387",
+ "/Gacute": "\u01F4",
+ "/Gamma": "\u0393",
+ "/Gammaafrican": "\u0194",
+ "/Gammadblstruck": "\u213E",
+ "/Gangiacoptic": "\u03EA",
+ "/Gbreve": "\u011E",
+ "/Gcaron": "\u01E6",
+ "/Gcedilla": "\u0122",
+ "/Gcircle": "\u24BC",
+ "/Gcircleblack": "\u1F156",
+ "/Gcircumflex": "\u011C",
+ "/Gcommaaccent": "\u0122",
+ "/Gdot": "\u0120",
+ "/Gdotaccent": "\u0120",
+ "/Gecyr": "\u0413",
+ "/Gecyrillic": "\u0413",
+ "/Gehookcyr": "\u0494",
+ "/Gehookstrokecyr": "\u04FA",
+ "/Germandbls": "\u1E9E",
+ "/Gestrokecyr": "\u0492",
+ "/Getailcyr": "\u04F6",
+ "/Geupcyr": "\u0490",
+ "/Ghadarmenian": "\u0542",
+ "/Ghemiddlehookcyrillic": "\u0494",
+ "/Ghestrokecyrillic": "\u0492",
+ "/Gheupturncyrillic": "\u0490",
+ "/Ghook": "\u0193",
+ "/Ghooksmall": "\u029B",
+ "/Gimarmenian": "\u0533",
+ "/Ginsular": "\uA77D",
+ "/Ginsularturned": "\uA77E",
+ "/Gjecyr": "\u0403",
+ "/Gjecyrillic": "\u0403",
+ "/Glottalstop": "\u0241",
+ "/Gmacron": "\u1E20",
+ "/Gmonospace": "\uFF27",
+ "/Gobliquestroke": "\uA7A0",
+ "/Gparens": "\u1F116",
+ "/Grave": "\uF6CE",
+ "/Gravesmall": "\uF760",
+ "/Gsmall": "\uF767",
+ "/Gsmallhook": "\u029B",
+ "/Gsquare": "\u1F136",
+ "/Gsquareblack": "\u1F176",
+ "/Gstroke": "\u01E4",
+ "/Gturnedsans": "\u2141",
+ "/H": "\u0048",
+ "/H18533": "\u25CF",
+ "/H18543": "\u25AA",
+ "/H18551": "\u25AB",
+ "/H22073": "\u25A1",
+ "/HPsquare": "\u33CB",
+ "/HVsquare": "\u1F14A",
+ "/Haabkhasiancyrillic": "\u04A8",
+ "/Haabkhcyr": "\u04A8",
+ "/Hacyr": "\u0425",
+ "/Hadescendercyrillic": "\u04B2",
+ "/Hahookcyr": "\u04FC",
+ "/Hardcyr": "\u042A",
+ "/Hardsigncyrillic": "\u042A",
+ "/Hastrokecyr": "\u04FE",
+ "/Hbar": "\u0126",
+ "/Hbrevebelow": "\u1E2A",
+ "/Hcaron": "\u021E",
+ "/Hcedilla": "\u1E28",
+ "/Hcircle": "\u24BD",
+ "/Hcircleblack": "\u1F157",
+ "/Hcircumflex": "\u0124",
+ "/Hdblstruck": "\u210D",
+ "/Hdescender": "\u2C67",
+ "/Hdieresis": "\u1E26",
+ "/Hdot": "\u1E22",
+ "/Hdotaccent": "\u1E22",
+ "/Hdotbelow": "\u1E24",
+ "/Heng": "\uA726",
+ "/Heta": "\u0370",
+ "/Hfraktur": "\u210C",
+ "/Hgfullwidth": "\u32CC",
+ "/Hhalf": "\u2C75",
+ "/Hhook": "\uA7AA",
+ "/Hmonospace": "\uFF28",
+ "/Hoarmenian": "\u0540",
+ "/HonAA": "\u0611",
+ "/HonRA": "\u0612",
+ "/HonSAW": "\u0610",
+ "/Horicoptic": "\u03E8",
+ "/Hparens": "\u1F117",
+ "/Hscript": "\u210B",
+ "/Hsmall": "\uF768",
+ "/Hsquare": "\u1F137",
+ "/Hsquareblack": "\u1F177",
+ "/Hstrokemod": "\uA7F8",
+ "/Hturned": "\uA78D",
+ "/Hungarumlaut": "\uF6CF",
+ "/Hungarumlautsmall": "\uF6F8",
+ "/Hwair": "\u01F6",
+ "/Hzsquare": "\u3390",
+ "/I": "\u0049",
+ "/IAcyrillic": "\u042F",
+ "/ICsquareblack": "\u1F18B",
+ "/IJ": "\u0132",
+ "/IUcyrillic": "\u042E",
+ "/Iacute": "\u00CD",
+ "/Iacutesmall": "\uF7ED",
+ "/Ibreve": "\u012C",
+ "/Icaron": "\u01CF",
+ "/Icircle": "\u24BE",
+ "/Icircleblack": "\u1F158",
+ "/Icircumflex": "\u00CE",
+ "/Icircumflexsmall": "\uF7EE",
+ "/Icyr": "\u0418",
+ "/Icyrillic": "\u0406",
+ "/Idblgrave": "\u0208",
+ "/Idieresis": "\u00CF",
+ "/Idieresisacute": "\u1E2E",
+ "/Idieresiscyr": "\u04E4",
+ "/Idieresiscyrillic": "\u04E4",
+ "/Idieresissmall": "\uF7EF",
+ "/Idot": "\u0130",
+ "/Idotaccent": "\u0130",
+ "/Idotbelow": "\u1ECA",
+ "/Iebrevecyr": "\u04D6",
+ "/Iebrevecyrillic": "\u04D6",
+ "/Iecyr": "\u0415",
+ "/Iecyrillic": "\u0415",
+ "/Iegravecyr": "\u0400",
+ "/Ifraktur": "\u2111",
+ "/Igrave": "\u00CC",
+ "/Igravecyr": "\u040D",
+ "/Igravedbl": "\u0208",
+ "/Igravesmall": "\uF7EC",
+ "/Ihoi": "\u1EC8",
+ "/Ihookabove": "\u1EC8",
+ "/Iicyrillic": "\u0418",
+ "/Iinvertedbreve": "\u020A",
+ "/Iishortcyrillic": "\u0419",
+ "/Imacron": "\u012A",
+ "/Imacroncyr": "\u04E2",
+ "/Imacroncyrillic": "\u04E2",
+ "/Imonospace": "\uFF29",
+ "/Iniarmenian": "\u053B",
+ "/Iocyr": "\u0401",
+ "/Iocyrillic": "\u0401",
+ "/Iogonek": "\u012E",
+ "/Iota": "\u0399",
+ "/Iotaacute": "\u1FDB",
+ "/Iotaafrican": "\u0196",
+ "/Iotaasper": "\u1F39",
+ "/Iotaasperacute": "\u1F3D",
+ "/Iotaaspergrave": "\u1F3B",
+ "/Iotaaspertilde": "\u1F3F",
+ "/Iotabreve": "\u1FD8",
+ "/Iotadieresis": "\u03AA",
+ "/Iotagrave": "\u1FDA",
+ "/Iotalenis": "\u1F38",
+ "/Iotalenisacute": "\u1F3C",
+ "/Iotalenisgrave": "\u1F3A",
+ "/Iotalenistilde": "\u1F3E",
+ "/Iotatonos": "\u038A",
+ "/Iotawithmacron": "\u1FD9",
+ "/Iparens": "\u1F118",
+ "/Is": "\uA76C",
+ "/Iscript": "\u2110",
+ "/Ishortcyr": "\u0419",
+ "/Ishortsharptailcyr": "\u048A",
+ "/Ismall": "\uF769",
+ "/Isquare": "\u1F138",
+ "/Isquareblack": "\u1F178",
+ "/Istroke": "\u0197",
+ "/Itilde": "\u0128",
+ "/Itildebelow": "\u1E2C",
+ "/Iukrcyr": "\u0406",
+ "/Izhitsacyr": "\u0474",
+ "/Izhitsacyrillic": "\u0474",
+ "/Izhitsadblgravecyrillic": "\u0476",
+ "/Izhitsagravedblcyr": "\u0476",
+ "/J": "\u004A",
+ "/Jaarmenian": "\u0541",
+ "/Jallajalalouhou": "\uFDFB",
+ "/Jcircle": "\u24BF",
+ "/Jcircleblack": "\u1F159",
+ "/Jcircumflex": "\u0134",
+ "/Jcrossed-tail": "\uA7B2",
+ "/Jecyr": "\u0408",
+ "/Jecyrillic": "\u0408",
+ "/Jheharmenian": "\u054B",
+ "/Jmonospace": "\uFF2A",
+ "/Jparens": "\u1F119",
+ "/Jsmall": "\uF76A",
+ "/Jsquare": "\u1F139",
+ "/Jsquareblack": "\u1F179",
+ "/Jstroke": "\u0248",
+ "/K": "\u004B",
+ "/KBsquare": "\u3385",
+ "/KKsquare": "\u33CD",
+ "/KORONIS": "\u1FBD",
+ "/Kaaleutcyr": "\u051E",
+ "/Kabashkcyr": "\u04A0",
+ "/Kabashkircyrillic": "\u04A0",
+ "/Kacute": "\u1E30",
+ "/Kacyr": "\u041A",
+ "/Kacyrillic": "\u041A",
+ "/Kadescendercyrillic": "\u049A",
+ "/Kahookcyr": "\u04C3",
+ "/Kahookcyrillic": "\u04C3",
+ "/Kaisymbol": "\u03CF",
+ "/Kappa": "\u039A",
+ "/Kastrokecyr": "\u049E",
+ "/Kastrokecyrillic": "\u049E",
+ "/Katailcyr": "\u049A",
+ "/Kaverticalstrokecyr": "\u049C",
+ "/Kaverticalstrokecyrillic": "\u049C",
+ "/Kcaron": "\u01E8",
+ "/Kcedilla": "\u0136",
+ "/Kcircle": "\u24C0",
+ "/Kcircleblack": "\u1F15A",
+ "/Kcommaaccent": "\u0136",
+ "/Kdescender": "\u2C69",
+ "/Kdiagonalstroke": "\uA742",
+ "/Kdotbelow": "\u1E32",
+ "/Keharmenian": "\u0554",
+ "/Kenarmenian": "\u053F",
+ "/Khacyrillic": "\u0425",
+ "/Kheicoptic": "\u03E6",
+ "/Khook": "\u0198",
+ "/Kjecyr": "\u040C",
+ "/Kjecyrillic": "\u040C",
+ "/Klinebelow": "\u1E34",
+ "/Kmonospace": "\uFF2B",
+ "/Kobliquestroke": "\uA7A2",
+ "/Koppa": "\u03DE",
+ "/Koppaarchaic": "\u03D8",
+ "/Koppacyr": "\u0480",
+ "/Koppacyrillic": "\u0480",
+ "/Koppagreek": "\u03DE",
+ "/Kparens": "\u1F11A",
+ "/Ksicyr": "\u046E",
+ "/Ksicyrillic": "\u046E",
+ "/Ksmall": "\uF76B",
+ "/Ksquare": "\u1F13A",
+ "/Ksquareblack": "\u1F17A",
+ "/Kstroke": "\uA740",
+ "/Kstrokediagonalstroke": "\uA744",
+ "/Kturned": "\uA7B0",
+ "/L": "\u004C",
+ "/LJ": "\u01C7",
+ "/LL": "\uF6BF",
+ "/LLwelsh": "\u1EFA",
+ "/LTDfullwidth": "\u32CF",
+ "/Lacute": "\u0139",
+ "/Lambda": "\u039B",
+ "/Lbar": "\u023D",
+ "/Lbelt": "\uA7AD",
+ "/Lbroken": "\uA746",
+ "/Lcaron": "\u013D",
+ "/Lcedilla": "\u013B",
+ "/Lcircle": "\u24C1",
+ "/Lcircleblack": "\u1F15B",
+ "/Lcircumflexbelow": "\u1E3C",
+ "/Lcommaaccent": "\u013B",
+ "/Ldblbar": "\u2C60",
+ "/Ldot": "\u013F",
+ "/Ldotaccent": "\u013F",
+ "/Ldotbelow": "\u1E36",
+ "/Ldotbelowmacron": "\u1E38",
+ "/Lhacyr": "\u0514",
+ "/Liwnarmenian": "\u053C",
+ "/Lj": "\u01C8",
+ "/Ljecyr": "\u0409",
+ "/Ljecyrillic": "\u0409",
+ "/Ljekomicyr": "\u0508",
+ "/Llinebelow": "\u1E3A",
+ "/Lmacrondot": "\u1E38",
+ "/Lmiddletilde": "\u2C62",
+ "/Lmonospace": "\uFF2C",
+ "/Lparens": "\u1F11B",
+ "/Lreversedsans": "\u2143",
+ "/Lscript": "\u2112",
+ "/Lslash": "\u0141",
+ "/Lslashsmall": "\uF6F9",
+ "/Lsmall": "\uF76C",
+ "/Lsquare": "\u1F13B",
+ "/Lsquareblack": "\u1F17B",
+ "/Lstroke": "\uA748",
+ "/Lturned": "\uA780",
+ "/Lturnedsans": "\u2142",
+ "/M": "\u004D",
+ "/MBsquare": "\u3386",
+ "/MVsquare": "\u1F14B",
+ "/Macron": "\uF6D0",
+ "/Macronsmall": "\uF7AF",
+ "/Macute": "\u1E3E",
+ "/Mcircle": "\u24C2",
+ "/Mcircleblack": "\u1F15C",
+ "/Mdot": "\u1E40",
+ "/Mdotaccent": "\u1E40",
+ "/Mdotbelow": "\u1E42",
+ "/Menarmenian": "\u0544",
+ "/Mhook": "\u2C6E",
+ "/Mmonospace": "\uFF2D",
+ "/Mohammad": "\uFDF4",
+ "/Mparens": "\u1F11C",
+ "/Mscript": "\u2133",
+ "/Msmall": "\uF76D",
+ "/Msquare": "\u1F13C",
+ "/Msquareblack": "\u1F17C",
+ "/Mturned": "\u019C",
+ "/Mturnedsmall": "\uA7FA",
+ "/Mu": "\u039C",
+ "/N": "\u004E",
+ "/NJ": "\u01CA",
+ "/Nacute": "\u0143",
+ "/Ncaron": "\u0147",
+ "/Ncedilla": "\u0145",
+ "/Ncircle": "\u24C3",
+ "/Ncircleblack": "\u1F15D",
+ "/Ncircumflexbelow": "\u1E4A",
+ "/Ncommaaccent": "\u0145",
+ "/Ndblstruck": "\u2115",
+ "/Ndescender": "\uA790",
+ "/Ndot": "\u1E44",
+ "/Ndotaccent": "\u1E44",
+ "/Ndotbelow": "\u1E46",
+ "/Ngrave": "\u01F8",
+ "/Nhookleft": "\u019D",
+ "/Nineroman": "\u2168",
+ "/Nj": "\u01CB",
+ "/Njecyr": "\u040A",
+ "/Njecyrillic": "\u040A",
+ "/Njekomicyr": "\u050A",
+ "/Nlinebelow": "\u1E48",
+ "/Nlongrightleg": "\u0220",
+ "/Nmonospace": "\uFF2E",
+ "/Nobliquestroke": "\uA7A4",
+ "/Nowarmenian": "\u0546",
+ "/Nparens": "\u1F11D",
+ "/Nsmall": "\uF76E",
+ "/Nsquare": "\u1F13D",
+ "/Nsquareblack": "\u1F17D",
+ "/Ntilde": "\u00D1",
+ "/Ntildesmall": "\uF7F1",
+ "/Nu": "\u039D",
+ "/O": "\u004F",
+ "/OE": "\u0152",
+ "/OEsmall": "\uF6FA",
+ "/OO": "\uA74E",
+ "/Oacute": "\u00D3",
+ "/Oacutesmall": "\uF7F3",
+ "/Obar": "\u019F",
+ "/Obarcyr": "\u04E8",
+ "/Obardieresiscyr": "\u04EA",
+ "/Obarredcyrillic": "\u04E8",
+ "/Obarreddieresiscyrillic": "\u04EA",
+ "/Obreve": "\u014E",
+ "/Ocaron": "\u01D1",
+ "/Ocenteredtilde": "\u019F",
+ "/Ocircle": "\u24C4",
+ "/Ocircleblack": "\u1F15E",
+ "/Ocircumflex": "\u00D4",
+ "/Ocircumflexacute": "\u1ED0",
+ "/Ocircumflexdotbelow": "\u1ED8",
+ "/Ocircumflexgrave": "\u1ED2",
+ "/Ocircumflexhoi": "\u1ED4",
+ "/Ocircumflexhookabove": "\u1ED4",
+ "/Ocircumflexsmall": "\uF7F4",
+ "/Ocircumflextilde": "\u1ED6",
+ "/Ocyr": "\u041E",
+ "/Ocyrillic": "\u041E",
+ "/Odblacute": "\u0150",
+ "/Odblgrave": "\u020C",
+ "/Odieresis": "\u00D6",
+ "/Odieresiscyr": "\u04E6",
+ "/Odieresiscyrillic": "\u04E6",
+ "/Odieresismacron": "\u022A",
+ "/Odieresissmall": "\uF7F6",
+ "/Odot": "\u022E",
+ "/Odotbelow": "\u1ECC",
+ "/Odotmacron": "\u0230",
+ "/Ogoneksmall": "\uF6FB",
+ "/Ograve": "\u00D2",
+ "/Ogravedbl": "\u020C",
+ "/Ogravesmall": "\uF7F2",
+ "/Oharmenian": "\u0555",
+ "/Ohm": "\u2126",
+ "/Ohoi": "\u1ECE",
+ "/Ohookabove": "\u1ECE",
+ "/Ohorn": "\u01A0",
+ "/Ohornacute": "\u1EDA",
+ "/Ohorndotbelow": "\u1EE2",
+ "/Ohorngrave": "\u1EDC",
+ "/Ohornhoi": "\u1EDE",
+ "/Ohornhookabove": "\u1EDE",
+ "/Ohorntilde": "\u1EE0",
+ "/Ohungarumlaut": "\u0150",
+ "/Oi": "\u01A2",
+ "/Oinvertedbreve": "\u020E",
+ "/Oloop": "\uA74C",
+ "/Omacron": "\u014C",
+ "/Omacronacute": "\u1E52",
+ "/Omacrongrave": "\u1E50",
+ "/Omega": "\u2126",
+ "/Omegaacute": "\u1FFB",
+ "/Omegaasper": "\u1F69",
+ "/Omegaasperacute": "\u1F6D",
+ "/Omegaasperacuteiotasub": "\u1FAD",
+ "/Omegaaspergrave": "\u1F6B",
+ "/Omegaaspergraveiotasub": "\u1FAB",
+ "/Omegaasperiotasub": "\u1FA9",
+ "/Omegaaspertilde": "\u1F6F",
+ "/Omegaaspertildeiotasub": "\u1FAF",
+ "/Omegacyr": "\u0460",
+ "/Omegacyrillic": "\u0460",
+ "/Omegagrave": "\u1FFA",
+ "/Omegagreek": "\u03A9",
+ "/Omegaiotasub": "\u1FFC",
+ "/Omegalenis": "\u1F68",
+ "/Omegalenisacute": "\u1F6C",
+ "/Omegalenisacuteiotasub": "\u1FAC",
+ "/Omegalenisgrave": "\u1F6A",
+ "/Omegalenisgraveiotasub": "\u1FAA",
+ "/Omegalenisiotasub": "\u1FA8",
+ "/Omegalenistilde": "\u1F6E",
+ "/Omegalenistildeiotasub": "\u1FAE",
+ "/Omegaroundcyr": "\u047A",
+ "/Omegaroundcyrillic": "\u047A",
+ "/Omegatitlocyr": "\u047C",
+ "/Omegatitlocyrillic": "\u047C",
+ "/Omegatonos": "\u038F",
+ "/Omicron": "\u039F",
+ "/Omicronacute": "\u1FF9",
+ "/Omicronasper": "\u1F49",
+ "/Omicronasperacute": "\u1F4D",
+ "/Omicronaspergrave": "\u1F4B",
+ "/Omicrongrave": "\u1FF8",
+ "/Omicronlenis": "\u1F48",
+ "/Omicronlenisacute": "\u1F4C",
+ "/Omicronlenisgrave": "\u1F4A",
+ "/Omicrontonos": "\u038C",
+ "/Omonospace": "\uFF2F",
+ "/Oneroman": "\u2160",
+ "/Oogonek": "\u01EA",
+ "/Oogonekmacron": "\u01EC",
+ "/Oopen": "\u0186",
+ "/Oparens": "\u1F11E",
+ "/Oslash": "\u00D8",
+ "/Oslashacute": "\u01FE",
+ "/Oslashsmall": "\uF7F8",
+ "/Osmall": "\uF76F",
+ "/Osquare": "\u1F13E",
+ "/Osquareblack": "\u1F17E",
+ "/Ostroke": "\uA74A",
+ "/Ostrokeacute": "\u01FE",
+ "/Otcyr": "\u047E",
+ "/Otcyrillic": "\u047E",
+ "/Otilde": "\u00D5",
+ "/Otildeacute": "\u1E4C",
+ "/Otildedieresis": "\u1E4E",
+ "/Otildemacron": "\u022C",
+ "/Otildesmall": "\uF7F5",
+ "/Ou": "\u0222",
+ "/P": "\u0050",
+ "/PAsquareblack": "\u1F18C",
+ "/PPVsquare": "\u1F14E",
+ "/Pacute": "\u1E54",
+ "/Palochkacyr": "\u04C0",
+ "/Pcircle": "\u24C5",
+ "/Pcircleblack": "\u1F15F",
+ "/Pcrosssquareblack": "\u1F18A",
+ "/Pdblstruck": "\u2119",
+ "/Pdot": "\u1E56",
+ "/Pdotaccent": "\u1E56",
+ "/Pecyr": "\u041F",
+ "/Pecyrillic": "\u041F",
+ "/Peharmenian": "\u054A",
+ "/Pehookcyr": "\u04A6",
+ "/Pemiddlehookcyrillic": "\u04A6",
+ "/Petailcyr": "\u0524",
+ "/Pflourish": "\uA752",
+ "/Phi": "\u03A6",
+ "/Phook": "\u01A4",
+ "/Pi": "\u03A0",
+ "/Pidblstruck": "\u213F",
+ "/Piwrarmenian": "\u0553",
+ "/Pmonospace": "\uFF30",
+ "/Pparens": "\u1F11F",
+ "/Psi": "\u03A8",
+ "/Psicyr": "\u0470",
+ "/Psicyrillic": "\u0470",
+ "/Psmall": "\uF770",
+ "/Psquare": "\u1F13F",
+ "/Psquareblack": "\u1F17F",
+ "/Pstroke": "\u2C63",
+ "/Pstrokedescender": "\uA750",
+ "/Ptail": "\uA754",
+ "/Q": "\u0051",
+ "/Qacyr": "\u051A",
+ "/QalaUsedAsKoranicStopSign": "\uFDF1",
+ "/Qcircle": "\u24C6",
+ "/Qcircleblack": "\u1F160",
+ "/Qdblstruck": "\u211A",
+ "/Qdiagonalstroke": "\uA758",
+ "/Qmonospace": "\uFF31",
+ "/Qparens": "\u1F120",
+ "/Qrotated": "\u213A",
+ "/Qsmall": "\uF771",
+ "/Qsmallhooktail": "\u024A",
+ "/Qsquare": "\u1F140",
+ "/Qsquareblack": "\u1F180",
+ "/Qstrokedescender": "\uA756",
+ "/R": "\u0052",
+ "/Raarmenian": "\u054C",
+ "/Racute": "\u0154",
+ "/Rasoul": "\uFDF6",
+ "/Rcaron": "\u0158",
+ "/Rcedilla": "\u0156",
+ "/Rcircle": "\u24C7",
+ "/Rcircleblack": "\u1F161",
+ "/Rcommaaccent": "\u0156",
+ "/Rdblgrave": "\u0210",
+ "/Rdblstruck": "\u211D",
+ "/Rdot": "\u1E58",
+ "/Rdotaccent": "\u1E58",
+ "/Rdotbelow": "\u1E5A",
+ "/Rdotbelowmacron": "\u1E5C",
+ "/Reharmenian": "\u0550",
+ "/Reverseddottedsigmalunatesymbol": "\u03FF",
+ "/Reversedzecyr": "\u0510",
+ "/Rfraktur": "\u211C",
+ "/Rgravedbl": "\u0210",
+ "/Rhacyr": "\u0516",
+ "/Rho": "\u03A1",
+ "/Rhoasper": "\u1FEC",
+ "/Ringsmall": "\uF6FC",
+ "/Rinsular": "\uA782",
+ "/Rinvertedbreve": "\u0212",
+ "/Rinvertedsmall": "\u0281",
+ "/Ritaliccircle": "\u1F12C",
+ "/Rlinebelow": "\u1E5E",
+ "/Rmacrondot": "\u1E5C",
+ "/Rmonospace": "\uFF32",
+ "/Robliquestroke": "\uA7A6",
+ "/Rparens": "\u1F121",
+ "/Rrotunda": "\uA75A",
+ "/Rscript": "\u211B",
+ "/Rsmall": "\uF772",
+ "/Rsmallinverted": "\u0281",
+ "/Rsmallinvertedsuperior": "\u02B6",
+ "/Rsquare": "\u1F141",
+ "/Rsquareblack": "\u1F181",
+ "/Rstroke": "\u024C",
+ "/Rsupinvertedmod": "\u02B6",
+ "/Rtail": "\u2C64",
+ "/RubElHizbstart": "\u06DE",
+ "/Rumrotunda": "\uA75C",
+ "/Rumsmall": "\uA776",
+ "/S": "\u0053",
+ "/SAsquareblack": "\u1F18D",
+ "/SDsquare": "\u1F14C",
+ "/SF010000": "\u250C",
+ "/SF020000": "\u2514",
+ "/SF030000": "\u2510",
+ "/SF040000": "\u2518",
+ "/SF050000": "\u253C",
+ "/SF060000": "\u252C",
+ "/SF070000": "\u2534",
+ "/SF080000": "\u251C",
+ "/SF090000": "\u2524",
+ "/SF100000": "\u2500",
+ "/SF110000": "\u2502",
+ "/SF190000": "\u2561",
+ "/SF200000": "\u2562",
+ "/SF210000": "\u2556",
+ "/SF220000": "\u2555",
+ "/SF230000": "\u2563",
+ "/SF240000": "\u2551",
+ "/SF250000": "\u2557",
+ "/SF260000": "\u255D",
+ "/SF270000": "\u255C",
+ "/SF280000": "\u255B",
+ "/SF360000": "\u255E",
+ "/SF370000": "\u255F",
+ "/SF380000": "\u255A",
+ "/SF390000": "\u2554",
+ "/SF400000": "\u2569",
+ "/SF410000": "\u2566",
+ "/SF420000": "\u2560",
+ "/SF430000": "\u2550",
+ "/SF440000": "\u256C",
+ "/SF450000": "\u2567",
+ "/SF460000": "\u2568",
+ "/SF470000": "\u2564",
+ "/SF480000": "\u2565",
+ "/SF490000": "\u2559",
+ "/SF500000": "\u2558",
+ "/SF510000": "\u2552",
+ "/SF520000": "\u2553",
+ "/SF530000": "\u256B",
+ "/SF540000": "\u256A",
+ "/SSsquare": "\u1F14D",
+ "/Sacute": "\u015A",
+ "/Sacutedotaccent": "\u1E64",
+ "/Safha": "\u0603",
+ "/Sajdah": "\u06E9",
+ "/Salam": "\uFDF5",
+ "/Salla": "\uFDF9",
+ "/SallaUsedAsKoranicStopSign": "\uFDF0",
+ "/SallallahouAlayheWasallam": "\uFDFA",
+ "/Saltillo": "\uA78B",
+ "/Sampi": "\u03E0",
+ "/Sampiarchaic": "\u0372",
+ "/Sampigreek": "\u03E0",
+ "/San": "\u03FA",
+ "/Sanah": "\u0601",
+ "/Scaron": "\u0160",
+ "/Scarondot": "\u1E66",
+ "/Scarondotaccent": "\u1E66",
+ "/Scaronsmall": "\uF6FD",
+ "/Scedilla": "\u015E",
+ "/Schwa": "\u018F",
+ "/Schwacyr": "\u04D8",
+ "/Schwacyrillic": "\u04D8",
+ "/Schwadieresiscyr": "\u04DA",
+ "/Schwadieresiscyrillic": "\u04DA",
+ "/Scircle": "\u24C8",
+ "/Scircleblack": "\u1F162",
+ "/Scircumflex": "\u015C",
+ "/Scommaaccent": "\u0218",
+ "/Scriptg": "\uA7AC",
+ "/Sdot": "\u1E60",
+ "/Sdotaccent": "\u1E60",
+ "/Sdotbelow": "\u1E62",
+ "/Sdotbelowdotabove": "\u1E68",
+ "/Sdotbelowdotaccent": "\u1E68",
+ "/Seharmenian": "\u054D",
+ "/Semisoftcyr": "\u048C",
+ "/Sevenroman": "\u2166",
+ "/Shaarmenian": "\u0547",
+ "/Shacyr": "\u0428",
+ "/Shacyrillic": "\u0428",
+ "/Shchacyr": "\u0429",
+ "/Shchacyrillic": "\u0429",
+ "/Sheicoptic": "\u03E2",
+ "/SheneGerishin:hb": "\u059E",
+ "/Shhacyr": "\u04BA",
+ "/Shhacyrillic": "\u04BA",
+ "/Shhatailcyr": "\u0526",
+ "/Shimacoptic": "\u03EC",
+ "/Sho": "\u03F7",
+ "/Sigma": "\u03A3",
+ "/Sigmalunatesymbol": "\u03F9",
+ "/Sigmalunatesymboldotted": "\u03FE",
+ "/Sigmareversedlunatesymbol": "\u03FD",
+ "/Sinsular": "\uA784",
+ "/Sixroman": "\u2165",
+ "/Sjekomicyr": "\u050C",
+ "/Smonospace": "\uFF33",
+ "/Sobliquestroke": "\uA7A8",
+ "/Softcyr": "\u042C",
+ "/Softsigncyrillic": "\u042C",
+ "/Sparens": "\u1F122",
+ "/Sshell": "\u1F12A",
+ "/Ssmall": "\uF773",
+ "/Ssquare": "\u1F142",
+ "/Ssquareblack": "\u1F182",
+ "/Sswashtail": "\u2C7E",
+ "/Stigma": "\u03DA",
+ "/Stigmagreek": "\u03DA",
+ "/T": "\u0054",
+ "/Tau": "\u03A4",
+ "/Tbar": "\u0166",
+ "/Tcaron": "\u0164",
+ "/Tcedilla": "\u0162",
+ "/Tcircle": "\u24C9",
+ "/Tcircleblack": "\u1F163",
+ "/Tcircumflexbelow": "\u1E70",
+ "/Tcommaaccent": "\u0162",
+ "/Tdot": "\u1E6A",
+ "/Tdotaccent": "\u1E6A",
+ "/Tdotbelow": "\u1E6C",
+ "/Tecyr": "\u0422",
+ "/Tecyrillic": "\u0422",
+ "/Tedescendercyrillic": "\u04AC",
+ "/Tenroman": "\u2169",
+ "/Tetailcyr": "\u04AC",
+ "/Tetsecyr": "\u04B4",
+ "/Tetsecyrillic": "\u04B4",
+ "/Theta": "\u0398",
+ "/Thetasymbol": "\u03F4",
+ "/Thook": "\u01AC",
+ "/Thorn": "\u00DE",
+ "/Thornsmall": "\uF7FE",
+ "/Thornstroke": "\uA764",
+ "/Thornstrokedescender": "\uA766",
+ "/Threeroman": "\u2162",
+ "/Tildesmall": "\uF6FE",
+ "/Tinsular": "\uA786",
+ "/Tiwnarmenian": "\u054F",
+ "/Tjekomicyr": "\u050E",
+ "/Tlinebelow": "\u1E6E",
+ "/Tmonospace": "\uFF34",
+ "/Toarmenian": "\u0539",
+ "/Tonefive": "\u01BC",
+ "/Tonesix": "\u0184",
+ "/Tonetwo": "\u01A7",
+ "/Tparens": "\u1F123",
+ "/Tresillo": "\uA72A",
+ "/Tretroflexhook": "\u01AE",
+ "/Tsecyr": "\u0426",
+ "/Tsecyrillic": "\u0426",
+ "/Tshecyr": "\u040B",
+ "/Tshecyrillic": "\u040B",
+ "/Tsmall": "\uF774",
+ "/Tsquare": "\u1F143",
+ "/Tsquareblack": "\u1F183",
+ "/Tturned": "\uA7B1",
+ "/Twelveroman": "\u216B",
+ "/Twithdiagonalstroke": "\u023E",
+ "/Tworoman": "\u2161",
+ "/Tz": "\uA728",
+ "/U": "\u0055",
+ "/Uacute": "\u00DA",
+ "/Uacutedblcyr": "\u04F2",
+ "/Uacutesmall": "\uF7FA",
+ "/Ubar": "\u0244",
+ "/Ubreve": "\u016C",
+ "/Ucaron": "\u01D3",
+ "/Ucircle": "\u24CA",
+ "/Ucircleblack": "\u1F164",
+ "/Ucircumflex": "\u00DB",
+ "/Ucircumflexbelow": "\u1E76",
+ "/Ucircumflexsmall": "\uF7FB",
+ "/Ucyr": "\u0423",
+ "/Ucyrillic": "\u0423",
+ "/Udblacute": "\u0170",
+ "/Udblgrave": "\u0214",
+ "/Udieresis": "\u00DC",
+ "/Udieresisacute": "\u01D7",
+ "/Udieresisbelow": "\u1E72",
+ "/Udieresiscaron": "\u01D9",
+ "/Udieresiscyr": "\u04F0",
+ "/Udieresiscyrillic": "\u04F0",
+ "/Udieresisgrave": "\u01DB",
+ "/Udieresismacron": "\u01D5",
+ "/Udieresissmall": "\uF7FC",
+ "/Udotbelow": "\u1EE4",
+ "/Ugrave": "\u00D9",
+ "/Ugravedbl": "\u0214",
+ "/Ugravesmall": "\uF7F9",
+ "/Uhoi": "\u1EE6",
+ "/Uhookabove": "\u1EE6",
+ "/Uhorn": "\u01AF",
+ "/Uhornacute": "\u1EE8",
+ "/Uhorndotbelow": "\u1EF0",
+ "/Uhorngrave": "\u1EEA",
+ "/Uhornhoi": "\u1EEC",
+ "/Uhornhookabove": "\u1EEC",
+ "/Uhorntilde": "\u1EEE",
+ "/Uhungarumlaut": "\u0170",
+ "/Uhungarumlautcyrillic": "\u04F2",
+ "/Uinvertedbreve": "\u0216",
+ "/Ukcyr": "\u0478",
+ "/Ukcyrillic": "\u0478",
+ "/Umacron": "\u016A",
+ "/Umacroncyr": "\u04EE",
+ "/Umacroncyrillic": "\u04EE",
+ "/Umacrondieresis": "\u1E7A",
+ "/Umonospace": "\uFF35",
+ "/Uogonek": "\u0172",
+ "/Uparens": "\u1F124",
+ "/Upsilon": "\u03A5",
+ "/Upsilon1": "\u03D2",
+ "/Upsilonacute": "\u1FEB",
+ "/Upsilonacutehooksymbol": "\u03D3",
+ "/Upsilonacutehooksymbolgreek": "\u03D3",
+ "/Upsilonadieresishooksymbol": "\u03D4",
+ "/Upsilonafrican": "\u01B1",
+ "/Upsilonasper": "\u1F59",
+ "/Upsilonasperacute": "\u1F5D",
+ "/Upsilonaspergrave": "\u1F5B",
+ "/Upsilonaspertilde": "\u1F5F",
+ "/Upsilonbreve": "\u1FE8",
+ "/Upsilondieresis": "\u03AB",
+ "/Upsilondieresishooksymbolgreek": "\u03D4",
+ "/Upsilongrave": "\u1FEA",
+ "/Upsilonhooksymbol": "\u03D2",
+ "/Upsilontonos": "\u038E",
+ "/Upsilonwithmacron": "\u1FE9",
+ "/Uring": "\u016E",
+ "/Ushortcyr": "\u040E",
+ "/Ushortcyrillic": "\u040E",
+ "/Usmall": "\uF775",
+ "/Usquare": "\u1F144",
+ "/Usquareblack": "\u1F184",
+ "/Ustraightcyr": "\u04AE",
+ "/Ustraightcyrillic": "\u04AE",
+ "/Ustraightstrokecyr": "\u04B0",
+ "/Ustraightstrokecyrillic": "\u04B0",
+ "/Utilde": "\u0168",
+ "/Utildeacute": "\u1E78",
+ "/Utildebelow": "\u1E74",
+ "/V": "\u0056",
+ "/Vcircle": "\u24CB",
+ "/Vcircleblack": "\u1F165",
+ "/Vdiagonalstroke": "\uA75E",
+ "/Vdotbelow": "\u1E7E",
+ "/Vecyr": "\u0412",
+ "/Vecyrillic": "\u0412",
+ "/Vend": "\uA768",
+ "/Vewarmenian": "\u054E",
+ "/Vhook": "\u01B2",
+ "/Visigothicz": "\uA762",
+ "/Vmod": "\u2C7D",
+ "/Vmonospace": "\uFF36",
+ "/Voarmenian": "\u0548",
+ "/Volapukae": "\uA79A",
+ "/Volapukoe": "\uA79C",
+ "/Volapukue": "\uA79E",
+ "/Vparens": "\u1F125",
+ "/Vsmall": "\uF776",
+ "/Vsquare": "\u1F145",
+ "/Vsquareblack": "\u1F185",
+ "/Vtilde": "\u1E7C",
+ "/Vturned": "\u0245",
+ "/Vwelsh": "\u1EFC",
+ "/Vy": "\uA760",
+ "/W": "\u0057",
+ "/WZcircle": "\u1F12E",
+ "/Wacute": "\u1E82",
+ "/Wasallam": "\uFDF8",
+ "/Wcircle": "\u24CC",
+ "/Wcircleblack": "\u1F166",
+ "/Wcircumflex": "\u0174",
+ "/Wdieresis": "\u1E84",
+ "/Wdot": "\u1E86",
+ "/Wdotaccent": "\u1E86",
+ "/Wdotbelow": "\u1E88",
+ "/Wecyr": "\u051C",
+ "/Wgrave": "\u1E80",
+ "/Whook": "\u2C72",
+ "/Wmonospace": "\uFF37",
+ "/Wparens": "\u1F126",
+ "/Wsmall": "\uF777",
+ "/Wsquare": "\u1F146",
+ "/Wsquareblack": "\u1F186",
+ "/Wynn": "\u01F7",
+ "/X": "\u0058",
+ "/Xatailcyr": "\u04B2",
+ "/Xcircle": "\u24CD",
+ "/Xcircleblack": "\u1F167",
+ "/Xdieresis": "\u1E8C",
+ "/Xdot": "\u1E8A",
+ "/Xdotaccent": "\u1E8A",
+ "/Xeharmenian": "\u053D",
+ "/Xi": "\u039E",
+ "/Xmonospace": "\uFF38",
+ "/Xparens": "\u1F127",
+ "/Xsmall": "\uF778",
+ "/Xsquare": "\u1F147",
+ "/Xsquareblack": "\u1F187",
+ "/Y": "\u0059",
+ "/Yacute": "\u00DD",
+ "/Yacutesmall": "\uF7FD",
+ "/Yacyr": "\u042F",
+ "/Yaecyr": "\u0518",
+ "/Yatcyr": "\u0462",
+ "/Yatcyrillic": "\u0462",
+ "/Ycircle": "\u24CE",
+ "/Ycircleblack": "\u1F168",
+ "/Ycircumflex": "\u0176",
+ "/Ydieresis": "\u0178",
+ "/Ydieresissmall": "\uF7FF",
+ "/Ydot": "\u1E8E",
+ "/Ydotaccent": "\u1E8E",
+ "/Ydotbelow": "\u1EF4",
+ "/Yericyrillic": "\u042B",
+ "/Yerudieresiscyrillic": "\u04F8",
+ "/Ygrave": "\u1EF2",
+ "/Yhoi": "\u1EF6",
+ "/Yhook": "\u01B3",
+ "/Yhookabove": "\u1EF6",
+ "/Yiarmenian": "\u0545",
+ "/Yicyrillic": "\u0407",
+ "/Yiwnarmenian": "\u0552",
+ "/Ylongcyr": "\u042B",
+ "/Ylongdieresiscyr": "\u04F8",
+ "/Yloop": "\u1EFE",
+ "/Ymacron": "\u0232",
+ "/Ymonospace": "\uFF39",
+ "/Yogh": "\u021C",
+ "/Yot": "\u037F",
+ "/Yparens": "\u1F128",
+ "/Ysmall": "\uF779",
+ "/Ysquare": "\u1F148",
+ "/Ysquareblack": "\u1F188",
+ "/Ystroke": "\u024E",
+ "/Ytilde": "\u1EF8",
+ "/Yturnedsans": "\u2144",
+ "/Yucyr": "\u042E",
+ "/Yukrcyr": "\u0407",
+ "/Yusbigcyr": "\u046A",
+ "/Yusbigcyrillic": "\u046A",
+ "/Yusbigiotifiedcyr": "\u046C",
+ "/Yusbigiotifiedcyrillic": "\u046C",
+ "/Yuslittlecyr": "\u0466",
+ "/Yuslittlecyrillic": "\u0466",
+ "/Yuslittleiotifiedcyr": "\u0468",
+ "/Yuslittleiotifiedcyrillic": "\u0468",
+ "/Z": "\u005A",
+ "/Zaarmenian": "\u0536",
+ "/Zacute": "\u0179",
+ "/Zcaron": "\u017D",
+ "/Zcaronsmall": "\uF6FF",
+ "/Zcircle": "\u24CF",
+ "/Zcircleblack": "\u1F169",
+ "/Zcircumflex": "\u1E90",
+ "/Zdblstruck": "\u2124",
+ "/Zdescender": "\u2C6B",
+ "/Zdot": "\u017B",
+ "/Zdotaccent": "\u017B",
+ "/Zdotbelow": "\u1E92",
+ "/Zecyr": "\u0417",
+ "/Zecyrillic": "\u0417",
+ "/Zedescendercyrillic": "\u0498",
+ "/Zedieresiscyr": "\u04DE",
+ "/Zedieresiscyrillic": "\u04DE",
+ "/Zeta": "\u0396",
+ "/Zetailcyr": "\u0498",
+ "/Zfraktur": "\u2128",
+ "/Zhearmenian": "\u053A",
+ "/Zhebrevecyr": "\u04C1",
+ "/Zhebrevecyrillic": "\u04C1",
+ "/Zhecyr": "\u0416",
+ "/Zhecyrillic": "\u0416",
+ "/Zhedescendercyrillic": "\u0496",
+ "/Zhedieresiscyr": "\u04DC",
+ "/Zhedieresiscyrillic": "\u04DC",
+ "/Zhetailcyr": "\u0496",
+ "/Zhook": "\u0224",
+ "/Zjekomicyr": "\u0504",
+ "/Zlinebelow": "\u1E94",
+ "/Zmonospace": "\uFF3A",
+ "/Zparens": "\u1F129",
+ "/Zsmall": "\uF77A",
+ "/Zsquare": "\u1F149",
+ "/Zsquareblack": "\u1F189",
+ "/Zstroke": "\u01B5",
+ "/Zswashtail": "\u2C7F",
+ "/a": "\u0061",
+ "/a.inferior": "\u2090",
+ "/aHonRAA": "\u0613",
+ "/aa": "\uA733",
+ "/aabengali": "\u0986",
+ "/aacute": "\u00E1",
+ "/aadeva": "\u0906",
+ "/aagujarati": "\u0A86",
+ "/aagurmukhi": "\u0A06",
+ "/aamatragurmukhi": "\u0A3E",
+ "/aarusquare": "\u3303",
+ "/aavowelsignbengali": "\u09BE",
+ "/aavowelsigndeva": "\u093E",
+ "/aavowelsigngujarati": "\u0ABE",
+ "/abbreviationmarkarmenian": "\u055F",
+ "/abbreviationsigndeva": "\u0970",
+ "/abengali": "\u0985",
+ "/abopomofo": "\u311A",
+ "/abreve": "\u0103",
+ "/abreveacute": "\u1EAF",
+ "/abrevecyr": "\u04D1",
+ "/abrevecyrillic": "\u04D1",
+ "/abrevedotbelow": "\u1EB7",
+ "/abrevegrave": "\u1EB1",
+ "/abrevehoi": "\u1EB3",
+ "/abrevehookabove": "\u1EB3",
+ "/abrevetilde": "\u1EB5",
+ "/absquareblack": "\u1F18E",
+ "/acaron": "\u01CE",
+ "/accountof": "\u2100",
+ "/accurrent": "\u23E6",
+ "/acircle": "\u24D0",
+ "/acirclekatakana": "\u32D0",
+ "/acircumflex": "\u00E2",
+ "/acircumflexacute": "\u1EA5",
+ "/acircumflexdotbelow": "\u1EAD",
+ "/acircumflexgrave": "\u1EA7",
+ "/acircumflexhoi": "\u1EA9",
+ "/acircumflexhookabove": "\u1EA9",
+ "/acircumflextilde": "\u1EAB",
+ "/activatearabicformshaping": "\u206D",
+ "/activatesymmetricswapping": "\u206B",
+ "/acute": "\u00B4",
+ "/acutebelowcmb": "\u0317",
+ "/acutecmb": "\u0301",
+ "/acutecomb": "\u0301",
+ "/acutedblmiddlemod": "\u02F6",
+ "/acutedeva": "\u0954",
+ "/acutelowmod": "\u02CF",
+ "/acutemod": "\u02CA",
+ "/acutetonecmb": "\u0341",
+ "/acyr": "\u0430",
+ "/acyrillic": "\u0430",
+ "/adblgrave": "\u0201",
+ "/addakgurmukhi": "\u0A71",
+ "/addressedsubject": "\u2101",
+ "/adegadegpada": "\uA9CB",
+ "/adegpada": "\uA9CA",
+ "/adeva": "\u0905",
+ "/adieresis": "\u00E4",
+ "/adieresiscyr": "\u04D3",
+ "/adieresiscyrillic": "\u04D3",
+ "/adieresismacron": "\u01DF",
+ "/adishakti": "\u262C",
+ "/admissionTickets": "\u1F39F",
+ "/adot": "\u0227",
+ "/adotbelow": "\u1EA1",
+ "/adotmacron": "\u01E1",
+ "/ae": "\u00E6",
+ "/aeacute": "\u01FD",
+ "/aekorean": "\u3150",
+ "/aemacron": "\u01E3",
+ "/aerialTramway": "\u1F6A1",
+ "/afghani": "\u060B",
+ "/afii00208": "\u2015",
+ "/afii08941": "\u20A4",
+ "/afii10017": "\u0410",
+ "/afii10018": "\u0411",
+ "/afii10019": "\u0412",
+ "/afii10020": "\u0413",
+ "/afii10021": "\u0414",
+ "/afii10022": "\u0415",
+ "/afii10023": "\u0401",
+ "/afii10024": "\u0416",
+ "/afii10025": "\u0417",
+ "/afii10026": "\u0418",
+ "/afii10027": "\u0419",
+ "/afii10028": "\u041A",
+ "/afii10029": "\u041B",
+ "/afii10030": "\u041C",
+ "/afii10031": "\u041D",
+ "/afii10032": "\u041E",
+ "/afii10033": "\u041F",
+ "/afii10034": "\u0420",
+ "/afii10035": "\u0421",
+ "/afii10036": "\u0422",
+ "/afii10037": "\u0423",
+ "/afii10038": "\u0424",
+ "/afii10039": "\u0425",
+ "/afii10040": "\u0426",
+ "/afii10041": "\u0427",
+ "/afii10042": "\u0428",
+ "/afii10043": "\u0429",
+ "/afii10044": "\u042A",
+ "/afii10045": "\u042B",
+ "/afii10046": "\u042C",
+ "/afii10047": "\u042D",
+ "/afii10048": "\u042E",
+ "/afii10049": "\u042F",
+ "/afii10050": "\u0490",
+ "/afii10051": "\u0402",
+ "/afii10052": "\u0403",
+ "/afii10053": "\u0404",
+ "/afii10054": "\u0405",
+ "/afii10055": "\u0406",
+ "/afii10056": "\u0407",
+ "/afii10057": "\u0408",
+ "/afii10058": "\u0409",
+ "/afii10059": "\u040A",
+ "/afii10060": "\u040B",
+ "/afii10061": "\u040C",
+ "/afii10062": "\u040E",
+ "/afii10063": "\uF6C4",
+ "/afii10064": "\uF6C5",
+ "/afii10065": "\u0430",
+ "/afii10066": "\u0431",
+ "/afii10067": "\u0432",
+ "/afii10068": "\u0433",
+ "/afii10069": "\u0434",
+ "/afii10070": "\u0435",
+ "/afii10071": "\u0451",
+ "/afii10072": "\u0436",
+ "/afii10073": "\u0437",
+ "/afii10074": "\u0438",
+ "/afii10075": "\u0439",
+ "/afii10076": "\u043A",
+ "/afii10077": "\u043B",
+ "/afii10078": "\u043C",
+ "/afii10079": "\u043D",
+ "/afii10080": "\u043E",
+ "/afii10081": "\u043F",
+ "/afii10082": "\u0440",
+ "/afii10083": "\u0441",
+ "/afii10084": "\u0442",
+ "/afii10085": "\u0443",
+ "/afii10086": "\u0444",
+ "/afii10087": "\u0445",
+ "/afii10088": "\u0446",
+ "/afii10089": "\u0447",
+ "/afii10090": "\u0448",
+ "/afii10091": "\u0449",
+ "/afii10092": "\u044A",
+ "/afii10093": "\u044B",
+ "/afii10094": "\u044C",
+ "/afii10095": "\u044D",
+ "/afii10096": "\u044E",
+ "/afii10097": "\u044F",
+ "/afii10098": "\u0491",
+ "/afii10099": "\u0452",
+ "/afii10100": "\u0453",
+ "/afii10101": "\u0454",
+ "/afii10102": "\u0455",
+ "/afii10103": "\u0456",
+ "/afii10104": "\u0457",
+ "/afii10105": "\u0458",
+ "/afii10106": "\u0459",
+ "/afii10107": "\u045A",
+ "/afii10108": "\u045B",
+ "/afii10109": "\u045C",
+ "/afii10110": "\u045E",
+ "/afii10145": "\u040F",
+ "/afii10146": "\u0462",
+ "/afii10147": "\u0472",
+ "/afii10148": "\u0474",
+ "/afii10192": "\uF6C6",
+ "/afii10193": "\u045F",
+ "/afii10194": "\u0463",
+ "/afii10195": "\u0473",
+ "/afii10196": "\u0475",
+ "/afii10831": "\uF6C7",
+ "/afii10832": "\uF6C8",
+ "/afii10846": "\u04D9",
+ "/afii299": "\u200E",
+ "/afii300": "\u200F",
+ "/afii301": "\u200D",
+ "/afii57381": "\u066A",
+ "/afii57388": "\u060C",
+ "/afii57392": "\u0660",
+ "/afii57393": "\u0661",
+ "/afii57394": "\u0662",
+ "/afii57395": "\u0663",
+ "/afii57396": "\u0664",
+ "/afii57397": "\u0665",
+ "/afii57398": "\u0666",
+ "/afii57399": "\u0667",
+ "/afii57400": "\u0668",
+ "/afii57401": "\u0669",
+ "/afii57403": "\u061B",
+ "/afii57407": "\u061F",
+ "/afii57409": "\u0621",
+ "/afii57410": "\u0622",
+ "/afii57411": "\u0623",
+ "/afii57412": "\u0624",
+ "/afii57413": "\u0625",
+ "/afii57414": "\u0626",
+ "/afii57415": "\u0627",
+ "/afii57416": "\u0628",
+ "/afii57417": "\u0629",
+ "/afii57418": "\u062A",
+ "/afii57419": "\u062B",
+ "/afii57420": "\u062C",
+ "/afii57421": "\u062D",
+ "/afii57422": "\u062E",
+ "/afii57423": "\u062F",
+ "/afii57424": "\u0630",
+ "/afii57425": "\u0631",
+ "/afii57426": "\u0632",
+ "/afii57427": "\u0633",
+ "/afii57428": "\u0634",
+ "/afii57429": "\u0635",
+ "/afii57430": "\u0636",
+ "/afii57431": "\u0637",
+ "/afii57432": "\u0638",
+ "/afii57433": "\u0639",
+ "/afii57434": "\u063A",
+ "/afii57440": "\u0640",
+ "/afii57441": "\u0641",
+ "/afii57442": "\u0642",
+ "/afii57443": "\u0643",
+ "/afii57444": "\u0644",
+ "/afii57445": "\u0645",
+ "/afii57446": "\u0646",
+ "/afii57448": "\u0648",
+ "/afii57449": "\u0649",
+ "/afii57450": "\u064A",
+ "/afii57451": "\u064B",
+ "/afii57452": "\u064C",
+ "/afii57453": "\u064D",
+ "/afii57454": "\u064E",
+ "/afii57455": "\u064F",
+ "/afii57456": "\u0650",
+ "/afii57457": "\u0651",
+ "/afii57458": "\u0652",
+ "/afii57470": "\u0647",
+ "/afii57505": "\u06A4",
+ "/afii57506": "\u067E",
+ "/afii57507": "\u0686",
+ "/afii57508": "\u0698",
+ "/afii57509": "\u06AF",
+ "/afii57511": "\u0679",
+ "/afii57512": "\u0688",
+ "/afii57513": "\u0691",
+ "/afii57514": "\u06BA",
+ "/afii57519": "\u06D2",
+ "/afii57534": "\u06D5",
+ "/afii57636": "\u20AA",
+ "/afii57645": "\u05BE",
+ "/afii57658": "\u05C3",
+ "/afii57664": "\u05D0",
+ "/afii57665": "\u05D1",
+ "/afii57666": "\u05D2",
+ "/afii57667": "\u05D3",
+ "/afii57668": "\u05D4",
+ "/afii57669": "\u05D5",
+ "/afii57670": "\u05D6",
+ "/afii57671": "\u05D7",
+ "/afii57672": "\u05D8",
+ "/afii57673": "\u05D9",
+ "/afii57674": "\u05DA",
+ "/afii57675": "\u05DB",
+ "/afii57676": "\u05DC",
+ "/afii57677": "\u05DD",
+ "/afii57678": "\u05DE",
+ "/afii57679": "\u05DF",
+ "/afii57680": "\u05E0",
+ "/afii57681": "\u05E1",
+ "/afii57682": "\u05E2",
+ "/afii57683": "\u05E3",
+ "/afii57684": "\u05E4",
+ "/afii57685": "\u05E5",
+ "/afii57686": "\u05E6",
+ "/afii57687": "\u05E7",
+ "/afii57688": "\u05E8",
+ "/afii57689": "\u05E9",
+ "/afii57690": "\u05EA",
+ "/afii57694": "\uFB2A",
+ "/afii57695": "\uFB2B",
+ "/afii57700": "\uFB4B",
+ "/afii57705": "\uFB1F",
+ "/afii57716": "\u05F0",
+ "/afii57717": "\u05F1",
+ "/afii57718": "\u05F2",
+ "/afii57723": "\uFB35",
+ "/afii57793": "\u05B4",
+ "/afii57794": "\u05B5",
+ "/afii57795": "\u05B6",
+ "/afii57796": "\u05BB",
+ "/afii57797": "\u05B8",
+ "/afii57798": "\u05B7",
+ "/afii57799": "\u05B0",
+ "/afii57800": "\u05B2",
+ "/afii57801": "\u05B1",
+ "/afii57802": "\u05B3",
+ "/afii57803": "\u05C2",
+ "/afii57804": "\u05C1",
+ "/afii57806": "\u05B9",
+ "/afii57807": "\u05BC",
+ "/afii57839": "\u05BD",
+ "/afii57841": "\u05BF",
+ "/afii57842": "\u05C0",
+ "/afii57929": "\u02BC",
+ "/afii61248": "\u2105",
+ "/afii61289": "\u2113",
+ "/afii61352": "\u2116",
+ "/afii61573": "\u202C",
+ "/afii61574": "\u202D",
+ "/afii61575": "\u202E",
+ "/afii61664": "\u200C",
+ "/afii63167": "\u066D",
+ "/afii64937": "\u02BD",
+ "/agrave": "\u00E0",
+ "/agravedbl": "\u0201",
+ "/agujarati": "\u0A85",
+ "/agurmukhi": "\u0A05",
+ "/ahiragana": "\u3042",
+ "/ahoi": "\u1EA3",
+ "/ahookabove": "\u1EA3",
+ "/aibengali": "\u0990",
+ "/aibopomofo": "\u311E",
+ "/aideva": "\u0910",
+ "/aiecyr": "\u04D5",
+ "/aiecyrillic": "\u04D5",
+ "/aigujarati": "\u0A90",
+ "/aigurmukhi": "\u0A10",
+ "/aimatragurmukhi": "\u0A48",
+ "/ain.fina": "\uFECA",
+ "/ain.init": "\uFECB",
+ "/ain.init_alefmaksura.fina": "\uFCF7",
+ "/ain.init_jeem.fina": "\uFC29",
+ "/ain.init_jeem.medi": "\uFCBA",
+ "/ain.init_jeem.medi_meem.medi": "\uFDC4",
+ "/ain.init_meem.fina": "\uFC2A",
+ "/ain.init_meem.medi": "\uFCBB",
+ "/ain.init_meem.medi_meem.medi": "\uFD77",
+ "/ain.init_yeh.fina": "\uFCF8",
+ "/ain.isol": "\uFEC9",
+ "/ain.medi": "\uFECC",
+ "/ain.medi_alefmaksura.fina": "\uFD13",
+ "/ain.medi_jeem.medi_meem.fina": "\uFD75",
+ "/ain.medi_meem.medi_alefmaksura.fina": "\uFD78",
+ "/ain.medi_meem.medi_meem.fina": "\uFD76",
+ "/ain.medi_meem.medi_yeh.fina": "\uFDB6",
+ "/ain.medi_yeh.fina": "\uFD14",
+ "/ainThreeDotsDownAbove": "\u075E",
+ "/ainTwoDotsAbove": "\u075D",
+ "/ainTwoDotsVerticallyAbove": "\u075F",
+ "/ainarabic": "\u0639",
+ "/ainfinalarabic": "\uFECA",
+ "/aininitialarabic": "\uFECB",
+ "/ainmedialarabic": "\uFECC",
+ "/ainthreedotsabove": "\u06A0",
+ "/ainvertedbreve": "\u0203",
+ "/airplaneArriving": "\u1F6EC",
+ "/airplaneDeparture": "\u1F6EB",
+ "/aivowelsignbengali": "\u09C8",
+ "/aivowelsigndeva": "\u0948",
+ "/aivowelsigngujarati": "\u0AC8",
+ "/akatakana": "\u30A2",
+ "/akatakanahalfwidth": "\uFF71",
+ "/akorean": "\u314F",
+ "/aktieselskab": "\u214D",
+ "/alarmclock": "\u23F0",
+ "/alef": "\u05D0",
+ "/alef.fina": "\uFE8E",
+ "/alef.init_fathatan.fina": "\uFD3D",
+ "/alef.isol": "\uFE8D",
+ "/alef.medi_fathatan.fina": "\uFD3C",
+ "/alef:hb": "\u05D0",
+ "/alefDigitThreeAbove": "\u0774",
+ "/alefDigitTwoAbove": "\u0773",
+ "/alefLamYehabove": "\u0616",
+ "/alefabove": "\u0670",
+ "/alefarabic": "\u0627",
+ "/alefdageshhebrew": "\uFB30",
+ "/aleffinalarabic": "\uFE8E",
+ "/alefhamza": "\u0623",
+ "/alefhamza.fina": "\uFE84",
+ "/alefhamza.isol": "\uFE83",
+ "/alefhamzaabovearabic": "\u0623",
+ "/alefhamzaabovefinalarabic": "\uFE84",
+ "/alefhamzabelow": "\u0625",
+ "/alefhamzabelow.fina": "\uFE88",
+ "/alefhamzabelow.isol": "\uFE87",
+ "/alefhamzabelowarabic": "\u0625",
+ "/alefhamzabelowfinalarabic": "\uFE88",
+ "/alefhebrew": "\u05D0",
+ "/alefhighhamza": "\u0675",
+ "/aleflamedhebrew": "\uFB4F",
+ "/alefmadda": "\u0622",
+ "/alefmadda.fina": "\uFE82",
+ "/alefmadda.isol": "\uFE81",
+ "/alefmaddaabovearabic": "\u0622",
+ "/alefmaddaabovefinalarabic": "\uFE82",
+ "/alefmaksura": "\u0649",
+ "/alefmaksura.fina": "\uFEF0",
+ "/alefmaksura.init_superscriptalef.fina": "\uFC5D",
+ "/alefmaksura.isol": "\uFEEF",
+ "/alefmaksura.medi_superscriptalef.fina": "\uFC90",
+ "/alefmaksuraarabic": "\u0649",
+ "/alefmaksurafinalarabic": "\uFEF0",
+ "/alefmaksurainitialarabic": "\uFEF3",
+ "/alefmaksuramedialarabic": "\uFEF4",
+ "/alefpatahhebrew": "\uFB2E",
+ "/alefqamatshebrew": "\uFB2F",
+ "/alefwasla": "\u0671",
+ "/alefwasla.fina": "\uFB51",
+ "/alefwasla.isol": "\uFB50",
+ "/alefwavyhamza": "\u0672",
+ "/alefwavyhamzabelow": "\u0673",
+ "/alefwide:hb": "\uFB21",
+ "/alefwithmapiq:hb": "\uFB30",
+ "/alefwithpatah:hb": "\uFB2E",
+ "/alefwithqamats:hb": "\uFB2F",
+ "/alembic": "\u2697",
+ "/aleph": "\u2135",
+ "/alienMonster": "\u1F47E",
+ "/allaroundprofile": "\u232E",
+ "/allequal": "\u224C",
+ "/allianceideographiccircled": "\u32AF",
+ "/allianceideographicparen": "\u323F",
+ "/almostequalorequal": "\u224A",
+ "/alpha": "\u03B1",
+ "/alphaacute": "\u1F71",
+ "/alphaacuteiotasub": "\u1FB4",
+ "/alphaasper": "\u1F01",
+ "/alphaasperacute": "\u1F05",
+ "/alphaasperacuteiotasub": "\u1F85",
+ "/alphaaspergrave": "\u1F03",
+ "/alphaaspergraveiotasub": "\u1F83",
+ "/alphaasperiotasub": "\u1F81",
+ "/alphaaspertilde": "\u1F07",
+ "/alphaaspertildeiotasub": "\u1F87",
+ "/alphabreve": "\u1FB0",
+ "/alphafunc": "\u237A",
+ "/alphagrave": "\u1F70",
+ "/alphagraveiotasub": "\u1FB2",
+ "/alphaiotasub": "\u1FB3",
+ "/alphalenis": "\u1F00",
+ "/alphalenisacute": "\u1F04",
+ "/alphalenisacuteiotasub": "\u1F84",
+ "/alphalenisgrave": "\u1F02",
+ "/alphalenisgraveiotasub": "\u1F82",
+ "/alphalenisiotasub": "\u1F80",
+ "/alphalenistilde": "\u1F06",
+ "/alphalenistildeiotasub": "\u1F86",
+ "/alphatilde": "\u1FB6",
+ "/alphatildeiotasub": "\u1FB7",
+ "/alphatonos": "\u03AC",
+ "/alphaturned": "\u0252",
+ "/alphaunderlinefunc": "\u2376",
+ "/alphawithmacron": "\u1FB1",
+ "/alternateonewayleftwaytraffic": "\u26D5",
+ "/alternative": "\u2387",
+ "/amacron": "\u0101",
+ "/ambulance": "\u1F691",
+ "/americanFootball": "\u1F3C8",
+ "/amfullwidth": "\u33C2",
+ "/amonospace": "\uFF41",
+ "/amountofcheck": "\u2447",
+ "/ampersand": "\u0026",
+ "/ampersandSindhi": "\u06FD",
+ "/ampersandmonospace": "\uFF06",
+ "/ampersandsmall": "\uF726",
+ "/ampersandturned": "\u214B",
+ "/amphora": "\u1F3FA",
+ "/amsquare": "\u33C2",
+ "/anbopomofo": "\u3122",
+ "/anchor": "\u2693",
+ "/ancoradown": "\u2E14",
+ "/ancoraup": "\u2E15",
+ "/andappada": "\uA9C3",
+ "/angbopomofo": "\u3124",
+ "/anger": "\u1F4A2",
+ "/angkhankhuthai": "\u0E5A",
+ "/angle": "\u2220",
+ "/anglearcright": "\u22BE",
+ "/anglebracketleft": "\u3008",
+ "/anglebracketleftvertical": "\uFE3F",
+ "/anglebracketright": "\u3009",
+ "/anglebracketrightvertical": "\uFE40",
+ "/angledottedright": "\u2E16",
+ "/angleleft": "\u2329",
+ "/anglemarkerdottedsubstitutionright": "\u2E01",
+ "/anglemarkersubstitutionright": "\u2E00",
+ "/angleright": "\u232A",
+ "/anglezigzagarrowdownright": "\u237C",
+ "/angryFace": "\u1F620",
+ "/angstrom": "\u212B",
+ "/anguishedFace": "\u1F627",
+ "/ankh": "\u2625",
+ "/anoteleia": "\u0387",
+ "/anpeasquare": "\u3302",
+ "/ant": "\u1F41C",
+ "/antennaBars": "\u1F4F6",
+ "/anticlockwiseDownwardsAndUpwardsOpenCircleArrows": "\u1F504",
+ "/anudattadeva": "\u0952",
+ "/anusvarabengali": "\u0982",
+ "/anusvaradeva": "\u0902",
+ "/anusvaragujarati": "\u0A82",
+ "/ao": "\uA735",
+ "/aogonek": "\u0105",
+ "/aovermfullwidth": "\u33DF",
+ "/apaatosquare": "\u3300",
+ "/aparen": "\u249C",
+ "/aparenthesized": "\u249C",
+ "/apostrophearmenian": "\u055A",
+ "/apostrophedblmod": "\u02EE",
+ "/apostrophemod": "\u02BC",
+ "/apple": "\uF8FF",
+ "/approaches": "\u2250",
+ "/approacheslimit": "\u2250",
+ "/approxequal": "\u2248",
+ "/approxequalorimage": "\u2252",
+ "/approximatelybutnotactuallyequal": "\u2246",
+ "/approximatelyequal": "\u2245",
+ "/approximatelyequalorimage": "\u2252",
+ "/apriltelegraph": "\u32C3",
+ "/aquarius": "\u2652",
+ "/ar:ae": "\u06D5",
+ "/ar:ain": "\u0639",
+ "/ar:alef": "\u0627",
+ "/ar:comma": "\u060C",
+ "/ar:cuberoot": "\u0606",
+ "/ar:decimalseparator": "\u066B",
+ "/ar:e": "\u06D0",
+ "/ar:eight": "\u0668",
+ "/ar:feh": "\u0641",
+ "/ar:five": "\u0665",
+ "/ar:four": "\u0664",
+ "/ar:fourthroot": "\u0607",
+ "/ar:kaf": "\u0643",
+ "/ar:ng": "\u06AD",
+ "/ar:nine": "\u0669",
+ "/ar:numbersign": "\u0600",
+ "/ar:oe": "\u06C6",
+ "/ar:one": "\u0661",
+ "/ar:peh": "\u067E",
+ "/ar:percent": "\u066A",
+ "/ar:perthousand": "\u060A",
+ "/ar:question": "\u061F",
+ "/ar:reh": "\u0631",
+ "/ar:semicolon": "\u061B",
+ "/ar:seven": "\u0667",
+ "/ar:shadda": "\u0651",
+ "/ar:six": "\u0666",
+ "/ar:sukun": "\u0652",
+ "/ar:three": "\u0663",
+ "/ar:two": "\u0662",
+ "/ar:u": "\u06C7",
+ "/ar:ve": "\u06CB",
+ "/ar:yu": "\u06C8",
+ "/ar:zero": "\u0660",
+ "/araeaekorean": "\u318E",
+ "/araeakorean": "\u318D",
+ "/arc": "\u2312",
+ "/archaicmepigraphic": "\uA7FF",
+ "/aries": "\u2648",
+ "/arighthalfring": "\u1E9A",
+ "/aring": "\u00E5",
+ "/aringacute": "\u01FB",
+ "/aringbelow": "\u1E01",
+ "/armn:Ayb": "\u0531",
+ "/armn:Ben": "\u0532",
+ "/armn:Ca": "\u053E",
+ "/armn:Cha": "\u0549",
+ "/armn:Cheh": "\u0543",
+ "/armn:Co": "\u0551",
+ "/armn:DRAMSIGN": "\u058F",
+ "/armn:Da": "\u0534",
+ "/armn:Ech": "\u0535",
+ "/armn:Eh": "\u0537",
+ "/armn:Et": "\u0538",
+ "/armn:Feh": "\u0556",
+ "/armn:Ghad": "\u0542",
+ "/armn:Gim": "\u0533",
+ "/armn:Ho": "\u0540",
+ "/armn:Ini": "\u053B",
+ "/armn:Ja": "\u0541",
+ "/armn:Jheh": "\u054B",
+ "/armn:Keh": "\u0554",
+ "/armn:Ken": "\u053F",
+ "/armn:Liwn": "\u053C",
+ "/armn:Men": "\u0544",
+ "/armn:Now": "\u0546",
+ "/armn:Oh": "\u0555",
+ "/armn:Peh": "\u054A",
+ "/armn:Piwr": "\u0553",
+ "/armn:Ra": "\u054C",
+ "/armn:Reh": "\u0550",
+ "/armn:Seh": "\u054D",
+ "/armn:Sha": "\u0547",
+ "/armn:Tiwn": "\u054F",
+ "/armn:To": "\u0539",
+ "/armn:Vew": "\u054E",
+ "/armn:Vo": "\u0548",
+ "/armn:Xeh": "\u053D",
+ "/armn:Yi": "\u0545",
+ "/armn:Yiwn": "\u0552",
+ "/armn:Za": "\u0536",
+ "/armn:Zhe": "\u053A",
+ "/armn:abbreviationmark": "\u055F",
+ "/armn:apostrophe": "\u055A",
+ "/armn:ayb": "\u0561",
+ "/armn:ben": "\u0562",
+ "/armn:ca": "\u056E",
+ "/armn:cha": "\u0579",
+ "/armn:cheh": "\u0573",
+ "/armn:co": "\u0581",
+ "/armn:comma": "\u055D",
+ "/armn:da": "\u0564",
+ "/armn:ech": "\u0565",
+ "/armn:ech_yiwn": "\u0587",
+ "/armn:eh": "\u0567",
+ "/armn:emphasismark": "\u055B",
+ "/armn:et": "\u0568",
+ "/armn:exclam": "\u055C",
+ "/armn:feh": "\u0586",
+ "/armn:ghad": "\u0572",
+ "/armn:gim": "\u0563",
+ "/armn:ho": "\u0570",
+ "/armn:hyphen": "\u058A",
+ "/armn:ini": "\u056B",
+ "/armn:ja": "\u0571",
+ "/armn:jheh": "\u057B",
+ "/armn:keh": "\u0584",
+ "/armn:ken": "\u056F",
+ "/armn:leftfacingeternitysign": "\u058E",
+ "/armn:liwn": "\u056C",
+ "/armn:men": "\u0574",
+ "/armn:men_ech": "\uFB14",
+ "/armn:men_ini": "\uFB15",
+ "/armn:men_now": "\uFB13",
+ "/armn:men_xeh": "\uFB17",
+ "/armn:now": "\u0576",
+ "/armn:oh": "\u0585",
+ "/armn:peh": "\u057A",
+ "/armn:period": "\u0589",
+ "/armn:piwr": "\u0583",
+ "/armn:question": "\u055E",
+ "/armn:ra": "\u057C",
+ "/armn:reh": "\u0580",
+ "/armn:rightfacingeternitysign": "\u058D",
+ "/armn:ringhalfleft": "\u0559",
+ "/armn:seh": "\u057D",
+ "/armn:sha": "\u0577",
+ "/armn:tiwn": "\u057F",
+ "/armn:to": "\u0569",
+ "/armn:vew": "\u057E",
+ "/armn:vew_now": "\uFB16",
+ "/armn:vo": "\u0578",
+ "/armn:xeh": "\u056D",
+ "/armn:yi": "\u0575",
+ "/armn:yiwn": "\u0582",
+ "/armn:za": "\u0566",
+ "/armn:zhe": "\u056A",
+ "/arrowNE": "\u2197",
+ "/arrowNW": "\u2196",
+ "/arrowSE": "\u2198",
+ "/arrowSW": "\u2199",
+ "/arrowanticlockwiseopencircle": "\u21BA",
+ "/arrowanticlockwisesemicircle": "\u21B6",
+ "/arrowboth": "\u2194",
+ "/arrowclockwiseopencircle": "\u21BB",
+ "/arrowclockwisesemicircle": "\u21B7",
+ "/arrowdashdown": "\u21E3",
+ "/arrowdashleft": "\u21E0",
+ "/arrowdashright": "\u21E2",
+ "/arrowdashup": "\u21E1",
+ "/arrowdblboth": "\u21D4",
+ "/arrowdbldown": "\u21D3",
+ "/arrowdblleft": "\u21D0",
+ "/arrowdblright": "\u21D2",
+ "/arrowdblup": "\u21D1",
+ "/arrowdown": "\u2193",
+ "/arrowdowndashed": "\u21E3",
+ "/arrowdownfrombar": "\u21A7",
+ "/arrowdownleft": "\u2199",
+ "/arrowdownright": "\u2198",
+ "/arrowdowntwoheaded": "\u21A1",
+ "/arrowdownwhite": "\u21E9",
+ "/arrowdownzigzag": "\u21AF",
+ "/arrowheaddown": "\u2304",
+ "/arrowheaddownlowmod": "\u02EF",
+ "/arrowheaddownmod": "\u02C5",
+ "/arrowheadleftlowmod": "\u02F1",
+ "/arrowheadleftmod": "\u02C2",
+ "/arrowheadrightlowmod": "\u02F2",
+ "/arrowheadrightmod": "\u02C3",
+ "/arrowheadtwobarsuphorizontal": "\u2324",
+ "/arrowheadup": "\u2303",
+ "/arrowheaduplowmod": "\u02F0",
+ "/arrowheadupmod": "\u02C4",
+ "/arrowhorizex": "\uF8E7",
+ "/arrowleft": "\u2190",
+ "/arrowleftdashed": "\u21E0",
+ "/arrowleftdbl": "\u21D0",
+ "/arrowleftdblstroke": "\u21CD",
+ "/arrowleftdowncorner": "\u21B5",
+ "/arrowleftdowntip": "\u21B2",
+ "/arrowleftfrombar": "\u21A4",
+ "/arrowlefthook": "\u21A9",
+ "/arrowleftloop": "\u21AB",
+ "/arrowleftlowmod": "\u02FF",
+ "/arrowleftoverright": "\u21C6",
+ "/arrowleftoverrighttobar": "\u21B9",
+ "/arrowleftright": "\u2194",
+ "/arrowleftrightstroke": "\u21AE",
+ "/arrowleftrightwave": "\u21AD",
+ "/arrowleftsquiggle": "\u21DC",
+ "/arrowleftstroke": "\u219A",
+ "/arrowlefttail": "\u21A2",
+ "/arrowlefttobar": "\u21E4",
+ "/arrowlefttwoheaded": "\u219E",
+ "/arrowleftuptip": "\u21B0",
+ "/arrowleftwave": "\u219C",
+ "/arrowleftwhite": "\u21E6",
+ "/arrowlongNWtobar": "\u21B8",
+ "/arrowright": "\u2192",
+ "/arrowrightdashed": "\u21E2",
+ "/arrowrightdblstroke": "\u21CF",
+ "/arrowrightdowncorner": "\u21B4",
+ "/arrowrightdowntip": "\u21B3",
+ "/arrowrightfrombar": "\u21A6",
+ "/arrowrightheavy": "\u279E",
+ "/arrowrighthook": "\u21AA",
+ "/arrowrightloop": "\u21AC",
+ "/arrowrightoverleft": "\u21C4",
+ "/arrowrightsmallcircle": "\u21F4",
+ "/arrowrightsquiggle": "\u21DD",
+ "/arrowrightstroke": "\u219B",
+ "/arrowrighttail": "\u21A3",
+ "/arrowrighttobar": "\u21E5",
+ "/arrowrighttwoheaded": "\u21A0",
+ "/arrowrightwave": "\u219D",
+ "/arrowrightwhite": "\u21E8",
+ "/arrowspaireddown": "\u21CA",
+ "/arrowspairedleft": "\u21C7",
+ "/arrowspairedright": "\u21C9",
+ "/arrowspairedup": "\u21C8",
+ "/arrowtableft": "\u21E4",
+ "/arrowtabright": "\u21E5",
+ "/arrowup": "\u2191",
+ "/arrowupdashed": "\u21E1",
+ "/arrowupdn": "\u2195",
+ "/arrowupdnbse": "\u21A8",
+ "/arrowupdown": "\u2195",
+ "/arrowupdownbase": "\u21A8",
+ "/arrowupdownwithbase": "\u21A8",
+ "/arrowupfrombar": "\u21A5",
+ "/arrowupleft": "\u2196",
+ "/arrowupleftofdown": "\u21C5",
+ "/arrowupright": "\u2197",
+ "/arrowuprighttip": "\u21B1",
+ "/arrowuptwoheaded": "\u219F",
+ "/arrowupwhite": "\u21E7",
+ "/arrowvertex": "\uF8E6",
+ "/articulatedLorry": "\u1F69B",
+ "/artistPalette": "\u1F3A8",
+ "/aruhuasquare": "\u3301",
+ "/asciicircum": "\u005E",
+ "/asciicircummonospace": "\uFF3E",
+ "/asciitilde": "\u007E",
+ "/asciitildemonospace": "\uFF5E",
+ "/ascript": "\u0251",
+ "/ascriptturned": "\u0252",
+ "/asmallhiragana": "\u3041",
+ "/asmallkatakana": "\u30A1",
+ "/asmallkatakanahalfwidth": "\uFF67",
+ "/asper": "\u1FFE",
+ "/asperacute": "\u1FDE",
+ "/aspergrave": "\u1FDD",
+ "/aspertilde": "\u1FDF",
+ "/assertion": "\u22A6",
+ "/asterisk": "\u002A",
+ "/asteriskaltonearabic": "\u066D",
+ "/asteriskarabic": "\u066D",
+ "/asteriskmath": "\u2217",
+ "/asteriskmonospace": "\uFF0A",
+ "/asterisksmall": "\uFE61",
+ "/asterism": "\u2042",
+ "/astonishedFace": "\u1F632",
+ "/astroke": "\u2C65",
+ "/astronomicaluranus": "\u26E2",
+ "/asuperior": "\uF6E9",
+ "/asympticallyequal": "\u2243",
+ "/asymptoticallyequal": "\u2243",
+ "/at": "\u0040",
+ "/athleticShoe": "\u1F45F",
+ "/atilde": "\u00E3",
+ "/atmonospace": "\uFF20",
+ "/atnachHafukh:hb": "\u05A2",
+ "/atom": "\u269B",
+ "/atsmall": "\uFE6B",
+ "/attentionideographiccircled": "\u329F",
+ "/aturned": "\u0250",
+ "/au": "\uA737",
+ "/aubengali": "\u0994",
+ "/aubergine": "\u1F346",
+ "/aubopomofo": "\u3120",
+ "/audeva": "\u0914",
+ "/aufullwidth": "\u3373",
+ "/augujarati": "\u0A94",
+ "/augurmukhi": "\u0A14",
+ "/augusttelegraph": "\u32C7",
+ "/aulengthmarkbengali": "\u09D7",
+ "/aumatragurmukhi": "\u0A4C",
+ "/austral": "\u20B3",
+ "/automatedTellerMachine": "\u1F3E7",
+ "/automobile": "\u1F697",
+ "/auvowelsignbengali": "\u09CC",
+ "/auvowelsigndeva": "\u094C",
+ "/auvowelsigngujarati": "\u0ACC",
+ "/av": "\uA739",
+ "/avagrahadeva": "\u093D",
+ "/avhorizontalbar": "\uA73B",
+ "/ay": "\uA73D",
+ "/aybarmenian": "\u0561",
+ "/ayin": "\u05E2",
+ "/ayin:hb": "\u05E2",
+ "/ayinalt:hb": "\uFB20",
+ "/ayinaltonehebrew": "\uFB20",
+ "/ayinhebrew": "\u05E2",
+ "/azla:hb": "\u059C",
+ "/b": "\u0062",
+ "/baarerusquare": "\u332D",
+ "/babengali": "\u09AC",
+ "/babyAngel": "\u1F47C",
+ "/babyBottle": "\u1F37C",
+ "/babyChick": "\u1F424",
+ "/backLeftwardsArrowAbove": "\u1F519",
+ "/backOfEnvelope": "\u1F582",
+ "/backslash": "\u005C",
+ "/backslashbarfunc": "\u2340",
+ "/backslashdbl": "\u244A",
+ "/backslashmonospace": "\uFF3C",
+ "/bactrianCamel": "\u1F42B",
+ "/badeva": "\u092C",
+ "/badmintonRacquetAndShuttlecock": "\u1F3F8",
+ "/bagdelimitersshapeleft": "\u27C5",
+ "/bagdelimitersshaperight": "\u27C6",
+ "/baggageClaim": "\u1F6C4",
+ "/bagujarati": "\u0AAC",
+ "/bagurmukhi": "\u0A2C",
+ "/bahiragana": "\u3070",
+ "/bahtthai": "\u0E3F",
+ "/bakatakana": "\u30D0",
+ "/balloon": "\u1F388",
+ "/ballotBoldScriptX": "\u1F5F6",
+ "/ballotBoxBallot": "\u1F5F3",
+ "/ballotBoxBoldCheck": "\u1F5F9",
+ "/ballotBoxBoldScriptX": "\u1F5F7",
+ "/ballotBoxScriptX": "\u1F5F5",
+ "/ballotScriptX": "\u1F5F4",
+ "/bamurda": "\uA9A8",
+ "/banana": "\u1F34C",
+ "/bank": "\u1F3E6",
+ "/banknoteDollarSign": "\u1F4B5",
+ "/banknoteEuroSign": "\u1F4B6",
+ "/banknotePoundSign": "\u1F4B7",
+ "/banknoteYenSign": "\u1F4B4",
+ "/bar": "\u007C",
+ "/barChart": "\u1F4CA",
+ "/barberPole": "\u1F488",
+ "/barfullwidth": "\u3374",
+ "/barmonospace": "\uFF5C",
+ "/barquillverticalleft": "\u2E20",
+ "/barquillverticalright": "\u2E21",
+ "/baseball": "\u26BE",
+ "/basketballAndHoop": "\u1F3C0",
+ "/bath": "\u1F6C0",
+ "/bathtub": "\u1F6C1",
+ "/battery": "\u1F50B",
+ "/bbopomofo": "\u3105",
+ "/bcircle": "\u24D1",
+ "/bdot": "\u1E03",
+ "/bdotaccent": "\u1E03",
+ "/bdotbelow": "\u1E05",
+ "/beachUmbrella": "\u1F3D6",
+ "/beamedAscendingMusicalNotes": "\u1F39C",
+ "/beamedDescendingMusicalNotes": "\u1F39D",
+ "/beamedeighthnotes": "\u266B",
+ "/beamedsixteenthnotes": "\u266C",
+ "/beamfunc": "\u2336",
+ "/bearFace": "\u1F43B",
+ "/beatingHeart": "\u1F493",
+ "/because": "\u2235",
+ "/becyr": "\u0431",
+ "/becyrillic": "\u0431",
+ "/bed": "\u1F6CF",
+ "/beeh": "\u067B",
+ "/beeh.fina": "\uFB53",
+ "/beeh.init": "\uFB54",
+ "/beeh.isol": "\uFB52",
+ "/beeh.medi": "\uFB55",
+ "/beerMug": "\u1F37A",
+ "/beetasquare": "\u333C",
+ "/beh": "\u0628",
+ "/beh.fina": "\uFE90",
+ "/beh.init": "\uFE91",
+ "/beh.init_alefmaksura.fina": "\uFC09",
+ "/beh.init_hah.fina": "\uFC06",
+ "/beh.init_hah.medi": "\uFC9D",
+ "/beh.init_heh.medi": "\uFCA0",
+ "/beh.init_jeem.fina": "\uFC05",
+ "/beh.init_jeem.medi": "\uFC9C",
+ "/beh.init_khah.fina": "\uFC07",
+ "/beh.init_khah.medi": "\uFC9E",
+ "/beh.init_meem.fina": "\uFC08",
+ "/beh.init_meem.medi": "\uFC9F",
+ "/beh.init_yeh.fina": "\uFC0A",
+ "/beh.isol": "\uFE8F",
+ "/beh.medi": "\uFE92",
+ "/beh.medi_alefmaksura.fina": "\uFC6E",
+ "/beh.medi_hah.medi_yeh.fina": "\uFDC2",
+ "/beh.medi_heh.medi": "\uFCE2",
+ "/beh.medi_khah.medi_yeh.fina": "\uFD9E",
+ "/beh.medi_meem.fina": "\uFC6C",
+ "/beh.medi_meem.medi": "\uFCE1",
+ "/beh.medi_noon.fina": "\uFC6D",
+ "/beh.medi_reh.fina": "\uFC6A",
+ "/beh.medi_yeh.fina": "\uFC6F",
+ "/beh.medi_zain.fina": "\uFC6B",
+ "/behDotBelowThreeDotsAbove": "\u0751",
+ "/behInvertedSmallVBelow": "\u0755",
+ "/behSmallV": "\u0756",
+ "/behThreeDotsHorizontallyBelow": "\u0750",
+ "/behThreeDotsUpBelow": "\u0752",
+ "/behThreeDotsUpBelowTwoDotsAbove": "\u0753",
+ "/behTwoDotsBelowDotAbove": "\u0754",
+ "/beharabic": "\u0628",
+ "/beheh": "\u0680",
+ "/beheh.fina": "\uFB5B",
+ "/beheh.init": "\uFB5C",
+ "/beheh.isol": "\uFB5A",
+ "/beheh.medi": "\uFB5D",
+ "/behfinalarabic": "\uFE90",
+ "/behinitialarabic": "\uFE91",
+ "/behiragana": "\u3079",
+ "/behmedialarabic": "\uFE92",
+ "/behmeeminitialarabic": "\uFC9F",
+ "/behmeemisolatedarabic": "\uFC08",
+ "/behnoonfinalarabic": "\uFC6D",
+ "/bekatakana": "\u30D9",
+ "/bellCancellationStroke": "\u1F515",
+ "/bellhopBell": "\u1F6CE",
+ "/beltbuckle": "\u2444",
+ "/benarmenian": "\u0562",
+ "/beng:a": "\u0985",
+ "/beng:aa": "\u0986",
+ "/beng:aasign": "\u09BE",
+ "/beng:abbreviationsign": "\u09FD",
+ "/beng:ai": "\u0990",
+ "/beng:aisign": "\u09C8",
+ "/beng:anji": "\u0980",
+ "/beng:anusvara": "\u0982",
+ "/beng:au": "\u0994",
+ "/beng:aulengthmark": "\u09D7",
+ "/beng:ausign": "\u09CC",
+ "/beng:avagraha": "\u09BD",
+ "/beng:ba": "\u09AC",
+ "/beng:bha": "\u09AD",
+ "/beng:ca": "\u099A",
+ "/beng:candrabindu": "\u0981",
+ "/beng:cha": "\u099B",
+ "/beng:currencyoneless": "\u09F8",
+ "/beng:da": "\u09A6",
+ "/beng:dda": "\u09A1",
+ "/beng:ddha": "\u09A2",
+ "/beng:dha": "\u09A7",
+ "/beng:e": "\u098F",
+ "/beng:eight": "\u09EE",
+ "/beng:esign": "\u09C7",
+ "/beng:five": "\u09EB",
+ "/beng:four": "\u09EA",
+ "/beng:fourcurrencynumerator": "\u09F7",
+ "/beng:ga": "\u0997",
+ "/beng:gandamark": "\u09FB",
+ "/beng:gha": "\u0998",
+ "/beng:ha": "\u09B9",
+ "/beng:i": "\u0987",
+ "/beng:ii": "\u0988",
+ "/beng:iisign": "\u09C0",
+ "/beng:isign": "\u09BF",
+ "/beng:isshar": "\u09FA",
+ "/beng:ja": "\u099C",
+ "/beng:jha": "\u099D",
+ "/beng:ka": "\u0995",
+ "/beng:kha": "\u0996",
+ "/beng:khandata": "\u09CE",
+ "/beng:la": "\u09B2",
+ "/beng:llvocal": "\u09E1",
+ "/beng:llvocalsign": "\u09E3",
+ "/beng:lvocal": "\u098C",
+ "/beng:lvocalsign": "\u09E2",
+ "/beng:ma": "\u09AE",
+ "/beng:na": "\u09A8",
+ "/beng:nga": "\u0999",
+ "/beng:nine": "\u09EF",
+ "/beng:nna": "\u09A3",
+ "/beng:nukta": "\u09BC",
+ "/beng:nya": "\u099E",
+ "/beng:o": "\u0993",
+ "/beng:one": "\u09E7",
+ "/beng:onecurrencynumerator": "\u09F4",
+ "/beng:osign": "\u09CB",
+ "/beng:pa": "\u09AA",
+ "/beng:pha": "\u09AB",
+ "/beng:ra": "\u09B0",
+ "/beng:ralowdiagonal": "\u09F1",
+ "/beng:ramiddiagonal": "\u09F0",
+ "/beng:rha": "\u09DD",
+ "/beng:rra": "\u09DC",
+ "/beng:rrvocal": "\u09E0",
+ "/beng:rrvocalsign": "\u09C4",
+ "/beng:rupee": "\u09F3",
+ "/beng:rupeemark": "\u09F2",
+ "/beng:rvocal": "\u098B",
+ "/beng:rvocalsign": "\u09C3",
+ "/beng:sa": "\u09B8",
+ "/beng:seven": "\u09ED",
+ "/beng:sha": "\u09B6",
+ "/beng:six": "\u09EC",
+ "/beng:sixteencurrencydenominator": "\u09F9",
+ "/beng:ssa": "\u09B7",
+ "/beng:ta": "\u09A4",
+ "/beng:tha": "\u09A5",
+ "/beng:three": "\u09E9",
+ "/beng:threecurrencynumerator": "\u09F6",
+ "/beng:tta": "\u099F",
+ "/beng:ttha": "\u09A0",
+ "/beng:two": "\u09E8",
+ "/beng:twocurrencynumerator": "\u09F5",
+ "/beng:u": "\u0989",
+ "/beng:usign": "\u09C1",
+ "/beng:uu": "\u098A",
+ "/beng:uusign": "\u09C2",
+ "/beng:vedicanusvara": "\u09FC",
+ "/beng:virama": "\u09CD",
+ "/beng:visarga": "\u0983",
+ "/beng:ya": "\u09AF",
+ "/beng:yya": "\u09DF",
+ "/beng:zero": "\u09E6",
+ "/bentoBox": "\u1F371",
+ "/benzenering": "\u232C",
+ "/benzeneringcircle": "\u23E3",
+ "/bet": "\u05D1",
+ "/bet:hb": "\u05D1",
+ "/beta": "\u03B2",
+ "/betasymbol": "\u03D0",
+ "/betasymbolgreek": "\u03D0",
+ "/betdagesh": "\uFB31",
+ "/betdageshhebrew": "\uFB31",
+ "/bethebrew": "\u05D1",
+ "/betrafehebrew": "\uFB4C",
+ "/between": "\u226C",
+ "/betwithdagesh:hb": "\uFB31",
+ "/betwithrafe:hb": "\uFB4C",
+ "/bflourish": "\uA797",
+ "/bhabengali": "\u09AD",
+ "/bhadeva": "\u092D",
+ "/bhagujarati": "\u0AAD",
+ "/bhagurmukhi": "\u0A2D",
+ "/bhook": "\u0253",
+ "/bicycle": "\u1F6B2",
+ "/bicyclist": "\u1F6B4",
+ "/bihiragana": "\u3073",
+ "/bikatakana": "\u30D3",
+ "/bikini": "\u1F459",
+ "/bilabialclick": "\u0298",
+ "/billiards": "\u1F3B1",
+ "/bindigurmukhi": "\u0A02",
+ "/biohazard": "\u2623",
+ "/bird": "\u1F426",
+ "/birthdayCake": "\u1F382",
+ "/birusquare": "\u3331",
+ "/bishopblack": "\u265D",
+ "/bishopwhite": "\u2657",
+ "/bitcoin": "\u20BF",
+ "/blackDownPointingBackhandIndex": "\u1F5A3",
+ "/blackDroplet": "\u1F322",
+ "/blackFolder": "\u1F5BF",
+ "/blackHardShellFloppyDisk": "\u1F5AA",
+ "/blackHeart": "\u1F5A4",
+ "/blackLeftPointingBackhandIndex": "\u1F59C",
+ "/blackPennant": "\u1F3F2",
+ "/blackPushpin": "\u1F588",
+ "/blackRightPointingBackhandIndex": "\u1F59D",
+ "/blackRosette": "\u1F3F6",
+ "/blackSkullAndCrossbones": "\u1F571",
+ "/blackSquareButton": "\u1F532",
+ "/blackTouchtoneTelephone": "\u1F57F",
+ "/blackUpPointingBackhandIndex": "\u1F5A2",
+ "/blackcircle": "\u25CF",
+ "/blackcircleforrecord": "\u23FA",
+ "/blackdiamond": "\u25C6",
+ "/blackdownpointingtriangle": "\u25BC",
+ "/blackforstopsquare": "\u23F9",
+ "/blackleftpointingpointer": "\u25C4",
+ "/blackleftpointingtriangle": "\u25C0",
+ "/blacklenticularbracketleft": "\u3010",
+ "/blacklenticularbracketleftvertical": "\uFE3B",
+ "/blacklenticularbracketright": "\u3011",
+ "/blacklenticularbracketrightvertical": "\uFE3C",
+ "/blacklowerlefttriangle": "\u25E3",
+ "/blacklowerrighttriangle": "\u25E2",
+ "/blackmediumpointingtriangledown": "\u23F7",
+ "/blackmediumpointingtriangleleft": "\u23F4",
+ "/blackmediumpointingtriangleright": "\u23F5",
+ "/blackmediumpointingtriangleup": "\u23F6",
+ "/blackpointingdoubletrianglebarverticalleft": "\u23EE",
+ "/blackpointingdoubletrianglebarverticalright": "\u23ED",
+ "/blackpointingdoubletriangledown": "\u23EC",
+ "/blackpointingdoubletriangleleft": "\u23EA",
+ "/blackpointingdoubletriangleright": "\u23E9",
+ "/blackpointingdoubletriangleup": "\u23EB",
+ "/blackpointingtriangledoublebarverticalright": "\u23EF",
+ "/blackrectangle": "\u25AC",
+ "/blackrightpointingpointer": "\u25BA",
+ "/blackrightpointingtriangle": "\u25B6",
+ "/blacksmallsquare": "\u25AA",
+ "/blacksmilingface": "\u263B",
+ "/blacksquare": "\u25A0",
+ "/blackstar": "\u2605",
+ "/blackupperlefttriangle": "\u25E4",
+ "/blackupperrighttriangle": "\u25E5",
+ "/blackuppointingsmalltriangle": "\u25B4",
+ "/blackuppointingtriangle": "\u25B2",
+ "/blackwardsbulletleft": "\u204C",
+ "/blackwardsbulletright": "\u204D",
+ "/blank": "\u2423",
+ "/blinebelow": "\u1E07",
+ "/block": "\u2588",
+ "/blossom": "\u1F33C",
+ "/blowfish": "\u1F421",
+ "/blueBook": "\u1F4D8",
+ "/blueHeart": "\u1F499",
+ "/bmonospace": "\uFF42",
+ "/boar": "\u1F417",
+ "/board": "\u2328",
+ "/bobaimaithai": "\u0E1A",
+ "/bohiragana": "\u307C",
+ "/bokatakana": "\u30DC",
+ "/bomb": "\u1F4A3",
+ "/book": "\u1F56E",
+ "/bookmark": "\u1F516",
+ "/bookmarkTabs": "\u1F4D1",
+ "/books": "\u1F4DA",
+ "/bopo:a": "\u311A",
+ "/bopo:ai": "\u311E",
+ "/bopo:an": "\u3122",
+ "/bopo:ang": "\u3124",
+ "/bopo:au": "\u3120",
+ "/bopo:b": "\u3105",
+ "/bopo:c": "\u3118",
+ "/bopo:ch": "\u3114",
+ "/bopo:d": "\u3109",
+ "/bopo:e": "\u311C",
+ "/bopo:eh": "\u311D",
+ "/bopo:ei": "\u311F",
+ "/bopo:en": "\u3123",
+ "/bopo:eng": "\u3125",
+ "/bopo:er": "\u3126",
+ "/bopo:f": "\u3108",
+ "/bopo:g": "\u310D",
+ "/bopo:gn": "\u312C",
+ "/bopo:h": "\u310F",
+ "/bopo:i": "\u3127",
+ "/bopo:ih": "\u312D",
+ "/bopo:iu": "\u3129",
+ "/bopo:j": "\u3110",
+ "/bopo:k": "\u310E",
+ "/bopo:l": "\u310C",
+ "/bopo:m": "\u3107",
+ "/bopo:n": "\u310B",
+ "/bopo:ng": "\u312B",
+ "/bopo:o": "\u311B",
+ "/bopo:ou": "\u3121",
+ "/bopo:owithdotabove": "\u312E",
+ "/bopo:p": "\u3106",
+ "/bopo:q": "\u3111",
+ "/bopo:r": "\u3116",
+ "/bopo:s": "\u3119",
+ "/bopo:sh": "\u3115",
+ "/bopo:t": "\u310A",
+ "/bopo:u": "\u3128",
+ "/bopo:v": "\u312A",
+ "/bopo:x": "\u3112",
+ "/bopo:z": "\u3117",
+ "/bopo:zh": "\u3113",
+ "/borutosquare": "\u333E",
+ "/bottlePoppingCork": "\u1F37E",
+ "/bouquet": "\u1F490",
+ "/bouquetOfFlowers": "\u1F395",
+ "/bowAndArrow": "\u1F3F9",
+ "/bowlOfHygieia": "\u1F54F",
+ "/bowling": "\u1F3B3",
+ "/boxlineverticalleft": "\u23B8",
+ "/boxlineverticalright": "\u23B9",
+ "/boy": "\u1F466",
+ "/boys": "\u1F6C9",
+ "/bparen": "\u249D",
+ "/bparenthesized": "\u249D",
+ "/bqfullwidth": "\u33C3",
+ "/bqsquare": "\u33C3",
+ "/braceex": "\uF8F4",
+ "/braceleft": "\u007B",
+ "/braceleftbt": "\uF8F3",
+ "/braceleftmid": "\uF8F2",
+ "/braceleftmonospace": "\uFF5B",
+ "/braceleftsmall": "\uFE5B",
+ "/bracelefttp": "\uF8F1",
+ "/braceleftvertical": "\uFE37",
+ "/braceright": "\u007D",
+ "/bracerightbt": "\uF8FE",
+ "/bracerightmid": "\uF8FD",
+ "/bracerightmonospace": "\uFF5D",
+ "/bracerightsmall": "\uFE5C",
+ "/bracerighttp": "\uF8FC",
+ "/bracerightvertical": "\uFE38",
+ "/bracketangledblleft": "\u27EA",
+ "/bracketangledblright": "\u27EB",
+ "/bracketangleleft": "\u27E8",
+ "/bracketangleright": "\u27E9",
+ "/bracketbottomcurly": "\u23DF",
+ "/bracketbottomsquare": "\u23B5",
+ "/bracketcornerupleftsquare": "\u23A1",
+ "/bracketcorneruprightsquare": "\u23A4",
+ "/bracketdottedsubstitutionleft": "\u2E04",
+ "/bracketdottedsubstitutionright": "\u2E05",
+ "/bracketextensioncurly": "\u23AA",
+ "/bracketextensionleftsquare": "\u23A2",
+ "/bracketextensionrightsquare": "\u23A5",
+ "/brackethalfbottomleft": "\u2E24",
+ "/brackethalfbottomright": "\u2E25",
+ "/brackethalftopleft": "\u2E22",
+ "/brackethalftopright": "\u2E23",
+ "/brackethookupleftcurly": "\u23A7",
+ "/brackethookuprightcurly": "\u23AB",
+ "/bracketleft": "\u005B",
+ "/bracketleftbt": "\uF8F0",
+ "/bracketleftex": "\uF8EF",
+ "/bracketleftmonospace": "\uFF3B",
+ "/bracketleftsquarequill": "\u2045",
+ "/bracketlefttp": "\uF8EE",
+ "/bracketlowercornerleftsquare": "\u23A3",
+ "/bracketlowercornerrightsquare": "\u23A6",
+ "/bracketlowerhookleftcurly": "\u23A9",
+ "/bracketlowerhookrightcurly": "\u23AD",
+ "/bracketmiddlepieceleftcurly": "\u23A8",
+ "/bracketmiddlepiecerightcurly": "\u23AC",
+ "/bracketoverbrackettopbottomsquare": "\u23B6",
+ "/bracketparaphraselowleft": "\u2E1C",
+ "/bracketparaphraselowright": "\u2E1D",
+ "/bracketraisedleft": "\u2E0C",
+ "/bracketraisedright": "\u2E0D",
+ "/bracketright": "\u005D",
+ "/bracketrightbt": "\uF8FB",
+ "/bracketrightex": "\uF8FA",
+ "/bracketrightmonospace": "\uFF3D",
+ "/bracketrightsquarequill": "\u2046",
+ "/bracketrighttp": "\uF8F9",
+ "/bracketsectionupleftlowerrightcurly": "\u23B0",
+ "/bracketsectionuprightlowerleftcurly": "\u23B1",
+ "/bracketshellbottom": "\u23E1",
+ "/bracketshelltop": "\u23E0",
+ "/bracketshellwhiteleft": "\u27EC",
+ "/bracketshellwhiteright": "\u27ED",
+ "/bracketsubstitutionleft": "\u2E02",
+ "/bracketsubstitutionright": "\u2E03",
+ "/brackettopcurly": "\u23DE",
+ "/brackettopsquare": "\u23B4",
+ "/brackettranspositionleft": "\u2E09",
+ "/brackettranspositionright": "\u2E0A",
+ "/bracketwhitesquareleft": "\u27E6",
+ "/bracketwhitesquareright": "\u27E7",
+ "/branchbankidentification": "\u2446",
+ "/bread": "\u1F35E",
+ "/breve": "\u02D8",
+ "/brevebelowcmb": "\u032E",
+ "/brevecmb": "\u0306",
+ "/breveinvertedbelowcmb": "\u032F",
+ "/breveinvertedcmb": "\u0311",
+ "/breveinverteddoublecmb": "\u0361",
+ "/brevemetrical": "\u23D1",
+ "/brideVeil": "\u1F470",
+ "/bridgeAtNight": "\u1F309",
+ "/bridgebelowcmb": "\u032A",
+ "/bridgeinvertedbelowcmb": "\u033A",
+ "/briefcase": "\u1F4BC",
+ "/brll:blank": "\u2800",
+ "/brokenHeart": "\u1F494",
+ "/brokenbar": "\u00A6",
+ "/brokencirclenorthwestarrow": "\u238B",
+ "/bstroke": "\u0180",
+ "/bsuperior": "\uF6EA",
+ "/btopbar": "\u0183",
+ "/bug": "\u1F41B",
+ "/buhiragana": "\u3076",
+ "/buildingConstruction": "\u1F3D7",
+ "/bukatakana": "\u30D6",
+ "/bullet": "\u2022",
+ "/bulletinverse": "\u25D8",
+ "/bulletoperator": "\u2219",
+ "/bullhorn": "\u1F56B",
+ "/bullhornSoundWaves": "\u1F56C",
+ "/bullseye": "\u25CE",
+ "/burrito": "\u1F32F",
+ "/bus": "\u1F68C",
+ "/busStop": "\u1F68F",
+ "/bussyerusquare": "\u3334",
+ "/bustInSilhouette": "\u1F464",
+ "/bustsInSilhouette": "\u1F465",
+ "/c": "\u0063",
+ "/caarmenian": "\u056E",
+ "/cabengali": "\u099A",
+ "/cactus": "\u1F335",
+ "/cacute": "\u0107",
+ "/cadauna": "\u2106",
+ "/cadeva": "\u091A",
+ "/caduceus": "\u2624",
+ "/cagujarati": "\u0A9A",
+ "/cagurmukhi": "\u0A1A",
+ "/cakraconsonant": "\uA9BF",
+ "/calendar": "\u1F4C5",
+ "/calfullwidth": "\u3388",
+ "/callideographicparen": "\u323A",
+ "/calsquare": "\u3388",
+ "/camera": "\u1F4F7",
+ "/cameraFlash": "\u1F4F8",
+ "/camping": "\u1F3D5",
+ "/camurda": "\uA996",
+ "/cancellationX": "\u1F5D9",
+ "/cancer": "\u264B",
+ "/candle": "\u1F56F",
+ "/candrabindubengali": "\u0981",
+ "/candrabinducmb": "\u0310",
+ "/candrabindudeva": "\u0901",
+ "/candrabindugujarati": "\u0A81",
+ "/candy": "\u1F36C",
+ "/canoe": "\u1F6F6",
+ "/capitulum": "\u2E3F",
+ "/capricorn": "\u2651",
+ "/capslock": "\u21EA",
+ "/cardFileBox": "\u1F5C3",
+ "/cardIndex": "\u1F4C7",
+ "/cardIndexDividers": "\u1F5C2",
+ "/careof": "\u2105",
+ "/caret": "\u2038",
+ "/caretinsertionpoint": "\u2041",
+ "/carettildedownfunc": "\u2371",
+ "/carettildeupfunc": "\u2372",
+ "/caron": "\u02C7",
+ "/caronbelowcmb": "\u032C",
+ "/caroncmb": "\u030C",
+ "/carouselHorse": "\u1F3A0",
+ "/carpStreamer": "\u1F38F",
+ "/carriagereturn": "\u21B5",
+ "/carsliding": "\u26D0",
+ "/castle": "\u26EB",
+ "/cat": "\u1F408",
+ "/catFace": "\u1F431",
+ "/catFaceWithTearsOfJoy": "\u1F639",
+ "/catFaceWithWrySmile": "\u1F63C",
+ "/caution": "\u2621",
+ "/cbar": "\uA793",
+ "/cbopomofo": "\u3118",
+ "/ccaron": "\u010D",
+ "/ccedilla": "\u00E7",
+ "/ccedillaacute": "\u1E09",
+ "/ccfullwidth": "\u33C4",
+ "/ccircle": "\u24D2",
+ "/ccircumflex": "\u0109",
+ "/ccurl": "\u0255",
+ "/cdfullwidth": "\u33C5",
+ "/cdot": "\u010B",
+ "/cdotaccent": "\u010B",
+ "/cdotreversed": "\uA73F",
+ "/cdsquare": "\u33C5",
+ "/cecak": "\uA981",
+ "/cecaktelu": "\uA9B3",
+ "/cedi": "\u20B5",
+ "/cedilla": "\u00B8",
+ "/cedillacmb": "\u0327",
+ "/ceilingleft": "\u2308",
+ "/ceilingright": "\u2309",
+ "/celticCross": "\u1F548",
+ "/cent": "\u00A2",
+ "/centigrade": "\u2103",
+ "/centinferior": "\uF6DF",
+ "/centmonospace": "\uFFE0",
+ "/centoldstyle": "\uF7A2",
+ "/centreddotwhitediamond": "\u27D0",
+ "/centreideographiccircled": "\u32A5",
+ "/centreline": "\u2104",
+ "/centrelineverticalsquarewhite": "\u2385",
+ "/centsuperior": "\uF6E0",
+ "/ceres": "\u26B3",
+ "/chaarmenian": "\u0579",
+ "/chabengali": "\u099B",
+ "/chadeva": "\u091B",
+ "/chagujarati": "\u0A9B",
+ "/chagurmukhi": "\u0A1B",
+ "/chains": "\u26D3",
+ "/chair": "\u2441",
+ "/chamkocircle": "\u327C",
+ "/charactertie": "\u2040",
+ "/chartDownwardsTrend": "\u1F4C9",
+ "/chartUpwardsTrend": "\u1F4C8",
+ "/chartUpwardsTrendAndYenSign": "\u1F4B9",
+ "/chbopomofo": "\u3114",
+ "/cheabkhasiancyrillic": "\u04BD",
+ "/cheabkhcyr": "\u04BD",
+ "/cheabkhtailcyr": "\u04BF",
+ "/checkbox": "\u2610",
+ "/checkboxchecked": "\u2611",
+ "/checkboxx": "\u2612",
+ "/checkmark": "\u2713",
+ "/checyr": "\u0447",
+ "/checyrillic": "\u0447",
+ "/chedescenderabkhasiancyrillic": "\u04BF",
+ "/chedescendercyrillic": "\u04B7",
+ "/chedieresiscyr": "\u04F5",
+ "/chedieresiscyrillic": "\u04F5",
+ "/cheeringMegaphone": "\u1F4E3",
+ "/cheharmenian": "\u0573",
+ "/chekhakascyr": "\u04CC",
+ "/chekhakassiancyrillic": "\u04CC",
+ "/chequeredFlag": "\u1F3C1",
+ "/cherries": "\u1F352",
+ "/cherryBlossom": "\u1F338",
+ "/chestnut": "\u1F330",
+ "/chetailcyr": "\u04B7",
+ "/chevertcyr": "\u04B9",
+ "/cheverticalstrokecyrillic": "\u04B9",
+ "/chi": "\u03C7",
+ "/chicken": "\u1F414",
+ "/chieuchacirclekorean": "\u3277",
+ "/chieuchaparenkorean": "\u3217",
+ "/chieuchcirclekorean": "\u3269",
+ "/chieuchkorean": "\u314A",
+ "/chieuchparenkorean": "\u3209",
+ "/childrenCrossing": "\u1F6B8",
+ "/chipmunk": "\u1F43F",
+ "/chirho": "\u2627",
+ "/chiron": "\u26B7",
+ "/chochangthai": "\u0E0A",
+ "/chochanthai": "\u0E08",
+ "/chochingthai": "\u0E09",
+ "/chochoethai": "\u0E0C",
+ "/chocolateBar": "\u1F36B",
+ "/chook": "\u0188",
+ "/christmasTree": "\u1F384",
+ "/church": "\u26EA",
+ "/cieucacirclekorean": "\u3276",
+ "/cieucaparenkorean": "\u3216",
+ "/cieuccirclekorean": "\u3268",
+ "/cieuckorean": "\u3148",
+ "/cieucparenkorean": "\u3208",
+ "/cieucuparenkorean": "\u321C",
+ "/cinema": "\u1F3A6",
+ "/circle": "\u25CB",
+ "/circleallbutupperquadrantleftblack": "\u25D5",
+ "/circlebackslashfunc": "\u2349",
+ "/circleblack": "\u25CF",
+ "/circledCrossPommee": "\u1F540",
+ "/circledInformationSource": "\u1F6C8",
+ "/circledasteriskoperator": "\u229B",
+ "/circledbarnotchhorizontal": "\u2389",
+ "/circledcrossinglanes": "\u26D2",
+ "/circleddash": "\u229D",
+ "/circleddivisionslash": "\u2298",
+ "/circleddotoperator": "\u2299",
+ "/circledequals": "\u229C",
+ "/circlediaeresisfunc": "\u2365",
+ "/circledminus": "\u2296",
+ "/circledot": "\u2299",
+ "/circledotrightwhite": "\u2686",
+ "/circledotted": "\u25CC",
+ "/circledringoperator": "\u229A",
+ "/circledtriangledown": "\u238A",
+ "/circlehalfleftblack": "\u25D0",
+ "/circlehalfrightblack": "\u25D1",
+ "/circleinversewhite": "\u25D9",
+ "/circlejotfunc": "\u233E",
+ "/circlelowerhalfblack": "\u25D2",
+ "/circlelowerquadrantleftwhite": "\u25F5",
+ "/circlelowerquadrantrightwhite": "\u25F6",
+ "/circlemultiply": "\u2297",
+ "/circleot": "\u2299",
+ "/circleplus": "\u2295",
+ "/circlepostalmark": "\u3036",
+ "/circlestarfunc": "\u235F",
+ "/circlestilefunc": "\u233D",
+ "/circlestroketwodotsaboveheavy": "\u26E3",
+ "/circletwodotsblackwhite": "\u2689",
+ "/circletwodotswhite": "\u2687",
+ "/circleunderlinefunc": "\u235C",
+ "/circleupperhalfblack": "\u25D3",
+ "/circleupperquadrantleftwhite": "\u25F4",
+ "/circleupperquadrantrightblack": "\u25D4",
+ "/circleupperquadrantrightwhite": "\u25F7",
+ "/circleverticalfill": "\u25CD",
+ "/circlewhite": "\u25CB",
+ "/circlewhitedotrightblack": "\u2688",
+ "/circlewithlefthalfblack": "\u25D0",
+ "/circlewithrighthalfblack": "\u25D1",
+ "/circumflex": "\u02C6",
+ "/circumflexbelowcmb": "\u032D",
+ "/circumflexcmb": "\u0302",
+ "/circumflexlow": "\uA788",
+ "/circusTent": "\u1F3AA",
+ "/cityscape": "\u1F3D9",
+ "/cityscapeAtDusk": "\u1F306",
+ "/cjk:ideographiccomma": "\u3001",
+ "/cjk:tortoiseshellbracketleft": "\u3014",
+ "/cjk:tortoiseshellbracketright": "\u3015",
+ "/clamshellMobilePhone": "\u1F581",
+ "/clapperBoard": "\u1F3AC",
+ "/clappingHandsSign": "\u1F44F",
+ "/classicalBuilding": "\u1F3DB",
+ "/clear": "\u2327",
+ "/clearscreen": "\u239A",
+ "/clickalveolar": "\u01C2",
+ "/clickbilabial": "\u0298",
+ "/clickdental": "\u01C0",
+ "/clicklateral": "\u01C1",
+ "/clickretroflex": "\u01C3",
+ "/clinkingBeerMugs": "\u1F37B",
+ "/clipboard": "\u1F4CB",
+ "/clockFaceEight-thirty": "\u1F563",
+ "/clockFaceEightOclock": "\u1F557",
+ "/clockFaceEleven-thirty": "\u1F566",
+ "/clockFaceElevenOclock": "\u1F55A",
+ "/clockFaceFive-thirty": "\u1F560",
+ "/clockFaceFiveOclock": "\u1F554",
+ "/clockFaceFour-thirty": "\u1F55F",
+ "/clockFaceFourOclock": "\u1F553",
+ "/clockFaceNine-thirty": "\u1F564",
+ "/clockFaceNineOclock": "\u1F558",
+ "/clockFaceOne-thirty": "\u1F55C",
+ "/clockFaceOneOclock": "\u1F550",
+ "/clockFaceSeven-thirty": "\u1F562",
+ "/clockFaceSevenOclock": "\u1F556",
+ "/clockFaceSix-thirty": "\u1F561",
+ "/clockFaceSixOclock": "\u1F555",
+ "/clockFaceTen-thirty": "\u1F565",
+ "/clockFaceTenOclock": "\u1F559",
+ "/clockFaceThree-thirty": "\u1F55E",
+ "/clockFaceThreeOclock": "\u1F552",
+ "/clockFaceTwelve-thirty": "\u1F567",
+ "/clockFaceTwelveOclock": "\u1F55B",
+ "/clockFaceTwo-thirty": "\u1F55D",
+ "/clockFaceTwoOclock": "\u1F551",
+ "/clockwiseDownwardsAndUpwardsOpenCircleArrows": "\u1F503",
+ "/clockwiseRightAndLeftSemicircleArrows": "\u1F5D8",
+ "/clockwiseRightwardsAndLeftwardsOpenCircleArrows": "\u1F501",
+ "/clockwiseRightwardsAndLeftwardsOpenCircleArrowsCircledOneOverlay": "\u1F502",
+ "/closedBook": "\u1F4D5",
+ "/closedLockKey": "\u1F510",
+ "/closedMailboxLoweredFlag": "\u1F4EA",
+ "/closedMailboxRaisedFlag": "\u1F4EB",
+ "/closedUmbrella": "\u1F302",
+ "/closedentryleft": "\u26DC",
+ "/closeup": "\u2050",
+ "/cloud": "\u2601",
+ "/cloudLightning": "\u1F329",
+ "/cloudRain": "\u1F327",
+ "/cloudSnow": "\u1F328",
+ "/cloudTornado": "\u1F32A",
+ "/clsquare": "\u1F191",
+ "/club": "\u2663",
+ "/clubblack": "\u2663",
+ "/clubsuitblack": "\u2663",
+ "/clubsuitwhite": "\u2667",
+ "/clubwhite": "\u2667",
+ "/cm2fullwidth": "\u33A0",
+ "/cm3fullwidth": "\u33A4",
+ "/cmb:a": "\u0363",
+ "/cmb:aaboveflat": "\u1DD3",
+ "/cmb:aboveogonek": "\u1DCE",
+ "/cmb:acute": "\u0301",
+ "/cmb:acutebelow": "\u0317",
+ "/cmb:acutegraveacute": "\u1DC9",
+ "/cmb:acutemacron": "\u1DC7",
+ "/cmb:acutetone": "\u0341",
+ "/cmb:adieresis": "\u1DF2",
+ "/cmb:ae": "\u1DD4",
+ "/cmb:almostequalabove": "\u034C",
+ "/cmb:almostequaltobelow": "\u1DFD",
+ "/cmb:alpha": "\u1DE7",
+ "/cmb:ao": "\u1DD5",
+ "/cmb:arrowheadleftbelow": "\u0354",
+ "/cmb:arrowheadrightabove": "\u0350",
+ "/cmb:arrowheadrightarrowheadupbelow": "\u0356",
+ "/cmb:arrowheadrightbelow": "\u0355",
+ "/cmb:arrowleftrightbelow": "\u034D",
+ "/cmb:arrowrightdoublebelow": "\u0362",
+ "/cmb:arrowupbelow": "\u034E",
+ "/cmb:asteriskbelow": "\u0359",
+ "/cmb:av": "\u1DD6",
+ "/cmb:b": "\u1DE8",
+ "/cmb:belowbreve": "\u032E",
+ "/cmb:beta": "\u1DE9",
+ "/cmb:breve": "\u0306",
+ "/cmb:brevemacron": "\u1DCB",
+ "/cmb:bridgeabove": "\u0346",
+ "/cmb:bridgebelow": "\u032A",
+ "/cmb:c": "\u0368",
+ "/cmb:candrabindu": "\u0310",
+ "/cmb:caron": "\u030C",
+ "/cmb:caronbelow": "\u032C",
+ "/cmb:ccedilla": "\u1DD7",
+ "/cmb:cedilla": "\u0327",
+ "/cmb:circumflex": "\u0302",
+ "/cmb:circumflexbelow": "\u032D",
+ "/cmb:commaaccentbelow": "\u0326",
+ "/cmb:commaturnedabove": "\u0312",
+ "/cmb:d": "\u0369",
+ "/cmb:dblarchinvertedbelow": "\u032B",
+ "/cmb:dbloverline": "\u033F",
+ "/cmb:dblverticallineabove": "\u030E",
+ "/cmb:dblverticallinebelow": "\u0348",
+ "/cmb:deletionmark": "\u1DFB",
+ "/cmb:dialytikatonos": "\u0344",
+ "/cmb:dieresis": "\u0308",
+ "/cmb:dieresisbelow": "\u0324",
+ "/cmb:dotaboveleft": "\u1DF8",
+ "/cmb:dotaccent": "\u0307",
+ "/cmb:dotbelowcomb": "\u0323",
+ "/cmb:dotrightabove": "\u0358",
+ "/cmb:dottedacute": "\u1DC1",
+ "/cmb:dottedgrave": "\u1DC0",
+ "/cmb:doubleabovecircumflex": "\u1DCD",
+ "/cmb:doublebelowbreve": "\u035C",
+ "/cmb:doublebreve": "\u035D",
+ "/cmb:doubleinvertedbelowbreve": "\u1DFC",
+ "/cmb:doubleringbelow": "\u035A",
+ "/cmb:downtackbelow": "\u031E",
+ "/cmb:e": "\u0364",
+ "/cmb:equalbelow": "\u0347",
+ "/cmb:esh": "\u1DEF",
+ "/cmb:eth": "\u1DD9",
+ "/cmb:f": "\u1DEB",
+ "/cmb:fermata": "\u0352",
+ "/cmb:g": "\u1DDA",
+ "/cmb:graphemejoiner": "\u034F",
+ "/cmb:grave": "\u0300",
+ "/cmb:graveacutegrave": "\u1DC8",
+ "/cmb:gravebelow": "\u0316",
+ "/cmb:gravedouble": "\u030F",
+ "/cmb:gravemacron": "\u1DC5",
+ "/cmb:gravetone": "\u0340",
+ "/cmb:gsmall": "\u1DDB",
+ "/cmb:h": "\u036A",
+ "/cmb:halfleftringabove": "\u0351",
+ "/cmb:halfleftringbelow": "\u031C",
+ "/cmb:halfrightringabove": "\u0357",
+ "/cmb:halfrightringbelow": "\u0339",
+ "/cmb:homotheticabove": "\u034B",
+ "/cmb:hookabove": "\u0309",
+ "/cmb:horn": "\u031B",
+ "/cmb:hungarumlaut": "\u030B",
+ "/cmb:i": "\u0365",
+ "/cmb:insulard": "\u1DD8",
+ "/cmb:invertedbelowbreve": "\u032F",
+ "/cmb:invertedbreve": "\u0311",
+ "/cmb:invertedbridgebelow": "\u033A",
+ "/cmb:inverteddoublebreve": "\u0361",
+ "/cmb:iotasub": "\u0345",
+ "/cmb:isbelow": "\u1DD0",
+ "/cmb:k": "\u1DDC",
+ "/cmb:kavykaaboveleft": "\u1DF7",
+ "/cmb:kavykaaboveright": "\u1DF6",
+ "/cmb:koronis": "\u0343",
+ "/cmb:l": "\u1DDD",
+ "/cmb:leftangleabove": "\u031A",
+ "/cmb:leftanglebelow": "\u0349",
+ "/cmb:leftarrowheadabove": "\u1DFE",
+ "/cmb:lefttackbelow": "\u0318",
+ "/cmb:lineverticalabove": "\u030D",
+ "/cmb:lineverticalbelow": "\u0329",
+ "/cmb:longs": "\u1DE5",
+ "/cmb:lowline": "\u0332",
+ "/cmb:lowlinedouble": "\u0333",
+ "/cmb:lsmall": "\u1DDE",
+ "/cmb:lwithdoublemiddletilde": "\u1DEC",
+ "/cmb:m": "\u036B",
+ "/cmb:macron": "\u0304",
+ "/cmb:macronacute": "\u1DC4",
+ "/cmb:macronbelow": "\u0331",
+ "/cmb:macronbreve": "\u1DCC",
+ "/cmb:macrondouble": "\u035E",
+ "/cmb:macrondoublebelow": "\u035F",
+ "/cmb:macrongrave": "\u1DC6",
+ "/cmb:minusbelow": "\u0320",
+ "/cmb:msmall": "\u1DDF",
+ "/cmb:n": "\u1DE0",
+ "/cmb:nottildeabove": "\u034A",
+ "/cmb:nsmall": "\u1DE1",
+ "/cmb:o": "\u0366",
+ "/cmb:odieresis": "\u1DF3",
+ "/cmb:ogonek": "\u0328",
+ "/cmb:overlaystrokelong": "\u0336",
+ "/cmb:overlaystrokeshort": "\u0335",
+ "/cmb:overline": "\u0305",
+ "/cmb:owithlightcentralizationstroke": "\u1DED",
+ "/cmb:p": "\u1DEE",
+ "/cmb:palatalizedhookbelow": "\u0321",
+ "/cmb:perispomeni": "\u0342",
+ "/cmb:plusbelow": "\u031F",
+ "/cmb:r": "\u036C",
+ "/cmb:rbelow": "\u1DCA",
+ "/cmb:retroflexhookbelow": "\u0322",
+ "/cmb:reversedcommaabove": "\u0314",
+ "/cmb:rightarrowheadanddownarrowheadbelow": "\u1DFF",
+ "/cmb:righttackbelow": "\u0319",
+ "/cmb:ringabove": "\u030A",
+ "/cmb:ringbelow": "\u0325",
+ "/cmb:rrotunda": "\u1DE3",
+ "/cmb:rsmall": "\u1DE2",
+ "/cmb:s": "\u1DE4",
+ "/cmb:schwa": "\u1DEA",
+ "/cmb:seagullbelow": "\u033C",
+ "/cmb:snakebelow": "\u1DC2",
+ "/cmb:soliduslongoverlay": "\u0338",
+ "/cmb:solidusshortoverlay": "\u0337",
+ "/cmb:squarebelow": "\u033B",
+ "/cmb:suspensionmark": "\u1DC3",
+ "/cmb:t": "\u036D",
+ "/cmb:tilde": "\u0303",
+ "/cmb:tildebelow": "\u0330",
+ "/cmb:tildedouble": "\u0360",
+ "/cmb:tildeoverlay": "\u0334",
+ "/cmb:tildevertical": "\u033E",
+ "/cmb:turnedabove": "\u0313",
+ "/cmb:turnedcommaabove": "\u0315",
+ "/cmb:u": "\u0367",
+ "/cmb:udieresis": "\u1DF4",
+ "/cmb:uptackabove": "\u1DF5",
+ "/cmb:uptackbelow": "\u031D",
+ "/cmb:urabove": "\u1DD1",
+ "/cmb:usabove": "\u1DD2",
+ "/cmb:uwithlightcentralizationstroke": "\u1DF0",
+ "/cmb:v": "\u036E",
+ "/cmb:w": "\u1DF1",
+ "/cmb:wideinvertedbridgebelow": "\u1DF9",
+ "/cmb:x": "\u036F",
+ "/cmb:xabove": "\u033D",
+ "/cmb:xbelow": "\u0353",
+ "/cmb:z": "\u1DE6",
+ "/cmb:zigzagabove": "\u035B",
+ "/cmb:zigzagbelow": "\u1DCF",
+ "/cmcubedsquare": "\u33A4",
+ "/cmfullwidth": "\u339D",
+ "/cmonospace": "\uFF43",
+ "/cmsquaredsquare": "\u33A0",
+ "/cntr:acknowledge": "\u2406",
+ "/cntr:backspace": "\u2408",
+ "/cntr:bell": "\u2407",
+ "/cntr:blank": "\u2422",
+ "/cntr:cancel": "\u2418",
+ "/cntr:carriagereturn": "\u240D",
+ "/cntr:datalinkescape": "\u2410",
+ "/cntr:delete": "\u2421",
+ "/cntr:deleteformtwo": "\u2425",
+ "/cntr:devicecontrolfour": "\u2414",
+ "/cntr:devicecontrolone": "\u2411",
+ "/cntr:devicecontrolthree": "\u2413",
+ "/cntr:devicecontroltwo": "\u2412",
+ "/cntr:endofmedium": "\u2419",
+ "/cntr:endoftext": "\u2403",
+ "/cntr:endoftransmission": "\u2404",
+ "/cntr:endoftransmissionblock": "\u2417",
+ "/cntr:enquiry": "\u2405",
+ "/cntr:escape": "\u241B",
+ "/cntr:fileseparator": "\u241C",
+ "/cntr:formfeed": "\u240C",
+ "/cntr:groupseparator": "\u241D",
+ "/cntr:horizontaltab": "\u2409",
+ "/cntr:linefeed": "\u240A",
+ "/cntr:negativeacknowledge": "\u2415",
+ "/cntr:newline": "\u2424",
+ "/cntr:null": "\u2400",
+ "/cntr:openbox": "\u2423",
+ "/cntr:recordseparator": "\u241E",
+ "/cntr:shiftin": "\u240F",
+ "/cntr:shiftout": "\u240E",
+ "/cntr:space": "\u2420",
+ "/cntr:startofheading": "\u2401",
+ "/cntr:startoftext": "\u2402",
+ "/cntr:substitute": "\u241A",
+ "/cntr:substituteformtwo": "\u2426",
+ "/cntr:synchronousidle": "\u2416",
+ "/cntr:unitseparator": "\u241F",
+ "/cntr:verticaltab": "\u240B",
+ "/coarmenian": "\u0581",
+ "/cocktailGlass": "\u1F378",
+ "/coffin": "\u26B0",
+ "/cofullwidth": "\u33C7",
+ "/collision": "\u1F4A5",
+ "/colon": "\u003A",
+ "/colonequals": "\u2254",
+ "/colonmod": "\uA789",
+ "/colonmonetary": "\u20A1",
+ "/colonmonospace": "\uFF1A",
+ "/colonraisedmod": "\u02F8",
+ "/colonsign": "\u20A1",
+ "/colonsmall": "\uFE55",
+ "/colontriangularhalfmod": "\u02D1",
+ "/colontriangularmod": "\u02D0",
+ "/comet": "\u2604",
+ "/comma": "\u002C",
+ "/commaabovecmb": "\u0313",
+ "/commaaboverightcmb": "\u0315",
+ "/commaaccent": "\uF6C3",
+ "/commaarabic": "\u060C",
+ "/commaarmenian": "\u055D",
+ "/commabarfunc": "\u236A",
+ "/commainferior": "\uF6E1",
+ "/commamonospace": "\uFF0C",
+ "/commaraised": "\u2E34",
+ "/commareversed": "\u2E41",
+ "/commareversedabovecmb": "\u0314",
+ "/commareversedmod": "\u02BD",
+ "/commasmall": "\uFE50",
+ "/commasuperior": "\uF6E2",
+ "/commaturnedabovecmb": "\u0312",
+ "/commaturnedmod": "\u02BB",
+ "/commercialat": "\uFE6B",
+ "/commercialminussign": "\u2052",
+ "/compass": "\u263C",
+ "/complement": "\u2201",
+ "/composition": "\u2384",
+ "/compression": "\u1F5DC",
+ "/con": "\uA76F",
+ "/confettiBall": "\u1F38A",
+ "/confoundedFace": "\u1F616",
+ "/confusedFace": "\u1F615",
+ "/congratulationideographiccircled": "\u3297",
+ "/congratulationideographicparen": "\u3237",
+ "/congruent": "\u2245",
+ "/conicaltaper": "\u2332",
+ "/conjunction": "\u260C",
+ "/consquareupblack": "\u26FE",
+ "/constructionSign": "\u1F6A7",
+ "/constructionWorker": "\u1F477",
+ "/containsasmembersmall": "\u220D",
+ "/containsasnormalsubgroorequalup": "\u22B5",
+ "/containsasnormalsubgroup": "\u22B3",
+ "/containslonghorizontalstroke": "\u22FA",
+ "/containsoverbar": "\u22FD",
+ "/containsoverbarsmall": "\u22FE",
+ "/containssmallverticalbarhorizontalstroke": "\u22FC",
+ "/containsverticalbarhorizontalstroke": "\u22FB",
+ "/continuousunderline": "\u2381",
+ "/contourintegral": "\u222E",
+ "/control": "\u2303",
+ "/controlACK": "\u0006",
+ "/controlBEL": "\u0007",
+ "/controlBS": "\u0008",
+ "/controlCAN": "\u0018",
+ "/controlCR": "\u000D",
+ "/controlDC1": "\u0011",
+ "/controlDC2": "\u0012",
+ "/controlDC3": "\u0013",
+ "/controlDC4": "\u0014",
+ "/controlDEL": "\u007F",
+ "/controlDLE": "\u0010",
+ "/controlEM": "\u0019",
+ "/controlENQ": "\u0005",
+ "/controlEOT": "\u0004",
+ "/controlESC": "\u001B",
+ "/controlETB": "\u0017",
+ "/controlETX": "\u0003",
+ "/controlFF": "\u000C",
+ "/controlFS": "\u001C",
+ "/controlGS": "\u001D",
+ "/controlHT": "\u0009",
+ "/controlKnobs": "\u1F39B",
+ "/controlLF": "\u000A",
+ "/controlNAK": "\u0015",
+ "/controlRS": "\u001E",
+ "/controlSI": "\u000F",
+ "/controlSO": "\u000E",
+ "/controlSOT": "\u0002",
+ "/controlSTX": "\u0001",
+ "/controlSUB": "\u001A",
+ "/controlSYN": "\u0016",
+ "/controlUS": "\u001F",
+ "/controlVT": "\u000B",
+ "/convavediamondwhite": "\u27E1",
+ "/convenienceStore": "\u1F3EA",
+ "/cookedRice": "\u1F35A",
+ "/cookie": "\u1F36A",
+ "/cooking": "\u1F373",
+ "/coolsquare": "\u1F192",
+ "/coproductarray": "\u2210",
+ "/copyideographiccircled": "\u32A2",
+ "/copyright": "\u00A9",
+ "/copyrightsans": "\uF8E9",
+ "/copyrightserif": "\uF6D9",
+ "/cornerbottomleft": "\u231E",
+ "/cornerbottomright": "\u231F",
+ "/cornerbracketleft": "\u300C",
+ "/cornerbracketlefthalfwidth": "\uFF62",
+ "/cornerbracketleftvertical": "\uFE41",
+ "/cornerbracketright": "\u300D",
+ "/cornerbracketrighthalfwidth": "\uFF63",
+ "/cornerbracketrightvertical": "\uFE42",
+ "/cornerdotupleft": "\u27D4",
+ "/cornertopleft": "\u231C",
+ "/cornertopright": "\u231D",
+ "/coroniseditorial": "\u2E0E",
+ "/corporationsquare": "\u337F",
+ "/correctideographiccircled": "\u32A3",
+ "/corresponds": "\u2258",
+ "/cosquare": "\u33C7",
+ "/couchAndLamp": "\u1F6CB",
+ "/counterbore": "\u2334",
+ "/countersink": "\u2335",
+ "/coupleHeart": "\u1F491",
+ "/coverkgfullwidth": "\u33C6",
+ "/coverkgsquare": "\u33C6",
+ "/cow": "\u1F404",
+ "/cowFace": "\u1F42E",
+ "/cpalatalhook": "\uA794",
+ "/cparen": "\u249E",
+ "/cparenthesized": "\u249E",
+ "/creditCard": "\u1F4B3",
+ "/crescentMoon": "\u1F319",
+ "/creversed": "\u2184",
+ "/cricketBatAndBall": "\u1F3CF",
+ "/crocodile": "\u1F40A",
+ "/cropbottomleft": "\u230D",
+ "/cropbottomright": "\u230C",
+ "/croptopleft": "\u230F",
+ "/croptopright": "\u230E",
+ "/crossPommee": "\u1F542",
+ "/crossPommeeHalf-circleBelow": "\u1F541",
+ "/crossedFlags": "\u1F38C",
+ "/crossedswords": "\u2694",
+ "/crossinglanes": "\u26CC",
+ "/crossmod": "\u02DF",
+ "/crossofjerusalem": "\u2629",
+ "/crossoflorraine": "\u2628",
+ "/crossonshieldblack": "\u26E8",
+ "/crown": "\u1F451",
+ "/crrn:rupee": "\u20A8",
+ "/cruzeiro": "\u20A2",
+ "/cryingCatFace": "\u1F63F",
+ "/cryingFace": "\u1F622",
+ "/crystalBall": "\u1F52E",
+ "/cstretched": "\u0297",
+ "/cstroke": "\u023C",
+ "/cuatrillo": "\uA72D",
+ "/cuatrillocomma": "\uA72F",
+ "/curlyand": "\u22CF",
+ "/curlylogicaland": "\u22CF",
+ "/curlylogicalor": "\u22CE",
+ "/curlyor": "\u22CE",
+ "/currency": "\u00A4",
+ "/currencyExchange": "\u1F4B1",
+ "/curryAndRice": "\u1F35B",
+ "/custard": "\u1F36E",
+ "/customeraccountnumber": "\u2449",
+ "/customs": "\u1F6C3",
+ "/cyclone": "\u1F300",
+ "/cylindricity": "\u232D",
+ "/cyrBreve": "\uF6D1",
+ "/cyrFlex": "\uF6D2",
+ "/cyrbreve": "\uF6D4",
+ "/cyrflex": "\uF6D5",
+ "/d": "\u0064",
+ "/daarmenian": "\u0564",
+ "/daasusquare": "\u3324",
+ "/dabengali": "\u09A6",
+ "/dad": "\u0636",
+ "/dad.fina": "\uFEBE",
+ "/dad.init": "\uFEBF",
+ "/dad.init_alefmaksura.fina": "\uFD07",
+ "/dad.init_hah.fina": "\uFC23",
+ "/dad.init_hah.medi": "\uFCB5",
+ "/dad.init_jeem.fina": "\uFC22",
+ "/dad.init_jeem.medi": "\uFCB4",
+ "/dad.init_khah.fina": "\uFC24",
+ "/dad.init_khah.medi": "\uFCB6",
+ "/dad.init_khah.medi_meem.medi": "\uFD70",
+ "/dad.init_meem.fina": "\uFC25",
+ "/dad.init_meem.medi": "\uFCB7",
+ "/dad.init_reh.fina": "\uFD10",
+ "/dad.init_yeh.fina": "\uFD08",
+ "/dad.isol": "\uFEBD",
+ "/dad.medi": "\uFEC0",
+ "/dad.medi_alefmaksura.fina": "\uFD23",
+ "/dad.medi_hah.medi_alefmaksura.fina": "\uFD6E",
+ "/dad.medi_hah.medi_yeh.fina": "\uFDAB",
+ "/dad.medi_khah.medi_meem.fina": "\uFD6F",
+ "/dad.medi_reh.fina": "\uFD2C",
+ "/dad.medi_yeh.fina": "\uFD24",
+ "/dadarabic": "\u0636",
+ "/daddotbelow": "\u06FB",
+ "/dadeva": "\u0926",
+ "/dadfinalarabic": "\uFEBE",
+ "/dadinitialarabic": "\uFEBF",
+ "/dadmedialarabic": "\uFEC0",
+ "/dafullwidth": "\u3372",
+ "/dagesh": "\u05BC",
+ "/dagesh:hb": "\u05BC",
+ "/dageshhebrew": "\u05BC",
+ "/dagger": "\u2020",
+ "/daggerKnife": "\u1F5E1",
+ "/daggerdbl": "\u2021",
+ "/daggerwithguardleft": "\u2E36",
+ "/daggerwithguardright": "\u2E37",
+ "/dagujarati": "\u0AA6",
+ "/dagurmukhi": "\u0A26",
+ "/dahal": "\u068C",
+ "/dahal.fina": "\uFB85",
+ "/dahal.isol": "\uFB84",
+ "/dahiragana": "\u3060",
+ "/dakatakana": "\u30C0",
+ "/dal": "\u062F",
+ "/dal.fina": "\uFEAA",
+ "/dal.isol": "\uFEA9",
+ "/dalInvertedSmallVBelow": "\u075A",
+ "/dalTwoDotsVerticallyBelowSmallTah": "\u0759",
+ "/dalarabic": "\u062F",
+ "/daldotbelow": "\u068A",
+ "/daldotbelowtahsmall": "\u068B",
+ "/daldownthreedotsabove": "\u068F",
+ "/dalet": "\u05D3",
+ "/dalet:hb": "\u05D3",
+ "/daletdagesh": "\uFB33",
+ "/daletdageshhebrew": "\uFB33",
+ "/dalethatafpatah": "\u05D3",
+ "/dalethatafpatahhebrew": "\u05D3",
+ "/dalethatafsegol": "\u05D3",
+ "/dalethatafsegolhebrew": "\u05D3",
+ "/dalethebrew": "\u05D3",
+ "/dalethiriq": "\u05D3",
+ "/dalethiriqhebrew": "\u05D3",
+ "/daletholam": "\u05D3",
+ "/daletholamhebrew": "\u05D3",
+ "/daletpatah": "\u05D3",
+ "/daletpatahhebrew": "\u05D3",
+ "/daletqamats": "\u05D3",
+ "/daletqamatshebrew": "\u05D3",
+ "/daletqubuts": "\u05D3",
+ "/daletqubutshebrew": "\u05D3",
+ "/daletsegol": "\u05D3",
+ "/daletsegolhebrew": "\u05D3",
+ "/daletsheva": "\u05D3",
+ "/daletshevahebrew": "\u05D3",
+ "/dalettsere": "\u05D3",
+ "/dalettserehebrew": "\u05D3",
+ "/daletwide:hb": "\uFB22",
+ "/daletwithdagesh:hb": "\uFB33",
+ "/dalfinalarabic": "\uFEAA",
+ "/dalfourdotsabove": "\u0690",
+ "/dalinvertedV": "\u06EE",
+ "/dalring": "\u0689",
+ "/damahaprana": "\uA9A3",
+ "/damma": "\u064F",
+ "/dammaIsol": "\uFE78",
+ "/dammaMedi": "\uFE79",
+ "/dammaarabic": "\u064F",
+ "/dammalowarabic": "\u064F",
+ "/dammareversed": "\u065D",
+ "/dammasmall": "\u0619",
+ "/dammatan": "\u064C",
+ "/dammatanIsol": "\uFE72",
+ "/dammatanaltonearabic": "\u064C",
+ "/dammatanarabic": "\u064C",
+ "/dancer": "\u1F483",
+ "/danda": "\u0964",
+ "/dango": "\u1F361",
+ "/darga:hb": "\u05A7",
+ "/dargahebrew": "\u05A7",
+ "/dargalefthebrew": "\u05A7",
+ "/darkShade": "\u2593",
+ "/darkSunglasses": "\u1F576",
+ "/dashwithupturnleft": "\u2E43",
+ "/dasiacmbcyr": "\u0485",
+ "/dasiapneumatacyrilliccmb": "\u0485",
+ "/dateseparator": "\u060D",
+ "/dayeighteentelegraph": "\u33F1",
+ "/dayeighttelegraph": "\u33E7",
+ "/dayeleventelegraph": "\u33EA",
+ "/dayfifteentelegraph": "\u33EE",
+ "/dayfivetelegraph": "\u33E4",
+ "/dayfourteentelegraph": "\u33ED",
+ "/dayfourtelegraph": "\u33E3",
+ "/daynineteentelegraph": "\u33F2",
+ "/dayninetelegraph": "\u33E8",
+ "/dayonetelegraph": "\u33E0",
+ "/dayseventeentelegraph": "\u33F0",
+ "/dayseventelegraph": "\u33E6",
+ "/daysixteentelegraph": "\u33EF",
+ "/daysixtelegraph": "\u33E5",
+ "/daytentelegraph": "\u33E9",
+ "/daythirteentelegraph": "\u33EC",
+ "/daythirtyonetelegraph": "\u33FE",
+ "/daythirtytelegraph": "\u33FD",
+ "/daythreetelegraph": "\u33E2",
+ "/daytwelvetelegraph": "\u33EB",
+ "/daytwentyeighttelegraph": "\u33FB",
+ "/daytwentyfivetelegraph": "\u33F8",
+ "/daytwentyfourtelegraph": "\u33F7",
+ "/daytwentyninetelegraph": "\u33FC",
+ "/daytwentyonetelegraph": "\u33F4",
+ "/daytwentyseventelegraph": "\u33FA",
+ "/daytwentysixtelegraph": "\u33F9",
+ "/daytwentytelegraph": "\u33F3",
+ "/daytwentythreetelegraph": "\u33F6",
+ "/daytwentytwotelegraph": "\u33F5",
+ "/daytwotelegraph": "\u33E1",
+ "/dbdigraph": "\u0238",
+ "/dbfullwidth": "\u33C8",
+ "/dblGrave": "\uF6D3",
+ "/dblanglebracketleft": "\u300A",
+ "/dblanglebracketleftvertical": "\uFE3D",
+ "/dblanglebracketright": "\u300B",
+ "/dblanglebracketrightvertical": "\uFE3E",
+ "/dblarchinvertedbelowcmb": "\u032B",
+ "/dblarrowNE": "\u21D7",
+ "/dblarrowNW": "\u21D6",
+ "/dblarrowSE": "\u21D8",
+ "/dblarrowSW": "\u21D9",
+ "/dblarrowdown": "\u21D3",
+ "/dblarrowleft": "\u21D4",
+ "/dblarrowleftright": "\u21D4",
+ "/dblarrowleftrightstroke": "\u21CE",
+ "/dblarrowleftstroke": "\u21CD",
+ "/dblarrowright": "\u21D2",
+ "/dblarrowrightstroke": "\u21CF",
+ "/dblarrowup": "\u21D1",
+ "/dblarrowupdown": "\u21D5",
+ "/dbldanda": "\u0965",
+ "/dbldnhorz": "\u2566",
+ "/dbldnleft": "\u2557",
+ "/dbldnright": "\u2554",
+ "/dblgrave": "\uF6D6",
+ "/dblgravecmb": "\u030F",
+ "/dblhorz": "\u2550",
+ "/dblintegral": "\u222C",
+ "/dbllowline": "\u2017",
+ "/dbllowlinecmb": "\u0333",
+ "/dbloverlinecmb": "\u033F",
+ "/dblprimemod": "\u02BA",
+ "/dblstrokearrowdown": "\u21DF",
+ "/dblstrokearrowup": "\u21DE",
+ "/dbluphorz": "\u2569",
+ "/dblupleft": "\u255D",
+ "/dblupright": "\u255A",
+ "/dblvert": "\u2551",
+ "/dblverthorz": "\u256C",
+ "/dblverticalbar": "\u2016",
+ "/dblverticallineabovecmb": "\u030E",
+ "/dblvertleft": "\u2563",
+ "/dblvertright": "\u2560",
+ "/dbopomofo": "\u3109",
+ "/dbsquare": "\u33C8",
+ "/dcaron": "\u010F",
+ "/dcedilla": "\u1E11",
+ "/dchecyr": "\u052D",
+ "/dcircle": "\u24D3",
+ "/dcircumflexbelow": "\u1E13",
+ "/dcroat": "\u0111",
+ "/dcurl": "\u0221",
+ "/ddabengali": "\u09A1",
+ "/ddadeva": "\u0921",
+ "/ddagujarati": "\u0AA1",
+ "/ddagurmukhi": "\u0A21",
+ "/ddahal": "\u068D",
+ "/ddahal.fina": "\uFB83",
+ "/ddahal.isol": "\uFB82",
+ "/ddal": "\u0688",
+ "/ddal.fina": "\uFB89",
+ "/ddal.isol": "\uFB88",
+ "/ddalarabic": "\u0688",
+ "/ddalfinalarabic": "\uFB89",
+ "/ddamahaprana": "\uA99E",
+ "/ddblstruckitalic": "\u2146",
+ "/dddhadeva": "\u095C",
+ "/ddhabengali": "\u09A2",
+ "/ddhadeva": "\u0922",
+ "/ddhagujarati": "\u0AA2",
+ "/ddhagurmukhi": "\u0A22",
+ "/ddot": "\u1E0B",
+ "/ddotaccent": "\u1E0B",
+ "/ddotbelow": "\u1E0D",
+ "/decembertelegraph": "\u32CB",
+ "/deciduousTree": "\u1F333",
+ "/decimalexponent": "\u23E8",
+ "/decimalseparatorarabic": "\u066B",
+ "/decimalseparatorpersian": "\u066B",
+ "/decreaseFontSize": "\u1F5DB",
+ "/decyr": "\u0434",
+ "/decyrillic": "\u0434",
+ "/degree": "\u00B0",
+ "/degreecelsius": "\u2103",
+ "/degreefahrenheit": "\u2109",
+ "/dehi:hb": "\u05AD",
+ "/dehihebrew": "\u05AD",
+ "/dehiragana": "\u3067",
+ "/deicoptic": "\u03EF",
+ "/dekatakana": "\u30C7",
+ "/dekomicyr": "\u0501",
+ "/deldiaeresisfunc": "\u2362",
+ "/deleteleft": "\u232B",
+ "/deleteright": "\u2326",
+ "/deliveryTruck": "\u1F69A",
+ "/delstilefunc": "\u2352",
+ "/delta": "\u03B4",
+ "/deltaequal": "\u225C",
+ "/deltastilefunc": "\u234B",
+ "/deltaturned": "\u018D",
+ "/deltaunderlinefunc": "\u2359",
+ "/deltildefunc": "\u236B",
+ "/denominatorminusonenumeratorbengali": "\u09F8",
+ "/dentistrybottomverticalleft": "\u23CC",
+ "/dentistrybottomverticalright": "\u23BF",
+ "/dentistrycircledownhorizontal": "\u23C1",
+ "/dentistrycircleuphorizontal": "\u23C2",
+ "/dentistrycirclevertical": "\u23C0",
+ "/dentistrydownhorizontal": "\u23C9",
+ "/dentistrytopverticalleft": "\u23CB",
+ "/dentistrytopverticalright": "\u23BE",
+ "/dentistrytriangledownhorizontal": "\u23C4",
+ "/dentistrytriangleuphorizontal": "\u23C5",
+ "/dentistrytrianglevertical": "\u23C3",
+ "/dentistryuphorizontal": "\u23CA",
+ "/dentistrywavedownhorizontal": "\u23C7",
+ "/dentistrywaveuphorizontal": "\u23C8",
+ "/dentistrywavevertical": "\u23C6",
+ "/departmentStore": "\u1F3EC",
+ "/derelictHouseBuilding": "\u1F3DA",
+ "/desert": "\u1F3DC",
+ "/desertIsland": "\u1F3DD",
+ "/desisquare": "\u3325",
+ "/desktopComputer": "\u1F5A5",
+ "/desktopWindow": "\u1F5D4",
+ "/deva:a": "\u0905",
+ "/deva:aa": "\u0906",
+ "/deva:aasign": "\u093E",
+ "/deva:abbreviation": "\u0970",
+ "/deva:acandra": "\u0972",
+ "/deva:acute": "\u0954",
+ "/deva:ai": "\u0910",
+ "/deva:aisign": "\u0948",
+ "/deva:anudatta": "\u0952",
+ "/deva:anusvara": "\u0902",
+ "/deva:ashort": "\u0904",
+ "/deva:au": "\u0914",
+ "/deva:ausign": "\u094C",
+ "/deva:avagraha": "\u093D",
+ "/deva:aw": "\u0975",
+ "/deva:awsign": "\u094F",
+ "/deva:ba": "\u092C",
+ "/deva:bba": "\u097F",
+ "/deva:bha": "\u092D",
+ "/deva:ca": "\u091A",
+ "/deva:candrabindu": "\u0901",
+ "/deva:candrabinduinverted": "\u0900",
+ "/deva:cha": "\u091B",
+ "/deva:da": "\u0926",
+ "/deva:danda": "\u0964",
+ "/deva:dbldanda": "\u0965",
+ "/deva:dda": "\u0921",
+ "/deva:ddda": "\u097E",
+ "/deva:dddha": "\u095C",
+ "/deva:ddha": "\u0922",
+ "/deva:dha": "\u0927",
+ "/deva:dothigh": "\u0971",
+ "/deva:e": "\u090F",
+ "/deva:ecandra": "\u090D",
+ "/deva:eight": "\u096E",
+ "/deva:eshort": "\u090E",
+ "/deva:esign": "\u0947",
+ "/deva:esigncandra": "\u0945",
+ "/deva:esignprishthamatra": "\u094E",
+ "/deva:esignshort": "\u0946",
+ "/deva:fa": "\u095E",
+ "/deva:five": "\u096B",
+ "/deva:four": "\u096A",
+ "/deva:ga": "\u0917",
+ "/deva:gga": "\u097B",
+ "/deva:gha": "\u0918",
+ "/deva:ghha": "\u095A",
+ "/deva:glottalstop": "\u097D",
+ "/deva:grave": "\u0953",
+ "/deva:ha": "\u0939",
+ "/deva:i": "\u0907",
+ "/deva:ii": "\u0908",
+ "/deva:iisign": "\u0940",
+ "/deva:isign": "\u093F",
+ "/deva:ja": "\u091C",
+ "/deva:jha": "\u091D",
+ "/deva:jja": "\u097C",
+ "/deva:ka": "\u0915",
+ "/deva:kha": "\u0916",
+ "/deva:khha": "\u0959",
+ "/deva:la": "\u0932",
+ "/deva:lla": "\u0933",
+ "/deva:llla": "\u0934",
+ "/deva:llvocal": "\u0961",
+ "/deva:llvocalsign": "\u0963",
+ "/deva:lvocal": "\u090C",
+ "/deva:lvocalsign": "\u0962",
+ "/deva:ma": "\u092E",
+ "/deva:marwaridda": "\u0978",
+ "/deva:na": "\u0928",
+ "/deva:nga": "\u0919",
+ "/deva:nine": "\u096F",
+ "/deva:nna": "\u0923",
+ "/deva:nnna": "\u0929",
+ "/deva:nukta": "\u093C",
+ "/deva:nya": "\u091E",
+ "/deva:o": "\u0913",
+ "/deva:ocandra": "\u0911",
+ "/deva:oe": "\u0973",
+ "/deva:oesign": "\u093A",
+ "/deva:om": "\u0950",
+ "/deva:one": "\u0967",
+ "/deva:ooe": "\u0974",
+ "/deva:ooesign": "\u093B",
+ "/deva:oshort": "\u0912",
+ "/deva:osign": "\u094B",
+ "/deva:osigncandra": "\u0949",
+ "/deva:osignshort": "\u094A",
+ "/deva:pa": "\u092A",
+ "/deva:pha": "\u092B",
+ "/deva:qa": "\u0958",
+ "/deva:ra": "\u0930",
+ "/deva:rha": "\u095D",
+ "/deva:rra": "\u0931",
+ "/deva:rrvocal": "\u0960",
+ "/deva:rrvocalsign": "\u0944",
+ "/deva:rvocal": "\u090B",
+ "/deva:rvocalsign": "\u0943",
+ "/deva:sa": "\u0938",
+ "/deva:seven": "\u096D",
+ "/deva:sha": "\u0936",
+ "/deva:signelongcandra": "\u0955",
+ "/deva:six": "\u096C",
+ "/deva:ssa": "\u0937",
+ "/deva:ta": "\u0924",
+ "/deva:tha": "\u0925",
+ "/deva:three": "\u0969",
+ "/deva:tta": "\u091F",
+ "/deva:ttha": "\u0920",
+ "/deva:two": "\u0968",
+ "/deva:u": "\u0909",
+ "/deva:udatta": "\u0951",
+ "/deva:ue": "\u0976",
+ "/deva:uesign": "\u0956",
+ "/deva:usign": "\u0941",
+ "/deva:uu": "\u090A",
+ "/deva:uue": "\u0977",
+ "/deva:uuesign": "\u0957",
+ "/deva:uusign": "\u0942",
+ "/deva:va": "\u0935",
+ "/deva:virama": "\u094D",
+ "/deva:visarga": "\u0903",
+ "/deva:ya": "\u092F",
+ "/deva:yaheavy": "\u097A",
+ "/deva:yya": "\u095F",
+ "/deva:za": "\u095B",
+ "/deva:zero": "\u0966",
+ "/deva:zha": "\u0979",
+ "/dezh": "\u02A4",
+ "/dfemaledbl": "\u26A2",
+ "/dhabengali": "\u09A7",
+ "/dhadeva": "\u0927",
+ "/dhagujarati": "\u0AA7",
+ "/dhagurmukhi": "\u0A27",
+ "/dhook": "\u0257",
+ "/diaeresisgreaterfunc": "\u2369",
+ "/dialytikatonos": "\u0385",
+ "/dialytikatonoscmb": "\u0344",
+ "/diametersign": "\u2300",
+ "/diamond": "\u2666",
+ "/diamondShapeADotInside": "\u1F4A0",
+ "/diamondinsquarewhite": "\u26CB",
+ "/diamondoperator": "\u22C4",
+ "/diamondsuitwhite": "\u2662",
+ "/diamondunderlinefunc": "\u235A",
+ "/diamondwhitewithdiamondsmallblack": "\u25C8",
+ "/diefive": "\u2684",
+ "/diefour": "\u2683",
+ "/dieone": "\u2680",
+ "/dieresis": "\u00A8",
+ "/dieresisacute": "\uF6D7",
+ "/dieresisbelowcmb": "\u0324",
+ "/dieresiscmb": "\u0308",
+ "/dieresisgrave": "\uF6D8",
+ "/dieresistilde": "\u1FC1",
+ "/dieresistonos": "\u0385",
+ "/dieselLocomotive": "\u1F6F2",
+ "/diesix": "\u2685",
+ "/diethree": "\u2682",
+ "/dietwo": "\u2681",
+ "/differencebetween": "\u224F",
+ "/digamma": "\u03DD",
+ "/digammapamphylian": "\u0377",
+ "/digramgreateryang": "\u268C",
+ "/digramgreateryin": "\u268F",
+ "/digramlesseryang": "\u268E",
+ "/digramlesseryin": "\u268D",
+ "/dihiragana": "\u3062",
+ "/dikatakana": "\u30C2",
+ "/dimensionorigin": "\u2331",
+ "/dingbatSAns-serifzerocircle": "\u1F10B",
+ "/dingbatSAns-serifzerocircleblack": "\u1F10C",
+ "/dinsular": "\uA77A",
+ "/directHit": "\u1F3AF",
+ "/directcurrentformtwo": "\u2393",
+ "/dirgamurevowel": "\uA9BB",
+ "/disabledcar": "\u26CD",
+ "/disappointedButRelievedFace": "\u1F625",
+ "/disappointedFace": "\u1F61E",
+ "/discontinuousunderline": "\u2382",
+ "/dittomark": "\u3003",
+ "/divide": "\u00F7",
+ "/divides": "\u2223",
+ "/divisionslash": "\u2215",
+ "/divisiontimes": "\u22C7",
+ "/divorce": "\u26AE",
+ "/dizzy": "\u1F4AB",
+ "/dizzyFace": "\u1F635",
+ "/djecyr": "\u0452",
+ "/djecyrillic": "\u0452",
+ "/djekomicyr": "\u0503",
+ "/dkshade": "\u2593",
+ "/dlfullwidth": "\u3397",
+ "/dlinebelow": "\u1E0F",
+ "/dlogicalorsquare": "\u27CF",
+ "/dlogicalsquare": "\u27CE",
+ "/dlsquare": "\u3397",
+ "/dm2fullwidth": "\u3378",
+ "/dm3fullwidth": "\u3379",
+ "/dmacron": "\u0111",
+ "/dmaledbl": "\u26A3",
+ "/dmfullwidth": "\u3377",
+ "/dmonospace": "\uFF44",
+ "/dnblock": "\u2584",
+ "/dndblhorzsng": "\u2565",
+ "/dndblleftsng": "\u2556",
+ "/dndblrightsng": "\u2553",
+ "/dngb:airplane": "\u2708",
+ "/dngb:arrowfeatheredblackNE": "\u27B6",
+ "/dngb:arrowfeatheredblackSE": "\u27B4",
+ "/dngb:arrowfeatheredblackheavyNE": "\u27B9",
+ "/dngb:arrowfeatheredblackheavySE": "\u27B7",
+ "/dngb:arrowheadrightblack": "\u27A4",
+ "/dngb:arrowheadrightthreeDbottomlight": "\u27A3",
+ "/dngb:arrowheadrightthreeDtoplight": "\u27A2",
+ "/dngb:arrowheavyNE": "\u279A",
+ "/dngb:arrowheavySE": "\u2798",
+ "/dngb:arrowrightbacktiltedshadowedwhite": "\u27AB",
+ "/dngb:arrowrightblack": "\u27A1",
+ "/dngb:arrowrightcircledwhiteheavy": "\u27B2",
+ "/dngb:arrowrightcurvedownblackheavy": "\u27A5",
+ "/dngb:arrowrightcurveupblackheavy": "\u27A6",
+ "/dngb:arrowrightfeatheredblack": "\u27B5",
+ "/dngb:arrowrightfeatheredblackheavy": "\u27B8",
+ "/dngb:arrowrightfeatheredwhite": "\u27B3",
+ "/dngb:arrowrightfronttiltedshadowedwhite": "\u27AC",
+ "/dngb:arrowrightheavy": "\u2799",
+ "/dngb:arrowrightleftshadedwhite": "\u27AA",
+ "/dngb:arrowrightoutlinedopen": "\u27BE",
+ "/dngb:arrowrightpointed": "\u279B",
+ "/dngb:arrowrightpointedblackheavy": "\u27A8",
+ "/dngb:arrowrightrightshadedwhite": "\u27A9",
+ "/dngb:arrowrightroundheavy": "\u279C",
+ "/dngb:arrowrightsquatblack": "\u27A7",
+ "/dngb:arrowrighttriangle": "\u279D",
+ "/dngb:arrowrighttriangledashed": "\u279F",
+ "/dngb:arrowrighttriangledashedheavy": "\u27A0",
+ "/dngb:arrowrighttriangleheavy": "\u279E",
+ "/dngb:arrowrightwedge": "\u27BC",
+ "/dngb:arrowrightwedgeheavy": "\u27BD",
+ "/dngb:arrowrightwideheavy": "\u2794",
+ "/dngb:arrowshadowrightlowerwhiteheavy": "\u27AD",
+ "/dngb:arrowshadowrightnotchedlowerwhite": "\u27AF",
+ "/dngb:arrowshadowrightnotchedupperwhite": "\u27B1",
+ "/dngb:arrowshadowrightupperwhiteheavy": "\u27AE",
+ "/dngb:arrowteardropright": "\u27BA",
+ "/dngb:arrowteardroprightheavy": "\u27BB",
+ "/dngb:asteriskballoon": "\u2749",
+ "/dngb:asteriskballoonfour": "\u2723",
+ "/dngb:asteriskballoonheavyfour": "\u2724",
+ "/dngb:asteriskcentreopen": "\u2732",
+ "/dngb:asteriskclubfour": "\u2725",
+ "/dngb:asteriskheavy": "\u2731",
+ "/dngb:asteriskpointedsixteen": "\u273A",
+ "/dngb:asteriskteardrop": "\u273B",
+ "/dngb:asteriskteardropcentreopen": "\u273C",
+ "/dngb:asteriskteardropfour": "\u2722",
+ "/dngb:asteriskteardropheavy": "\u273D",
+ "/dngb:asteriskteardroppinwheelheavy": "\u2743",
+ "/dngb:asteriskteardroppropellereight": "\u274A",
+ "/dngb:asteriskteardroppropellerheavyeight": "\u274B",
+ "/dngb:ballotx": "\u2717",
+ "/dngb:ballotxheavy": "\u2718",
+ "/dngb:bracketleftpointedangleheavyornament": "\u2770",
+ "/dngb:bracketleftpointedanglemediumornament": "\u276C",
+ "/dngb:bracketrightpointedangleheavyornament": "\u2771",
+ "/dngb:bracketrightpointedanglemediumornament": "\u276D",
+ "/dngb:bracketshellleftlightornament": "\u2772",
+ "/dngb:bracketshellrightlightornament": "\u2773",
+ "/dngb:check": "\u2713",
+ "/dngb:checkheavy": "\u2714",
+ "/dngb:checkwhiteheavy": "\u2705",
+ "/dngb:chevronsnowflakeheavy": "\u2746",
+ "/dngb:circleshadowedwhite": "\u274D",
+ "/dngb:commaheavydoubleornament": "\u275E",
+ "/dngb:commaheavydoubleturnedornament": "\u275D",
+ "/dngb:commaheavyornament": "\u275C",
+ "/dngb:commaheavyturnedornament": "\u275B",
+ "/dngb:compasstarpointedblackeight": "\u2737",
+ "/dngb:compasstarpointedblackheavyeight": "\u2738",
+ "/dngb:cross": "\u274C",
+ "/dngb:crosscentreopen": "\u271B",
+ "/dngb:crosscentreopenheavy": "\u271C",
+ "/dngb:curlybracketleftmediumornament": "\u2774",
+ "/dngb:curlybracketrightmediumornament": "\u2775",
+ "/dngb:curlyloop": "\u27B0",
+ "/dngb:curlyloopdouble": "\u27BF",
+ "/dngb:curvedstemparagraphsignornament": "\u2761",
+ "/dngb:diamondminusxblackwhite": "\u2756",
+ "/dngb:divisionsignheavy": "\u2797",
+ "/dngb:eightnegativecircled": "\u277D",
+ "/dngb:eightsanscircled": "\u2787",
+ "/dngb:eightsansnegativecircled": "\u2791",
+ "/dngb:envelope": "\u2709",
+ "/dngb:exclamationheavy": "\u2757",
+ "/dngb:exclamationheavyornament": "\u2762",
+ "/dngb:exclamationwhiteornament": "\u2755",
+ "/dngb:fivenegativecircled": "\u277A",
+ "/dngb:fivesanscircled": "\u2784",
+ "/dngb:fivesansnegativecircled": "\u278E",
+ "/dngb:floralheart": "\u2766",
+ "/dngb:floralheartbulletrotated": "\u2767",
+ "/dngb:floretteblack": "\u273F",
+ "/dngb:floretteoutlinedpetalledblackeight": "\u2741",
+ "/dngb:florettepetalledblackwhitesix": "\u273E",
+ "/dngb:florettewhite": "\u2740",
+ "/dngb:fournegativecircled": "\u2779",
+ "/dngb:foursanscircled": "\u2783",
+ "/dngb:foursansnegativecircled": "\u278D",
+ "/dngb:greekcrossheavy": "\u271A",
+ "/dngb:greekcrossoutlined": "\u2719",
+ "/dngb:heartblackheavy": "\u2764",
+ "/dngb:heartbulletrotatedblackheavy": "\u2765",
+ "/dngb:heartexclamationheavyornament": "\u2763",
+ "/dngb:hvictory": "\u270C",
+ "/dngb:hwriting": "\u270D",
+ "/dngb:latincross": "\u271D",
+ "/dngb:latincrossoutlined": "\u271F",
+ "/dngb:latincrossshadowedwhite": "\u271E",
+ "/dngb:lowcommaheavydoubleornament": "\u2760",
+ "/dngb:lowcommaheavyornament": "\u275F",
+ "/dngb:maltesecross": "\u2720",
+ "/dngb:minussignheavy": "\u2796",
+ "/dngb:multiplicationx": "\u2715",
+ "/dngb:multiplicationxheavy": "\u2716",
+ "/dngb:nibblack": "\u2712",
+ "/dngb:nibwhite": "\u2711",
+ "/dngb:ninenegativecircled": "\u277E",
+ "/dngb:ninesanscircled": "\u2788",
+ "/dngb:ninesansnegativecircled": "\u2792",
+ "/dngb:onenegativecircled": "\u2776",
+ "/dngb:onesanscircled": "\u2780",
+ "/dngb:onesansnegativecircled": "\u278A",
+ "/dngb:parenthesisleftflattenedmediumornament": "\u276A",
+ "/dngb:parenthesisleftmediumornament": "\u2768",
+ "/dngb:parenthesisrightflattenedmediumornament": "\u276B",
+ "/dngb:parenthesisrightmediumornament": "\u2769",
+ "/dngb:pencil": "\u270F",
+ "/dngb:pencillowerright": "\u270E",
+ "/dngb:pencilupperright": "\u2710",
+ "/dngb:plussignheavy": "\u2795",
+ "/dngb:questionblackornament": "\u2753",
+ "/dngb:questionwhiteornament": "\u2754",
+ "/dngb:quotationleftpointedangleheavyornament": "\u276E",
+ "/dngb:quotationrightpointedangleheavyornament": "\u276F",
+ "/dngb:raisedfist": "\u270A",
+ "/dngb:raisedh": "\u270B",
+ "/dngb:safetyscissorsblack": "\u2700",
+ "/dngb:scissorsblack": "\u2702",
+ "/dngb:scissorslowerblade": "\u2703",
+ "/dngb:scissorsupperblade": "\u2701",
+ "/dngb:scissorswhite": "\u2704",
+ "/dngb:sevennegativecircled": "\u277C",
+ "/dngb:sevensanscircled": "\u2786",
+ "/dngb:sevensansnegativecircled": "\u2790",
+ "/dngb:sixnegativecircled": "\u277B",
+ "/dngb:sixsanscircled": "\u2785",
+ "/dngb:sixsansnegativecircled": "\u278F",
+ "/dngb:snowflake": "\u2744",
+ "/dngb:snowflaketight": "\u2745",
+ "/dngb:sparkle": "\u2747",
+ "/dngb:sparkleheavy": "\u2748",
+ "/dngb:sparkles": "\u2728",
+ "/dngb:spokedasteriskeight": "\u2733",
+ "/dngb:squaredcrossnegative": "\u274E",
+ "/dngb:squarelowerrightshadowedwhite": "\u2751",
+ "/dngb:squareshadowlowerrightwhite": "\u274F",
+ "/dngb:squareshadowupperrightwhite": "\u2750",
+ "/dngb:squareupperrightshadowedwhite": "\u2752",
+ "/dngb:starcentreblackwhite": "\u272C",
+ "/dngb:starcentreopenblack": "\u272B",
+ "/dngb:starcentreopenpointedcircledeight": "\u2742",
+ "/dngb:starcircledwhite": "\u272A",
+ "/dngb:starofdavid": "\u2721",
+ "/dngb:staroutlinedblack": "\u272D",
+ "/dngb:staroutlinedblackheavy": "\u272E",
+ "/dngb:staroutlinedstresswhite": "\u2729",
+ "/dngb:starpinwheel": "\u272F",
+ "/dngb:starpointedblackeight": "\u2734",
+ "/dngb:starpointedblackfour": "\u2726",
+ "/dngb:starpointedblacksix": "\u2736",
+ "/dngb:starpointedblacktwelve": "\u2739",
+ "/dngb:starpointedpinwheeleight": "\u2735",
+ "/dngb:starpointedwhitefour": "\u2727",
+ "/dngb:starshadowedwhite": "\u2730",
+ "/dngb:tapedrive": "\u2707",
+ "/dngb:telephonelocationsign": "\u2706",
+ "/dngb:tennegativecircled": "\u277F",
+ "/dngb:tensanscircled": "\u2789",
+ "/dngb:tensansnegativecircled": "\u2793",
+ "/dngb:threenegativecircled": "\u2778",
+ "/dngb:threesanscircled": "\u2782",
+ "/dngb:threesansnegativecircled": "\u278C",
+ "/dngb:twonegativecircled": "\u2777",
+ "/dngb:twosanscircled": "\u2781",
+ "/dngb:twosansnegativecircled": "\u278B",
+ "/dngb:verticalbarheavy": "\u275A",
+ "/dngb:verticalbarlight": "\u2758",
+ "/dngb:verticalbarmedium": "\u2759",
+ "/dnheavyhorzlight": "\u2530",
+ "/dnheavyleftlight": "\u2512",
+ "/dnheavyleftuplight": "\u2527",
+ "/dnheavyrightlight": "\u250E",
+ "/dnheavyrightuplight": "\u251F",
+ "/dnheavyuphorzlight": "\u2541",
+ "/dnlighthorzheavy": "\u252F",
+ "/dnlightleftheavy": "\u2511",
+ "/dnlightleftupheavy": "\u2529",
+ "/dnlightrightheavy": "\u250D",
+ "/dnlightrightupheavy": "\u2521",
+ "/dnlightuphorzheavy": "\u2547",
+ "/dnsnghorzdbl": "\u2564",
+ "/dnsngleftdbl": "\u2555",
+ "/dnsngrightdbl": "\u2552",
+ "/doNotLitter": "\u1F6AF",
+ "/dochadathai": "\u0E0E",
+ "/document": "\u1F5CE",
+ "/documentPicture": "\u1F5BB",
+ "/documentText": "\u1F5B9",
+ "/documentTextAndPicture": "\u1F5BA",
+ "/dodekthai": "\u0E14",
+ "/doesnotcontainasnormalsubgroorequalup": "\u22ED",
+ "/doesnotcontainasnormalsubgroup": "\u22EB",
+ "/doesnotdivide": "\u2224",
+ "/doesnotforce": "\u22AE",
+ "/doesnotprecede": "\u2280",
+ "/doesnotprecedeorequal": "\u22E0",
+ "/doesnotprove": "\u22AC",
+ "/doesnotsucceed": "\u2281",
+ "/doesnotsucceedorequal": "\u22E1",
+ "/dog": "\u1F415",
+ "/dogFace": "\u1F436",
+ "/dohiragana": "\u3069",
+ "/dokatakana": "\u30C9",
+ "/dollar": "\u0024",
+ "/dollarinferior": "\uF6E3",
+ "/dollarmonospace": "\uFF04",
+ "/dollaroldstyle": "\uF724",
+ "/dollarsmall": "\uFE69",
+ "/dollarsuperior": "\uF6E4",
+ "/dolphin": "\u1F42C",
+ "/dominohorizontal_00_00": "\u1F031",
+ "/dominohorizontal_00_01": "\u1F032",
+ "/dominohorizontal_00_02": "\u1F033",
+ "/dominohorizontal_00_03": "\u1F034",
+ "/dominohorizontal_00_04": "\u1F035",
+ "/dominohorizontal_00_05": "\u1F036",
+ "/dominohorizontal_00_06": "\u1F037",
+ "/dominohorizontal_01_00": "\u1F038",
+ "/dominohorizontal_01_01": "\u1F039",
+ "/dominohorizontal_01_02": "\u1F03A",
+ "/dominohorizontal_01_03": "\u1F03B",
+ "/dominohorizontal_01_04": "\u1F03C",
+ "/dominohorizontal_01_05": "\u1F03D",
+ "/dominohorizontal_01_06": "\u1F03E",
+ "/dominohorizontal_02_00": "\u1F03F",
+ "/dominohorizontal_02_01": "\u1F040",
+ "/dominohorizontal_02_02": "\u1F041",
+ "/dominohorizontal_02_03": "\u1F042",
+ "/dominohorizontal_02_04": "\u1F043",
+ "/dominohorizontal_02_05": "\u1F044",
+ "/dominohorizontal_02_06": "\u1F045",
+ "/dominohorizontal_03_00": "\u1F046",
+ "/dominohorizontal_03_01": "\u1F047",
+ "/dominohorizontal_03_02": "\u1F048",
+ "/dominohorizontal_03_03": "\u1F049",
+ "/dominohorizontal_03_04": "\u1F04A",
+ "/dominohorizontal_03_05": "\u1F04B",
+ "/dominohorizontal_03_06": "\u1F04C",
+ "/dominohorizontal_04_00": "\u1F04D",
+ "/dominohorizontal_04_01": "\u1F04E",
+ "/dominohorizontal_04_02": "\u1F04F",
+ "/dominohorizontal_04_03": "\u1F050",
+ "/dominohorizontal_04_04": "\u1F051",
+ "/dominohorizontal_04_05": "\u1F052",
+ "/dominohorizontal_04_06": "\u1F053",
+ "/dominohorizontal_05_00": "\u1F054",
+ "/dominohorizontal_05_01": "\u1F055",
+ "/dominohorizontal_05_02": "\u1F056",
+ "/dominohorizontal_05_03": "\u1F057",
+ "/dominohorizontal_05_04": "\u1F058",
+ "/dominohorizontal_05_05": "\u1F059",
+ "/dominohorizontal_05_06": "\u1F05A",
+ "/dominohorizontal_06_00": "\u1F05B",
+ "/dominohorizontal_06_01": "\u1F05C",
+ "/dominohorizontal_06_02": "\u1F05D",
+ "/dominohorizontal_06_03": "\u1F05E",
+ "/dominohorizontal_06_04": "\u1F05F",
+ "/dominohorizontal_06_05": "\u1F060",
+ "/dominohorizontal_06_06": "\u1F061",
+ "/dominohorizontalback": "\u1F030",
+ "/dominovertical_00_00": "\u1F063",
+ "/dominovertical_00_01": "\u1F064",
+ "/dominovertical_00_02": "\u1F065",
+ "/dominovertical_00_03": "\u1F066",
+ "/dominovertical_00_04": "\u1F067",
+ "/dominovertical_00_05": "\u1F068",
+ "/dominovertical_00_06": "\u1F069",
+ "/dominovertical_01_00": "\u1F06A",
+ "/dominovertical_01_01": "\u1F06B",
+ "/dominovertical_01_02": "\u1F06C",
+ "/dominovertical_01_03": "\u1F06D",
+ "/dominovertical_01_04": "\u1F06E",
+ "/dominovertical_01_05": "\u1F06F",
+ "/dominovertical_01_06": "\u1F070",
+ "/dominovertical_02_00": "\u1F071",
+ "/dominovertical_02_01": "\u1F072",
+ "/dominovertical_02_02": "\u1F073",
+ "/dominovertical_02_03": "\u1F074",
+ "/dominovertical_02_04": "\u1F075",
+ "/dominovertical_02_05": "\u1F076",
+ "/dominovertical_02_06": "\u1F077",
+ "/dominovertical_03_00": "\u1F078",
+ "/dominovertical_03_01": "\u1F079",
+ "/dominovertical_03_02": "\u1F07A",
+ "/dominovertical_03_03": "\u1F07B",
+ "/dominovertical_03_04": "\u1F07C",
+ "/dominovertical_03_05": "\u1F07D",
+ "/dominovertical_03_06": "\u1F07E",
+ "/dominovertical_04_00": "\u1F07F",
+ "/dominovertical_04_01": "\u1F080",
+ "/dominovertical_04_02": "\u1F081",
+ "/dominovertical_04_03": "\u1F082",
+ "/dominovertical_04_04": "\u1F083",
+ "/dominovertical_04_05": "\u1F084",
+ "/dominovertical_04_06": "\u1F085",
+ "/dominovertical_05_00": "\u1F086",
+ "/dominovertical_05_01": "\u1F087",
+ "/dominovertical_05_02": "\u1F088",
+ "/dominovertical_05_03": "\u1F089",
+ "/dominovertical_05_04": "\u1F08A",
+ "/dominovertical_05_05": "\u1F08B",
+ "/dominovertical_05_06": "\u1F08C",
+ "/dominovertical_06_00": "\u1F08D",
+ "/dominovertical_06_01": "\u1F08E",
+ "/dominovertical_06_02": "\u1F08F",
+ "/dominovertical_06_03": "\u1F090",
+ "/dominovertical_06_04": "\u1F091",
+ "/dominovertical_06_05": "\u1F092",
+ "/dominovertical_06_06": "\u1F093",
+ "/dominoverticalback": "\u1F062",
+ "/dong": "\u20AB",
+ "/door": "\u1F6AA",
+ "/dorusquare": "\u3326",
+ "/dot": "\u27D1",
+ "/dotaccent": "\u02D9",
+ "/dotaccentcmb": "\u0307",
+ "/dotbelowcmb": "\u0323",
+ "/dotbelowcomb": "\u0323",
+ "/dotkatakana": "\u30FB",
+ "/dotlessbeh": "\u066E",
+ "/dotlessfeh": "\u06A1",
+ "/dotlessi": "\u0131",
+ "/dotlessj": "\uF6BE",
+ "/dotlessjstroke": "\u025F",
+ "/dotlessjstrokehook": "\u0284",
+ "/dotlesskhahabove": "\u06E1",
+ "/dotlessqaf": "\u066F",
+ "/dotlower:hb": "\u05C5",
+ "/dotmath": "\u22C5",
+ "/dotminus": "\u2238",
+ "/dotplus": "\u2214",
+ "/dotraised": "\u2E33",
+ "/dots1": "\u2801",
+ "/dots12": "\u2803",
+ "/dots123": "\u2807",
+ "/dots1234": "\u280F",
+ "/dots12345": "\u281F",
+ "/dots123456": "\u283F",
+ "/dots1234567": "\u287F",
+ "/dots12345678": "\u28FF",
+ "/dots1234568": "\u28BF",
+ "/dots123457": "\u285F",
+ "/dots1234578": "\u28DF",
+ "/dots123458": "\u289F",
+ "/dots12346": "\u282F",
+ "/dots123467": "\u286F",
+ "/dots1234678": "\u28EF",
+ "/dots123468": "\u28AF",
+ "/dots12347": "\u284F",
+ "/dots123478": "\u28CF",
+ "/dots12348": "\u288F",
+ "/dots1235": "\u2817",
+ "/dots12356": "\u2837",
+ "/dots123567": "\u2877",
+ "/dots1235678": "\u28F7",
+ "/dots123568": "\u28B7",
+ "/dots12357": "\u2857",
+ "/dots123578": "\u28D7",
+ "/dots12358": "\u2897",
+ "/dots1236": "\u2827",
+ "/dots12367": "\u2867",
+ "/dots123678": "\u28E7",
+ "/dots12368": "\u28A7",
+ "/dots1237": "\u2847",
+ "/dots12378": "\u28C7",
+ "/dots1238": "\u2887",
+ "/dots124": "\u280B",
+ "/dots1245": "\u281B",
+ "/dots12456": "\u283B",
+ "/dots124567": "\u287B",
+ "/dots1245678": "\u28FB",
+ "/dots124568": "\u28BB",
+ "/dots12457": "\u285B",
+ "/dots124578": "\u28DB",
+ "/dots12458": "\u289B",
+ "/dots1246": "\u282B",
+ "/dots12467": "\u286B",
+ "/dots124678": "\u28EB",
+ "/dots12468": "\u28AB",
+ "/dots1247": "\u284B",
+ "/dots12478": "\u28CB",
+ "/dots1248": "\u288B",
+ "/dots125": "\u2813",
+ "/dots1256": "\u2833",
+ "/dots12567": "\u2873",
+ "/dots125678": "\u28F3",
+ "/dots12568": "\u28B3",
+ "/dots1257": "\u2853",
+ "/dots12578": "\u28D3",
+ "/dots1258": "\u2893",
+ "/dots126": "\u2823",
+ "/dots1267": "\u2863",
+ "/dots12678": "\u28E3",
+ "/dots1268": "\u28A3",
+ "/dots127": "\u2843",
+ "/dots1278": "\u28C3",
+ "/dots128": "\u2883",
+ "/dots13": "\u2805",
+ "/dots134": "\u280D",
+ "/dots1345": "\u281D",
+ "/dots13456": "\u283D",
+ "/dots134567": "\u287D",
+ "/dots1345678": "\u28FD",
+ "/dots134568": "\u28BD",
+ "/dots13457": "\u285D",
+ "/dots134578": "\u28DD",
+ "/dots13458": "\u289D",
+ "/dots1346": "\u282D",
+ "/dots13467": "\u286D",
+ "/dots134678": "\u28ED",
+ "/dots13468": "\u28AD",
+ "/dots1347": "\u284D",
+ "/dots13478": "\u28CD",
+ "/dots1348": "\u288D",
+ "/dots135": "\u2815",
+ "/dots1356": "\u2835",
+ "/dots13567": "\u2875",
+ "/dots135678": "\u28F5",
+ "/dots13568": "\u28B5",
+ "/dots1357": "\u2855",
+ "/dots13578": "\u28D5",
+ "/dots1358": "\u2895",
+ "/dots136": "\u2825",
+ "/dots1367": "\u2865",
+ "/dots13678": "\u28E5",
+ "/dots1368": "\u28A5",
+ "/dots137": "\u2845",
+ "/dots1378": "\u28C5",
+ "/dots138": "\u2885",
+ "/dots14": "\u2809",
+ "/dots145": "\u2819",
+ "/dots1456": "\u2839",
+ "/dots14567": "\u2879",
+ "/dots145678": "\u28F9",
+ "/dots14568": "\u28B9",
+ "/dots1457": "\u2859",
+ "/dots14578": "\u28D9",
+ "/dots1458": "\u2899",
+ "/dots146": "\u2829",
+ "/dots1467": "\u2869",
+ "/dots14678": "\u28E9",
+ "/dots1468": "\u28A9",
+ "/dots147": "\u2849",
+ "/dots1478": "\u28C9",
+ "/dots148": "\u2889",
+ "/dots15": "\u2811",
+ "/dots156": "\u2831",
+ "/dots1567": "\u2871",
+ "/dots15678": "\u28F1",
+ "/dots1568": "\u28B1",
+ "/dots157": "\u2851",
+ "/dots1578": "\u28D1",
+ "/dots158": "\u2891",
+ "/dots16": "\u2821",
+ "/dots167": "\u2861",
+ "/dots1678": "\u28E1",
+ "/dots168": "\u28A1",
+ "/dots17": "\u2841",
+ "/dots178": "\u28C1",
+ "/dots18": "\u2881",
+ "/dots2": "\u2802",
+ "/dots23": "\u2806",
+ "/dots234": "\u280E",
+ "/dots2345": "\u281E",
+ "/dots23456": "\u283E",
+ "/dots234567": "\u287E",
+ "/dots2345678": "\u28FE",
+ "/dots234568": "\u28BE",
+ "/dots23457": "\u285E",
+ "/dots234578": "\u28DE",
+ "/dots23458": "\u289E",
+ "/dots2346": "\u282E",
+ "/dots23467": "\u286E",
+ "/dots234678": "\u28EE",
+ "/dots23468": "\u28AE",
+ "/dots2347": "\u284E",
+ "/dots23478": "\u28CE",
+ "/dots2348": "\u288E",
+ "/dots235": "\u2816",
+ "/dots2356": "\u2836",
+ "/dots23567": "\u2876",
+ "/dots235678": "\u28F6",
+ "/dots23568": "\u28B6",
+ "/dots2357": "\u2856",
+ "/dots23578": "\u28D6",
+ "/dots2358": "\u2896",
+ "/dots236": "\u2826",
+ "/dots2367": "\u2866",
+ "/dots23678": "\u28E6",
+ "/dots2368": "\u28A6",
+ "/dots237": "\u2846",
+ "/dots2378": "\u28C6",
+ "/dots238": "\u2886",
+ "/dots24": "\u280A",
+ "/dots245": "\u281A",
+ "/dots2456": "\u283A",
+ "/dots24567": "\u287A",
+ "/dots245678": "\u28FA",
+ "/dots24568": "\u28BA",
+ "/dots2457": "\u285A",
+ "/dots24578": "\u28DA",
+ "/dots2458": "\u289A",
+ "/dots246": "\u282A",
+ "/dots2467": "\u286A",
+ "/dots24678": "\u28EA",
+ "/dots2468": "\u28AA",
+ "/dots247": "\u284A",
+ "/dots2478": "\u28CA",
+ "/dots248": "\u288A",
+ "/dots25": "\u2812",
+ "/dots256": "\u2832",
+ "/dots2567": "\u2872",
+ "/dots25678": "\u28F2",
+ "/dots2568": "\u28B2",
+ "/dots257": "\u2852",
+ "/dots2578": "\u28D2",
+ "/dots258": "\u2892",
+ "/dots26": "\u2822",
+ "/dots267": "\u2862",
+ "/dots2678": "\u28E2",
+ "/dots268": "\u28A2",
+ "/dots27": "\u2842",
+ "/dots278": "\u28C2",
+ "/dots28": "\u2882",
+ "/dots3": "\u2804",
+ "/dots34": "\u280C",
+ "/dots345": "\u281C",
+ "/dots3456": "\u283C",
+ "/dots34567": "\u287C",
+ "/dots345678": "\u28FC",
+ "/dots34568": "\u28BC",
+ "/dots3457": "\u285C",
+ "/dots34578": "\u28DC",
+ "/dots3458": "\u289C",
+ "/dots346": "\u282C",
+ "/dots3467": "\u286C",
+ "/dots34678": "\u28EC",
+ "/dots3468": "\u28AC",
+ "/dots347": "\u284C",
+ "/dots3478": "\u28CC",
+ "/dots348": "\u288C",
+ "/dots35": "\u2814",
+ "/dots356": "\u2834",
+ "/dots3567": "\u2874",
+ "/dots35678": "\u28F4",
+ "/dots3568": "\u28B4",
+ "/dots357": "\u2854",
+ "/dots3578": "\u28D4",
+ "/dots358": "\u2894",
+ "/dots36": "\u2824",
+ "/dots367": "\u2864",
+ "/dots3678": "\u28E4",
+ "/dots368": "\u28A4",
+ "/dots37": "\u2844",
+ "/dots378": "\u28C4",
+ "/dots38": "\u2884",
+ "/dots4": "\u2808",
+ "/dots45": "\u2818",
+ "/dots456": "\u2838",
+ "/dots4567": "\u2878",
+ "/dots45678": "\u28F8",
+ "/dots4568": "\u28B8",
+ "/dots457": "\u2858",
+ "/dots4578": "\u28D8",
+ "/dots458": "\u2898",
+ "/dots46": "\u2828",
+ "/dots467": "\u2868",
+ "/dots4678": "\u28E8",
+ "/dots468": "\u28A8",
+ "/dots47": "\u2848",
+ "/dots478": "\u28C8",
+ "/dots48": "\u2888",
+ "/dots5": "\u2810",
+ "/dots56": "\u2830",
+ "/dots567": "\u2870",
+ "/dots5678": "\u28F0",
+ "/dots568": "\u28B0",
+ "/dots57": "\u2850",
+ "/dots578": "\u28D0",
+ "/dots58": "\u2890",
+ "/dots6": "\u2820",
+ "/dots67": "\u2860",
+ "/dots678": "\u28E0",
+ "/dots68": "\u28A0",
+ "/dots7": "\u2840",
+ "/dots78": "\u28C0",
+ "/dots8": "\u2880",
+ "/dotsquarefour": "\u2E2C",
+ "/dottedcircle": "\u25CC",
+ "/dottedcross": "\u205C",
+ "/dotupper:hb": "\u05C4",
+ "/doublebarvertical": "\u23F8",
+ "/doubleyodpatah": "\uFB1F",
+ "/doubleyodpatahhebrew": "\uFB1F",
+ "/doughnut": "\u1F369",
+ "/doveOfPeace": "\u1F54A",
+ "/downtackbelowcmb": "\u031E",
+ "/downtackmod": "\u02D5",
+ "/downwarrowleftofuparrow": "\u21F5",
+ "/dparen": "\u249F",
+ "/dparenthesized": "\u249F",
+ "/drachma": "\u20AF",
+ "/dragon": "\u1F409",
+ "/dragonFace": "\u1F432",
+ "/draughtskingblack": "\u26C3",
+ "/draughtskingwhite": "\u26C1",
+ "/draughtsmanblack": "\u26C2",
+ "/draughtsmanwhite": "\u26C0",
+ "/dress": "\u1F457",
+ "/driveslow": "\u26DA",
+ "/dromedaryCamel": "\u1F42A",
+ "/droplet": "\u1F4A7",
+ "/dsquare": "\u1F1A5",
+ "/dsuperior": "\uF6EB",
+ "/dtail": "\u0256",
+ "/dtopbar": "\u018C",
+ "/duhiragana": "\u3065",
+ "/dukatakana": "\u30C5",
+ "/dul": "\u068E",
+ "/dul.fina": "\uFB87",
+ "/dul.isol": "\uFB86",
+ "/dum": "\uA771",
+ "/dvd": "\u1F4C0",
+ "/dyeh": "\u0684",
+ "/dyeh.fina": "\uFB73",
+ "/dyeh.init": "\uFB74",
+ "/dyeh.isol": "\uFB72",
+ "/dyeh.medi": "\uFB75",
+ "/dz": "\u01F3",
+ "/dzaltone": "\u02A3",
+ "/dzcaron": "\u01C6",
+ "/dzcurl": "\u02A5",
+ "/dzeabkhasiancyrillic": "\u04E1",
+ "/dzeabkhcyr": "\u04E1",
+ "/dzecyr": "\u0455",
+ "/dzecyrillic": "\u0455",
+ "/dzed": "\u02A3",
+ "/dzedcurl": "\u02A5",
+ "/dzhecyr": "\u045F",
+ "/dzhecyrillic": "\u045F",
+ "/dzjekomicyr": "\u0507",
+ "/dzzhecyr": "\u052B",
+ "/e": "\u0065",
+ "/e-mail": "\u1F4E7",
+ "/e.fina": "\uFBE5",
+ "/e.inferior": "\u2091",
+ "/e.init": "\uFBE6",
+ "/e.isol": "\uFBE4",
+ "/e.medi": "\uFBE7",
+ "/eVfullwidth": "\u32CE",
+ "/eacute": "\u00E9",
+ "/earOfMaize": "\u1F33D",
+ "/earOfRice": "\u1F33E",
+ "/earth": "\u2641",
+ "/earthGlobeAmericas": "\u1F30E",
+ "/earthGlobeAsiaAustralia": "\u1F30F",
+ "/earthGlobeEuropeAfrica": "\u1F30D",
+ "/earthground": "\u23DA",
+ "/earthideographiccircled": "\u328F",
+ "/earthideographicparen": "\u322F",
+ "/eastsyriaccross": "\u2671",
+ "/ebengali": "\u098F",
+ "/ebopomofo": "\u311C",
+ "/ebreve": "\u0115",
+ "/ecandradeva": "\u090D",
+ "/ecandragujarati": "\u0A8D",
+ "/ecandravowelsigndeva": "\u0945",
+ "/ecandravowelsigngujarati": "\u0AC5",
+ "/ecaron": "\u011B",
+ "/ecedilla": "\u0229",
+ "/ecedillabreve": "\u1E1D",
+ "/echarmenian": "\u0565",
+ "/echyiwnarmenian": "\u0587",
+ "/ecircle": "\u24D4",
+ "/ecirclekatakana": "\u32D3",
+ "/ecircumflex": "\u00EA",
+ "/ecircumflexacute": "\u1EBF",
+ "/ecircumflexbelow": "\u1E19",
+ "/ecircumflexdotbelow": "\u1EC7",
+ "/ecircumflexgrave": "\u1EC1",
+ "/ecircumflexhoi": "\u1EC3",
+ "/ecircumflexhookabove": "\u1EC3",
+ "/ecircumflextilde": "\u1EC5",
+ "/ecyrillic": "\u0454",
+ "/edblgrave": "\u0205",
+ "/edblstruckitalic": "\u2147",
+ "/edeva": "\u090F",
+ "/edieresis": "\u00EB",
+ "/edot": "\u0117",
+ "/edotaccent": "\u0117",
+ "/edotbelow": "\u1EB9",
+ "/eegurmukhi": "\u0A0F",
+ "/eekaasquare": "\u3308",
+ "/eematragurmukhi": "\u0A47",
+ "/efcyr": "\u0444",
+ "/efcyrillic": "\u0444",
+ "/egrave": "\u00E8",
+ "/egravedbl": "\u0205",
+ "/egujarati": "\u0A8F",
+ "/egyptain": "\uA725",
+ "/egyptalef": "\uA723",
+ "/eharmenian": "\u0567",
+ "/ehbopomofo": "\u311D",
+ "/ehiragana": "\u3048",
+ "/ehoi": "\u1EBB",
+ "/ehookabove": "\u1EBB",
+ "/eibopomofo": "\u311F",
+ "/eight": "\u0038",
+ "/eight.inferior": "\u2088",
+ "/eight.roman": "\u2167",
+ "/eight.romansmall": "\u2177",
+ "/eight.superior": "\u2078",
+ "/eightarabic": "\u0668",
+ "/eightbengali": "\u09EE",
+ "/eightcircle": "\u2467",
+ "/eightcircledbl": "\u24FC",
+ "/eightcircleinversesansserif": "\u2791",
+ "/eightcomma": "\u1F109",
+ "/eightdeva": "\u096E",
+ "/eighteencircle": "\u2471",
+ "/eighteencircleblack": "\u24F2",
+ "/eighteenparen": "\u2485",
+ "/eighteenparenthesized": "\u2485",
+ "/eighteenperiod": "\u2499",
+ "/eightfar": "\u06F8",
+ "/eightgujarati": "\u0AEE",
+ "/eightgurmukhi": "\u0A6E",
+ "/eighthackarabic": "\u0668",
+ "/eighthangzhou": "\u3028",
+ "/eighthnote": "\u266A",
+ "/eighthnotebeamed": "\u266B",
+ "/eightideographiccircled": "\u3287",
+ "/eightideographicparen": "\u3227",
+ "/eightinferior": "\u2088",
+ "/eightksquare": "\u1F19F",
+ "/eightmonospace": "\uFF18",
+ "/eightoldstyle": "\uF738",
+ "/eightparen": "\u247B",
+ "/eightparenthesized": "\u247B",
+ "/eightperiod": "\u248F",
+ "/eightpersian": "\u06F8",
+ "/eightroman": "\u2177",
+ "/eightsuperior": "\u2078",
+ "/eightthai": "\u0E58",
+ "/eightycirclesquare": "\u324F",
+ "/einvertedbreve": "\u0207",
+ "/eiotifiedcyr": "\u0465",
+ "/eiotifiedcyrillic": "\u0465",
+ "/eject": "\u23CF",
+ "/ekatakana": "\u30A8",
+ "/ekatakanahalfwidth": "\uFF74",
+ "/ekonkargurmukhi": "\u0A74",
+ "/ekorean": "\u3154",
+ "/elcyr": "\u043B",
+ "/elcyrillic": "\u043B",
+ "/electricLightBulb": "\u1F4A1",
+ "/electricPlug": "\u1F50C",
+ "/electricTorch": "\u1F526",
+ "/electricalintersection": "\u23E7",
+ "/electricarrow": "\u2301",
+ "/element": "\u2208",
+ "/elementdotabove": "\u22F5",
+ "/elementlonghorizontalstroke": "\u22F2",
+ "/elementopeningup": "\u27D2",
+ "/elementoverbar": "\u22F6",
+ "/elementoverbarsmall": "\u22F7",
+ "/elementsmall": "\u220A",
+ "/elementsmallverticalbarhorizontalstroke": "\u22F4",
+ "/elementtwoshorizontalstroke": "\u22F9",
+ "/elementunderbar": "\u22F8",
+ "/elementverticalbarhorizontalstroke": "\u22F3",
+ "/elephant": "\u1F418",
+ "/eleven.roman": "\u216A",
+ "/eleven.romansmall": "\u217A",
+ "/elevencircle": "\u246A",
+ "/elevencircleblack": "\u24EB",
+ "/elevenparen": "\u247E",
+ "/elevenparenthesized": "\u247E",
+ "/elevenperiod": "\u2492",
+ "/elevenroman": "\u217A",
+ "/elhookcyr": "\u0513",
+ "/ellipsis": "\u2026",
+ "/ellipsisdiagonaldownright": "\u22F1",
+ "/ellipsisdiagonalupright": "\u22F0",
+ "/ellipsismidhorizontal": "\u22EF",
+ "/ellipsisvertical": "\u22EE",
+ "/elmiddlehookcyr": "\u0521",
+ "/elsharptailcyr": "\u04C6",
+ "/eltailcyr": "\u052F",
+ "/emacron": "\u0113",
+ "/emacronacute": "\u1E17",
+ "/emacrongrave": "\u1E15",
+ "/emcyr": "\u043C",
+ "/emcyrillic": "\u043C",
+ "/emdash": "\u2014",
+ "/emdashdbl": "\u2E3A",
+ "/emdashtpl": "\u2E3B",
+ "/emdashvertical": "\uFE31",
+ "/emojiModifierFitzpatrickType-1-2": "\u1F3FB",
+ "/emojiModifierFitzpatrickType-3": "\u1F3FC",
+ "/emojiModifierFitzpatrickType-4": "\u1F3FD",
+ "/emojiModifierFitzpatrickType-5": "\u1F3FE",
+ "/emojiModifierFitzpatrickType-6": "\u1F3FF",
+ "/emonospace": "\uFF45",
+ "/emphasis": "\u2383",
+ "/emphasismarkarmenian": "\u055B",
+ "/emptyDocument": "\u1F5CB",
+ "/emptyNote": "\u1F5C5",
+ "/emptyNotePad": "\u1F5C7",
+ "/emptyNotePage": "\u1F5C6",
+ "/emptyPage": "\u1F5CC",
+ "/emptyPages": "\u1F5CD",
+ "/emptyset": "\u2205",
+ "/emquad": "\u2001",
+ "/emsharptailcyr": "\u04CE",
+ "/emspace": "\u2003",
+ "/enbopomofo": "\u3123",
+ "/encyr": "\u043D",
+ "/encyrillic": "\u043D",
+ "/endLeftwardsArrowAbove": "\u1F51A",
+ "/endash": "\u2013",
+ "/endashvertical": "\uFE32",
+ "/endescendercyrillic": "\u04A3",
+ "/endpro": "\u220E",
+ "/eng": "\u014B",
+ "/engbopomofo": "\u3125",
+ "/engecyr": "\u04A5",
+ "/enghecyrillic": "\u04A5",
+ "/enhookcyr": "\u04C8",
+ "/enhookcyrillic": "\u04C8",
+ "/enhookleftcyr": "\u0529",
+ "/enmiddlehookcyr": "\u0523",
+ "/enotch": "\u2C78",
+ "/enquad": "\u2000",
+ "/ensharptailcyr": "\u04CA",
+ "/enspace": "\u2002",
+ "/entailcyr": "\u04A3",
+ "/enter": "\u2386",
+ "/enterpriseideographiccircled": "\u32AD",
+ "/enterpriseideographicparen": "\u323D",
+ "/envelopeDownwardsArrowAbove": "\u1F4E9",
+ "/envelopeLightning": "\u1F584",
+ "/eogonek": "\u0119",
+ "/eokorean": "\u3153",
+ "/eopen": "\u025B",
+ "/eopenclosed": "\u029A",
+ "/eopenreversed": "\u025C",
+ "/eopenreversedclosed": "\u025E",
+ "/eopenreversedhook": "\u025D",
+ "/eparen": "\u24A0",
+ "/eparenthesized": "\u24A0",
+ "/epsilon": "\u03B5",
+ "/epsilonacute": "\u1F73",
+ "/epsilonasper": "\u1F11",
+ "/epsilonasperacute": "\u1F15",
+ "/epsilonaspergrave": "\u1F13",
+ "/epsilongrave": "\u1F72",
+ "/epsilonlenis": "\u1F10",
+ "/epsilonlenisacute": "\u1F14",
+ "/epsilonlenisgrave": "\u1F12",
+ "/epsilonlunatesymbol": "\u03F5",
+ "/epsilonreversedlunatesymbol": "\u03F6",
+ "/epsilontonos": "\u03AD",
+ "/epsilonunderlinefunc": "\u2377",
+ "/equal": "\u003D",
+ "/equal.inferior": "\u208C",
+ "/equal.superior": "\u207C",
+ "/equalandparallel": "\u22D5",
+ "/equalbydefinition": "\u225D",
+ "/equalmonospace": "\uFF1D",
+ "/equalorgreater": "\u22DD",
+ "/equalorless": "\u22DC",
+ "/equalorprecedes": "\u22DE",
+ "/equalorsucceeds": "\u22DF",
+ "/equalscolon": "\u2255",
+ "/equalsmall": "\uFE66",
+ "/equalsuperior": "\u207C",
+ "/equiangular": "\u225A",
+ "/equivalence": "\u2261",
+ "/equivalent": "\u224D",
+ "/eranameheiseisquare": "\u337B",
+ "/eranamemeizisquare": "\u337E",
+ "/eranamesyouwasquare": "\u337C",
+ "/eranametaisyousquare": "\u337D",
+ "/eraseleft": "\u232B",
+ "/eraseright": "\u2326",
+ "/erbopomofo": "\u3126",
+ "/ercyr": "\u0440",
+ "/ercyrillic": "\u0440",
+ "/ereversed": "\u0258",
+ "/ereversedcyr": "\u044D",
+ "/ereversedcyrillic": "\u044D",
+ "/ereverseddieresiscyr": "\u04ED",
+ "/ergfullwidth": "\u32CD",
+ "/ertickcyr": "\u048F",
+ "/escript": "\u212F",
+ "/escyr": "\u0441",
+ "/escyrillic": "\u0441",
+ "/esdescendercyrillic": "\u04AB",
+ "/esh": "\u0283",
+ "/eshcurl": "\u0286",
+ "/eshortdeva": "\u090E",
+ "/eshortvowelsigndeva": "\u0946",
+ "/eshreversedloop": "\u01AA",
+ "/eshsquatreversed": "\u0285",
+ "/esmallhiragana": "\u3047",
+ "/esmallkatakana": "\u30A7",
+ "/esmallkatakanahalfwidth": "\uFF6A",
+ "/estailcyr": "\u04AB",
+ "/estimated": "\u212E",
+ "/estimates": "\u2259",
+ "/estroke": "\u0247",
+ "/esukuudosquare": "\u3307",
+ "/esuperior": "\uF6EC",
+ "/et": "\uA76B",
+ "/eta": "\u03B7",
+ "/etaacute": "\u1F75",
+ "/etaacuteiotasub": "\u1FC4",
+ "/etaasper": "\u1F21",
+ "/etaasperacute": "\u1F25",
+ "/etaasperacuteiotasub": "\u1F95",
+ "/etaaspergrave": "\u1F23",
+ "/etaaspergraveiotasub": "\u1F93",
+ "/etaasperiotasub": "\u1F91",
+ "/etaaspertilde": "\u1F27",
+ "/etaaspertildeiotasub": "\u1F97",
+ "/etagrave": "\u1F74",
+ "/etagraveiotasub": "\u1FC2",
+ "/etaiotasub": "\u1FC3",
+ "/etalenis": "\u1F20",
+ "/etalenisacute": "\u1F24",
+ "/etalenisacuteiotasub": "\u1F94",
+ "/etalenisgrave": "\u1F22",
+ "/etalenisgraveiotasub": "\u1F92",
+ "/etalenisiotasub": "\u1F90",
+ "/etalenistilde": "\u1F26",
+ "/etalenistildeiotasub": "\u1F96",
+ "/etarmenian": "\u0568",
+ "/etatilde": "\u1FC6",
+ "/etatildeiotasub": "\u1FC7",
+ "/etatonos": "\u03AE",
+ "/eth": "\u00F0",
+ "/ethi:aaglottal": "\u12A3",
+ "/ethi:aglottal": "\u12A0",
+ "/ethi:ba": "\u1260",
+ "/ethi:baa": "\u1263",
+ "/ethi:be": "\u1265",
+ "/ethi:bee": "\u1264",
+ "/ethi:bi": "\u1262",
+ "/ethi:bo": "\u1266",
+ "/ethi:bu": "\u1261",
+ "/ethi:bwa": "\u1267",
+ "/ethi:ca": "\u1278",
+ "/ethi:caa": "\u127B",
+ "/ethi:ce": "\u127D",
+ "/ethi:cee": "\u127C",
+ "/ethi:cha": "\u1328",
+ "/ethi:chaa": "\u132B",
+ "/ethi:che": "\u132D",
+ "/ethi:chee": "\u132C",
+ "/ethi:chi": "\u132A",
+ "/ethi:cho": "\u132E",
+ "/ethi:chu": "\u1329",
+ "/ethi:chwa": "\u132F",
+ "/ethi:ci": "\u127A",
+ "/ethi:co": "\u127E",
+ "/ethi:colon": "\u1365",
+ "/ethi:comma": "\u1363",
+ "/ethi:cu": "\u1279",
+ "/ethi:cwa": "\u127F",
+ "/ethi:da": "\u12F0",
+ "/ethi:daa": "\u12F3",
+ "/ethi:dda": "\u12F8",
+ "/ethi:ddaa": "\u12FB",
+ "/ethi:dde": "\u12FD",
+ "/ethi:ddee": "\u12FC",
+ "/ethi:ddi": "\u12FA",
+ "/ethi:ddo": "\u12FE",
+ "/ethi:ddu": "\u12F9",
+ "/ethi:ddwa": "\u12FF",
+ "/ethi:de": "\u12F5",
+ "/ethi:dee": "\u12F4",
+ "/ethi:di": "\u12F2",
+ "/ethi:do": "\u12F6",
+ "/ethi:du": "\u12F1",
+ "/ethi:dwa": "\u12F7",
+ "/ethi:eeglottal": "\u12A4",
+ "/ethi:eglottal": "\u12A5",
+ "/ethi:eight": "\u1370",
+ "/ethi:eighty": "\u1379",
+ "/ethi:fa": "\u1348",
+ "/ethi:faa": "\u134B",
+ "/ethi:fe": "\u134D",
+ "/ethi:fee": "\u134C",
+ "/ethi:fi": "\u134A",
+ "/ethi:fifty": "\u1376",
+ "/ethi:five": "\u136D",
+ "/ethi:fo": "\u134E",
+ "/ethi:forty": "\u1375",
+ "/ethi:four": "\u136C",
+ "/ethi:fu": "\u1349",
+ "/ethi:fullstop": "\u1362",
+ "/ethi:fwa": "\u134F",
+ "/ethi:fya": "\u135A",
+ "/ethi:ga": "\u1308",
+ "/ethi:gaa": "\u130B",
+ "/ethi:ge": "\u130D",
+ "/ethi:gee": "\u130C",
+ "/ethi:geminationandvowellengthmarkcmb": "\u135D",
+ "/ethi:geminationmarkcmb": "\u135F",
+ "/ethi:gga": "\u1318",
+ "/ethi:ggaa": "\u131B",
+ "/ethi:gge": "\u131D",
+ "/ethi:ggee": "\u131C",
+ "/ethi:ggi": "\u131A",
+ "/ethi:ggo": "\u131E",
+ "/ethi:ggu": "\u1319",
+ "/ethi:ggwaa": "\u131F",
+ "/ethi:gi": "\u130A",
+ "/ethi:go": "\u130E",
+ "/ethi:goa": "\u130F",
+ "/ethi:gu": "\u1309",
+ "/ethi:gwa": "\u1310",
+ "/ethi:gwaa": "\u1313",
+ "/ethi:gwe": "\u1315",
+ "/ethi:gwee": "\u1314",
+ "/ethi:gwi": "\u1312",
+ "/ethi:ha": "\u1200",
+ "/ethi:haa": "\u1203",
+ "/ethi:he": "\u1205",
+ "/ethi:hee": "\u1204",
+ "/ethi:hha": "\u1210",
+ "/ethi:hhaa": "\u1213",
+ "/ethi:hhe": "\u1215",
+ "/ethi:hhee": "\u1214",
+ "/ethi:hhi": "\u1212",
+ "/ethi:hho": "\u1216",
+ "/ethi:hhu": "\u1211",
+ "/ethi:hhwa": "\u1217",
+ "/ethi:hi": "\u1202",
+ "/ethi:ho": "\u1206",
+ "/ethi:hoa": "\u1207",
+ "/ethi:hu": "\u1201",
+ "/ethi:hundred": "\u137B",
+ "/ethi:iglottal": "\u12A2",
+ "/ethi:ja": "\u1300",
+ "/ethi:jaa": "\u1303",
+ "/ethi:je": "\u1305",
+ "/ethi:jee": "\u1304",
+ "/ethi:ji": "\u1302",
+ "/ethi:jo": "\u1306",
+ "/ethi:ju": "\u1301",
+ "/ethi:jwa": "\u1307",
+ "/ethi:ka": "\u12A8",
+ "/ethi:kaa": "\u12AB",
+ "/ethi:ke": "\u12AD",
+ "/ethi:kee": "\u12AC",
+ "/ethi:ki": "\u12AA",
+ "/ethi:ko": "\u12AE",
+ "/ethi:koa": "\u12AF",
+ "/ethi:ku": "\u12A9",
+ "/ethi:kwa": "\u12B0",
+ "/ethi:kwaa": "\u12B3",
+ "/ethi:kwe": "\u12B5",
+ "/ethi:kwee": "\u12B4",
+ "/ethi:kwi": "\u12B2",
+ "/ethi:kxa": "\u12B8",
+ "/ethi:kxaa": "\u12BB",
+ "/ethi:kxe": "\u12BD",
+ "/ethi:kxee": "\u12BC",
+ "/ethi:kxi": "\u12BA",
+ "/ethi:kxo": "\u12BE",
+ "/ethi:kxu": "\u12B9",
+ "/ethi:kxwa": "\u12C0",
+ "/ethi:kxwaa": "\u12C3",
+ "/ethi:kxwe": "\u12C5",
+ "/ethi:kxwee": "\u12C4",
+ "/ethi:kxwi": "\u12C2",
+ "/ethi:la": "\u1208",
+ "/ethi:laa": "\u120B",
+ "/ethi:le": "\u120D",
+ "/ethi:lee": "\u120C",
+ "/ethi:li": "\u120A",
+ "/ethi:lo": "\u120E",
+ "/ethi:lu": "\u1209",
+ "/ethi:lwa": "\u120F",
+ "/ethi:ma": "\u1218",
+ "/ethi:maa": "\u121B",
+ "/ethi:me": "\u121D",
+ "/ethi:mee": "\u121C",
+ "/ethi:mi": "\u121A",
+ "/ethi:mo": "\u121E",
+ "/ethi:mu": "\u1219",
+ "/ethi:mwa": "\u121F",
+ "/ethi:mya": "\u1359",
+ "/ethi:na": "\u1290",
+ "/ethi:naa": "\u1293",
+ "/ethi:ne": "\u1295",
+ "/ethi:nee": "\u1294",
+ "/ethi:ni": "\u1292",
+ "/ethi:nine": "\u1371",
+ "/ethi:ninety": "\u137A",
+ "/ethi:no": "\u1296",
+ "/ethi:nu": "\u1291",
+ "/ethi:nwa": "\u1297",
+ "/ethi:nya": "\u1298",
+ "/ethi:nyaa": "\u129B",
+ "/ethi:nye": "\u129D",
+ "/ethi:nyee": "\u129C",
+ "/ethi:nyi": "\u129A",
+ "/ethi:nyo": "\u129E",
+ "/ethi:nyu": "\u1299",
+ "/ethi:nywa": "\u129F",
+ "/ethi:oglottal": "\u12A6",
+ "/ethi:one": "\u1369",
+ "/ethi:pa": "\u1350",
+ "/ethi:paa": "\u1353",
+ "/ethi:paragraphseparator": "\u1368",
+ "/ethi:pe": "\u1355",
+ "/ethi:pee": "\u1354",
+ "/ethi:pha": "\u1330",
+ "/ethi:phaa": "\u1333",
+ "/ethi:pharyngeala": "\u12D0",
+ "/ethi:pharyngealaa": "\u12D3",
+ "/ethi:pharyngeale": "\u12D5",
+ "/ethi:pharyngealee": "\u12D4",
+ "/ethi:pharyngeali": "\u12D2",
+ "/ethi:pharyngealo": "\u12D6",
+ "/ethi:pharyngealu": "\u12D1",
+ "/ethi:phe": "\u1335",
+ "/ethi:phee": "\u1334",
+ "/ethi:phi": "\u1332",
+ "/ethi:pho": "\u1336",
+ "/ethi:phu": "\u1331",
+ "/ethi:phwa": "\u1337",
+ "/ethi:pi": "\u1352",
+ "/ethi:po": "\u1356",
+ "/ethi:prefacecolon": "\u1366",
+ "/ethi:pu": "\u1351",
+ "/ethi:pwa": "\u1357",
+ "/ethi:qa": "\u1240",
+ "/ethi:qaa": "\u1243",
+ "/ethi:qe": "\u1245",
+ "/ethi:qee": "\u1244",
+ "/ethi:qha": "\u1250",
+ "/ethi:qhaa": "\u1253",
+ "/ethi:qhe": "\u1255",
+ "/ethi:qhee": "\u1254",
+ "/ethi:qhi": "\u1252",
+ "/ethi:qho": "\u1256",
+ "/ethi:qhu": "\u1251",
+ "/ethi:qhwa": "\u1258",
+ "/ethi:qhwaa": "\u125B",
+ "/ethi:qhwe": "\u125D",
+ "/ethi:qhwee": "\u125C",
+ "/ethi:qhwi": "\u125A",
+ "/ethi:qi": "\u1242",
+ "/ethi:qo": "\u1246",
+ "/ethi:qoa": "\u1247",
+ "/ethi:qu": "\u1241",
+ "/ethi:questionmark": "\u1367",
+ "/ethi:qwa": "\u1248",
+ "/ethi:qwaa": "\u124B",
+ "/ethi:qwe": "\u124D",
+ "/ethi:qwee": "\u124C",
+ "/ethi:qwi": "\u124A",
+ "/ethi:ra": "\u1228",
+ "/ethi:raa": "\u122B",
+ "/ethi:re": "\u122D",
+ "/ethi:ree": "\u122C",
+ "/ethi:ri": "\u122A",
+ "/ethi:ro": "\u122E",
+ "/ethi:ru": "\u1229",
+ "/ethi:rwa": "\u122F",
+ "/ethi:rya": "\u1358",
+ "/ethi:sa": "\u1230",
+ "/ethi:saa": "\u1233",
+ "/ethi:se": "\u1235",
+ "/ethi:sectionmark": "\u1360",
+ "/ethi:see": "\u1234",
+ "/ethi:semicolon": "\u1364",
+ "/ethi:seven": "\u136F",
+ "/ethi:seventy": "\u1378",
+ "/ethi:sha": "\u1238",
+ "/ethi:shaa": "\u123B",
+ "/ethi:she": "\u123D",
+ "/ethi:shee": "\u123C",
+ "/ethi:shi": "\u123A",
+ "/ethi:sho": "\u123E",
+ "/ethi:shu": "\u1239",
+ "/ethi:shwa": "\u123F",
+ "/ethi:si": "\u1232",
+ "/ethi:six": "\u136E",
+ "/ethi:sixty": "\u1377",
+ "/ethi:so": "\u1236",
+ "/ethi:su": "\u1231",
+ "/ethi:swa": "\u1237",
+ "/ethi:sza": "\u1220",
+ "/ethi:szaa": "\u1223",
+ "/ethi:sze": "\u1225",
+ "/ethi:szee": "\u1224",
+ "/ethi:szi": "\u1222",
+ "/ethi:szo": "\u1226",
+ "/ethi:szu": "\u1221",
+ "/ethi:szwa": "\u1227",
+ "/ethi:ta": "\u1270",
+ "/ethi:taa": "\u1273",
+ "/ethi:te": "\u1275",
+ "/ethi:tee": "\u1274",
+ "/ethi:ten": "\u1372",
+ "/ethi:tenthousand": "\u137C",
+ "/ethi:tha": "\u1320",
+ "/ethi:thaa": "\u1323",
+ "/ethi:the": "\u1325",
+ "/ethi:thee": "\u1324",
+ "/ethi:thi": "\u1322",
+ "/ethi:thirty": "\u1374",
+ "/ethi:tho": "\u1326",
+ "/ethi:three": "\u136B",
+ "/ethi:thu": "\u1321",
+ "/ethi:thwa": "\u1327",
+ "/ethi:ti": "\u1272",
+ "/ethi:to": "\u1276",
+ "/ethi:tsa": "\u1338",
+ "/ethi:tsaa": "\u133B",
+ "/ethi:tse": "\u133D",
+ "/ethi:tsee": "\u133C",
+ "/ethi:tsi": "\u133A",
+ "/ethi:tso": "\u133E",
+ "/ethi:tsu": "\u1339",
+ "/ethi:tswa": "\u133F",
+ "/ethi:tu": "\u1271",
+ "/ethi:twa": "\u1277",
+ "/ethi:twenty": "\u1373",
+ "/ethi:two": "\u136A",
+ "/ethi:tza": "\u1340",
+ "/ethi:tzaa": "\u1343",
+ "/ethi:tze": "\u1345",
+ "/ethi:tzee": "\u1344",
+ "/ethi:tzi": "\u1342",
+ "/ethi:tzo": "\u1346",
+ "/ethi:tzoa": "\u1347",
+ "/ethi:tzu": "\u1341",
+ "/ethi:uglottal": "\u12A1",
+ "/ethi:va": "\u1268",
+ "/ethi:vaa": "\u126B",
+ "/ethi:ve": "\u126D",
+ "/ethi:vee": "\u126C",
+ "/ethi:vi": "\u126A",
+ "/ethi:vo": "\u126E",
+ "/ethi:vowellengthmarkcmb": "\u135E",
+ "/ethi:vu": "\u1269",
+ "/ethi:vwa": "\u126F",
+ "/ethi:wa": "\u12C8",
+ "/ethi:waa": "\u12CB",
+ "/ethi:waglottal": "\u12A7",
+ "/ethi:we": "\u12CD",
+ "/ethi:wee": "\u12CC",
+ "/ethi:wi": "\u12CA",
+ "/ethi:wo": "\u12CE",
+ "/ethi:woa": "\u12CF",
+ "/ethi:wordspace": "\u1361",
+ "/ethi:wu": "\u12C9",
+ "/ethi:xa": "\u1280",
+ "/ethi:xaa": "\u1283",
+ "/ethi:xe": "\u1285",
+ "/ethi:xee": "\u1284",
+ "/ethi:xi": "\u1282",
+ "/ethi:xo": "\u1286",
+ "/ethi:xoa": "\u1287",
+ "/ethi:xu": "\u1281",
+ "/ethi:xwa": "\u1288",
+ "/ethi:xwaa": "\u128B",
+ "/ethi:xwe": "\u128D",
+ "/ethi:xwee": "\u128C",
+ "/ethi:xwi": "\u128A",
+ "/ethi:ya": "\u12E8",
+ "/ethi:yaa": "\u12EB",
+ "/ethi:ye": "\u12ED",
+ "/ethi:yee": "\u12EC",
+ "/ethi:yi": "\u12EA",
+ "/ethi:yo": "\u12EE",
+ "/ethi:yoa": "\u12EF",
+ "/ethi:yu": "\u12E9",
+ "/ethi:za": "\u12D8",
+ "/ethi:zaa": "\u12DB",
+ "/ethi:ze": "\u12DD",
+ "/ethi:zee": "\u12DC",
+ "/ethi:zha": "\u12E0",
+ "/ethi:zhaa": "\u12E3",
+ "/ethi:zhe": "\u12E5",
+ "/ethi:zhee": "\u12E4",
+ "/ethi:zhi": "\u12E2",
+ "/ethi:zho": "\u12E6",
+ "/ethi:zhu": "\u12E1",
+ "/ethi:zhwa": "\u12E7",
+ "/ethi:zi": "\u12DA",
+ "/ethi:zo": "\u12DE",
+ "/ethi:zu": "\u12D9",
+ "/ethi:zwa": "\u12DF",
+ "/etilde": "\u1EBD",
+ "/etildebelow": "\u1E1B",
+ "/etnahta:hb": "\u0591",
+ "/etnahtafoukhhebrew": "\u0591",
+ "/etnahtafoukhlefthebrew": "\u0591",
+ "/etnahtahebrew": "\u0591",
+ "/etnahtalefthebrew": "\u0591",
+ "/eturned": "\u01DD",
+ "/eukorean": "\u3161",
+ "/eukrcyr": "\u0454",
+ "/euler": "\u2107",
+ "/euro": "\u20AC",
+ "/euroarchaic": "\u20A0",
+ "/europeanCastle": "\u1F3F0",
+ "/europeanPostOffice": "\u1F3E4",
+ "/evergreenTree": "\u1F332",
+ "/evowelsignbengali": "\u09C7",
+ "/evowelsigndeva": "\u0947",
+ "/evowelsigngujarati": "\u0AC7",
+ "/excellentideographiccircled": "\u329D",
+ "/excess": "\u2239",
+ "/exclam": "\u0021",
+ "/exclamarmenian": "\u055C",
+ "/exclamationquestion": "\u2049",
+ "/exclamdbl": "\u203C",
+ "/exclamdown": "\u00A1",
+ "/exclamdownsmall": "\uF7A1",
+ "/exclammonospace": "\uFF01",
+ "/exclamsmall": "\uF721",
+ "/existential": "\u2203",
+ "/expressionlessFace": "\u1F611",
+ "/extraterrestrialAlien": "\u1F47D",
+ "/eye": "\u1F441",
+ "/eyeglasses": "\u1F453",
+ "/eyes": "\u1F440",
+ "/ezh": "\u0292",
+ "/ezhcaron": "\u01EF",
+ "/ezhcurl": "\u0293",
+ "/ezhreversed": "\u01B9",
+ "/ezhtail": "\u01BA",
+ "/f": "\u0066",
+ "/f_f": "\uFB00",
+ "/f_f_i": "\uFB03",
+ "/f_f_l": "\uFB04",
+ "/faceMassage": "\u1F486",
+ "/faceSavouringDeliciousFood": "\u1F60B",
+ "/faceScreamingInFear": "\u1F631",
+ "/faceThrowingAKiss": "\u1F618",
+ "/faceWithColdSweat": "\u1F613",
+ "/faceWithLookOfTriumph": "\u1F624",
+ "/faceWithMedicalMask": "\u1F637",
+ "/faceWithNoGoodGesture": "\u1F645",
+ "/faceWithOkGesture": "\u1F646",
+ "/faceWithOpenMouth": "\u1F62E",
+ "/faceWithOpenMouthAndColdSweat": "\u1F630",
+ "/faceWithRollingEyes": "\u1F644",
+ "/faceWithStuckOutTongue": "\u1F61B",
+ "/faceWithStuckOutTongueAndTightlyClosedEyes": "\u1F61D",
+ "/faceWithStuckOutTongueAndWinkingEye": "\u1F61C",
+ "/faceWithTearsOfJoy": "\u1F602",
+ "/faceWithoutMouth": "\u1F636",
+ "/facsimile": "\u213B",
+ "/factory": "\u1F3ED",
+ "/fadeva": "\u095E",
+ "/fagurmukhi": "\u0A5E",
+ "/fahrenheit": "\u2109",
+ "/fallenLeaf": "\u1F342",
+ "/fallingdiagonal": "\u27CD",
+ "/fallingdiagonalincircleinsquareblackwhite": "\u26DE",
+ "/family": "\u1F46A",
+ "/farsi": "\u262B",
+ "/farsiYehDigitFourBelow": "\u0777",
+ "/farsiYehDigitThreeAbove": "\u0776",
+ "/farsiYehDigitTwoAbove": "\u0775",
+ "/fatha": "\u064E",
+ "/fathaIsol": "\uFE76",
+ "/fathaMedi": "\uFE77",
+ "/fathaarabic": "\u064E",
+ "/fathalowarabic": "\u064E",
+ "/fathasmall": "\u0618",
+ "/fathatan": "\u064B",
+ "/fathatanIsol": "\uFE70",
+ "/fathatanarabic": "\u064B",
+ "/fathatwodotsdots": "\u065E",
+ "/fatherChristmas": "\u1F385",
+ "/faxIcon": "\u1F5B7",
+ "/faxMachine": "\u1F4E0",
+ "/fbopomofo": "\u3108",
+ "/fcircle": "\u24D5",
+ "/fdot": "\u1E1F",
+ "/fdotaccent": "\u1E1F",
+ "/fearfulFace": "\u1F628",
+ "/februarytelegraph": "\u32C1",
+ "/feh.fina": "\uFED2",
+ "/feh.init": "\uFED3",
+ "/feh.init_alefmaksura.fina": "\uFC31",
+ "/feh.init_hah.fina": "\uFC2E",
+ "/feh.init_hah.medi": "\uFCBF",
+ "/feh.init_jeem.fina": "\uFC2D",
+ "/feh.init_jeem.medi": "\uFCBE",
+ "/feh.init_khah.fina": "\uFC2F",
+ "/feh.init_khah.medi": "\uFCC0",
+ "/feh.init_khah.medi_meem.medi": "\uFD7D",
+ "/feh.init_meem.fina": "\uFC30",
+ "/feh.init_meem.medi": "\uFCC1",
+ "/feh.init_yeh.fina": "\uFC32",
+ "/feh.isol": "\uFED1",
+ "/feh.medi": "\uFED4",
+ "/feh.medi_alefmaksura.fina": "\uFC7C",
+ "/feh.medi_khah.medi_meem.fina": "\uFD7C",
+ "/feh.medi_meem.medi_yeh.fina": "\uFDC1",
+ "/feh.medi_yeh.fina": "\uFC7D",
+ "/fehThreeDotsUpBelow": "\u0761",
+ "/fehTwoDotsBelow": "\u0760",
+ "/feharabic": "\u0641",
+ "/feharmenian": "\u0586",
+ "/fehdotbelow": "\u06A3",
+ "/fehdotbelowright": "\u06A2",
+ "/fehfinalarabic": "\uFED2",
+ "/fehinitialarabic": "\uFED3",
+ "/fehmedialarabic": "\uFED4",
+ "/fehthreedotsbelow": "\u06A5",
+ "/feicoptic": "\u03E5",
+ "/female": "\u2640",
+ "/femaleideographiccircled": "\u329B",
+ "/feng": "\u02A9",
+ "/ferrisWheel": "\u1F3A1",
+ "/ferry": "\u26F4",
+ "/festivalideographicparen": "\u3240",
+ "/ff": "\uFB00",
+ "/ffi": "\uFB03",
+ "/ffl": "\uFB04",
+ "/fhook": "\u0192",
+ "/fi": "\uFB01", # ligature "fi"
+ "/fieldHockeyStickAndBall": "\u1F3D1",
+ "/fifteencircle": "\u246E",
+ "/fifteencircleblack": "\u24EF",
+ "/fifteenparen": "\u2482",
+ "/fifteenparenthesized": "\u2482",
+ "/fifteenperiod": "\u2496",
+ "/fifty.roman": "\u216C",
+ "/fifty.romansmall": "\u217C",
+ "/fiftycircle": "\u32BF",
+ "/fiftycirclesquare": "\u324C",
+ "/fiftyearlyform.roman": "\u2186",
+ "/fiftythousand.roman": "\u2187",
+ "/figuredash": "\u2012",
+ "/figurespace": "\u2007",
+ "/fileCabinet": "\u1F5C4",
+ "/fileFolder": "\u1F4C1",
+ "/filledbox": "\u25A0",
+ "/filledrect": "\u25AC",
+ "/filledstopabove": "\u06EC",
+ "/filmFrames": "\u1F39E",
+ "/filmProjector": "\u1F4FD",
+ "/finalkaf": "\u05DA",
+ "/finalkaf:hb": "\u05DA",
+ "/finalkafdagesh": "\uFB3A",
+ "/finalkafdageshhebrew": "\uFB3A",
+ "/finalkafhebrew": "\u05DA",
+ "/finalkafqamats": "\u05DA",
+ "/finalkafqamatshebrew": "\u05DA",
+ "/finalkafsheva": "\u05DA",
+ "/finalkafshevahebrew": "\u05DA",
+ "/finalkafwithdagesh:hb": "\uFB3A",
+ "/finalmem": "\u05DD",
+ "/finalmem:hb": "\u05DD",
+ "/finalmemhebrew": "\u05DD",
+ "/finalmemwide:hb": "\uFB26",
+ "/finalnun": "\u05DF",
+ "/finalnun:hb": "\u05DF",
+ "/finalnunhebrew": "\u05DF",
+ "/finalpe": "\u05E3",
+ "/finalpe:hb": "\u05E3",
+ "/finalpehebrew": "\u05E3",
+ "/finalpewithdagesh:hb": "\uFB43",
+ "/finalsigma": "\u03C2",
+ "/finaltsadi": "\u05E5",
+ "/finaltsadi:hb": "\u05E5",
+ "/finaltsadihebrew": "\u05E5",
+ "/financialideographiccircled": "\u3296",
+ "/financialideographicparen": "\u3236",
+ "/finsular": "\uA77C",
+ "/fire": "\u1F525",
+ "/fireEngine": "\u1F692",
+ "/fireideographiccircled": "\u328B",
+ "/fireideographicparen": "\u322B",
+ "/fireworkSparkler": "\u1F387",
+ "/fireworks": "\u1F386",
+ "/firstQuarterMoon": "\u1F313",
+ "/firstQuarterMoonFace": "\u1F31B",
+ "/firstquartermoon": "\u263D",
+ "/firststrongisolate": "\u2068",
+ "/firsttonechinese": "\u02C9",
+ "/fish": "\u1F41F",
+ "/fishCakeSwirlDesign": "\u1F365",
+ "/fisheye": "\u25C9",
+ "/fishingPoleAndFish": "\u1F3A3",
+ "/fistedHandSign": "\u1F44A",
+ "/fitacyr": "\u0473",
+ "/fitacyrillic": "\u0473",
+ "/five": "\u0035",
+ "/five.inferior": "\u2085",
+ "/five.roman": "\u2164",
+ "/five.romansmall": "\u2174",
+ "/five.superior": "\u2075",
+ "/fivearabic": "\u0665",
+ "/fivebengali": "\u09EB",
+ "/fivecircle": "\u2464",
+ "/fivecircledbl": "\u24F9",
+ "/fivecircleinversesansserif": "\u278E",
+ "/fivecomma": "\u1F106",
+ "/fivedeva": "\u096B",
+ "/fivedot": "\u2E2D",
+ "/fivedotpunctuation": "\u2059",
+ "/fiveeighths": "\u215D",
+ "/fivefar": "\u06F5",
+ "/fivegujarati": "\u0AEB",
+ "/fivegurmukhi": "\u0A6B",
+ "/fivehackarabic": "\u0665",
+ "/fivehangzhou": "\u3025",
+ "/fivehundred.roman": "\u216E",
+ "/fivehundred.romansmall": "\u217E",
+ "/fiveideographiccircled": "\u3284",
+ "/fiveideographicparen": "\u3224",
+ "/fiveinferior": "\u2085",
+ "/fivemonospace": "\uFF15",
+ "/fiveoldstyle": "\uF735",
+ "/fiveparen": "\u2478",
+ "/fiveparenthesized": "\u2478",
+ "/fiveperiod": "\u248C",
+ "/fivepersian": "\u06F5",
+ "/fivepointedstar": "\u066D",
+ "/fivepointonesquare": "\u1F1A0",
+ "/fiveroman": "\u2174",
+ "/fivesixths": "\u215A",
+ "/fivesuperior": "\u2075",
+ "/fivethai": "\u0E55",
+ "/fivethousand.roman": "\u2181",
+ "/fl": "\uFB02",
+ "/flagblack": "\u2691",
+ "/flaghorizontalmiddlestripeblackwhite": "\u26FF",
+ "/flaginhole": "\u26F3",
+ "/flagwhite": "\u2690",
+ "/flatness": "\u23E5",
+ "/fleurdelis": "\u269C",
+ "/flexedBiceps": "\u1F4AA",
+ "/floorleft": "\u230A",
+ "/floorright": "\u230B",
+ "/floppyDisk": "\u1F4BE",
+ "/floralheartbulletreversedrotated": "\u2619",
+ "/florin": "\u0192",
+ "/flower": "\u2698",
+ "/flowerPlayingCards": "\u1F3B4",
+ "/flowerpunctuationmark": "\u2055",
+ "/flushedFace": "\u1F633",
+ "/flyingEnvelope": "\u1F585",
+ "/flyingSaucer": "\u1F6F8",
+ "/fmfullwidth": "\u3399",
+ "/fmonospace": "\uFF46",
+ "/fmsquare": "\u3399",
+ "/fofanthai": "\u0E1F",
+ "/fofathai": "\u0E1D",
+ "/fog": "\u1F32B",
+ "/foggy": "\u1F301",
+ "/folder": "\u1F5C0",
+ "/fongmanthai": "\u0E4F",
+ "/footnote": "\u0602",
+ "/footprints": "\u1F463",
+ "/footsquare": "\u23CD",
+ "/forall": "\u2200",
+ "/forces": "\u22A9",
+ "/fork": "\u2442",
+ "/forkKnife": "\u1F374",
+ "/forkKnifePlate": "\u1F37D",
+ "/forsamaritan": "\u214F",
+ "/fortycircle": "\u32B5",
+ "/fortycirclesquare": "\u324B",
+ "/fortyeightcircle": "\u32BD",
+ "/fortyfivecircle": "\u32BA",
+ "/fortyfourcircle": "\u32B9",
+ "/fortyninecircle": "\u32BE",
+ "/fortyonecircle": "\u32B6",
+ "/fortysevencircle": "\u32BC",
+ "/fortysixcircle": "\u32BB",
+ "/fortythreecircle": "\u32B8",
+ "/fortytwocircle": "\u32B7",
+ "/fountain": "\u26F2",
+ "/four": "\u0034",
+ "/four.inferior": "\u2084",
+ "/four.roman": "\u2163",
+ "/four.romansmall": "\u2173",
+ "/four.superior": "\u2074",
+ "/fourLeafClover": "\u1F340",
+ "/fourarabic": "\u0664",
+ "/fourbengali": "\u09EA",
+ "/fourcircle": "\u2463",
+ "/fourcircledbl": "\u24F8",
+ "/fourcircleinversesansserif": "\u278D",
+ "/fourcomma": "\u1F105",
+ "/fourdeva": "\u096A",
+ "/fourdotmark": "\u205B",
+ "/fourdotpunctuation": "\u2058",
+ "/fourfar": "\u06F4",
+ "/fourfifths": "\u2158",
+ "/fourgujarati": "\u0AEA",
+ "/fourgurmukhi": "\u0A6A",
+ "/fourhackarabic": "\u0664",
+ "/fourhangzhou": "\u3024",
+ "/fourideographiccircled": "\u3283",
+ "/fourideographicparen": "\u3223",
+ "/fourinferior": "\u2084",
+ "/fourksquare": "\u1F19E",
+ "/fourmonospace": "\uFF14",
+ "/fournumeratorbengali": "\u09F7",
+ "/fouroldstyle": "\uF734",
+ "/fourparen": "\u2477",
+ "/fourparenthesized": "\u2477",
+ "/fourperemspace": "\u2005",
+ "/fourperiod": "\u248B",
+ "/fourpersian": "\u06F4",
+ "/fourroman": "\u2173",
+ "/foursuperior": "\u2074",
+ "/fourteencircle": "\u246D",
+ "/fourteencircleblack": "\u24EE",
+ "/fourteenparen": "\u2481",
+ "/fourteenparenthesized": "\u2481",
+ "/fourteenperiod": "\u2495",
+ "/fourthai": "\u0E54",
+ "/fourthtonechinese": "\u02CB",
+ "/fparen": "\u24A1",
+ "/fparenthesized": "\u24A1",
+ "/fraction": "\u2044",
+ "/frameAnX": "\u1F5BE",
+ "/framePicture": "\u1F5BC",
+ "/frameTiles": "\u1F5BD",
+ "/franc": "\u20A3",
+ "/freesquare": "\u1F193",
+ "/frenchFries": "\u1F35F",
+ "/freversedepigraphic": "\uA7FB",
+ "/friedShrimp": "\u1F364",
+ "/frogFace": "\u1F438",
+ "/front-facingBabyChick": "\u1F425",
+ "/frown": "\u2322",
+ "/frowningFaceWithOpenMouth": "\u1F626",
+ "/frowningfacewhite": "\u2639",
+ "/fstroke": "\uA799",
+ "/fturned": "\u214E",
+ "/fuelpump": "\u26FD",
+ "/fullBlock": "\u2588",
+ "/fullMoon": "\u1F315",
+ "/fullMoonFace": "\u1F31D",
+ "/functionapplication": "\u2061",
+ "/funeralurn": "\u26B1",
+ "/fuse": "\u23DB",
+ "/fwd:A": "\uFF21",
+ "/fwd:B": "\uFF22",
+ "/fwd:C": "\uFF23",
+ "/fwd:D": "\uFF24",
+ "/fwd:E": "\uFF25",
+ "/fwd:F": "\uFF26",
+ "/fwd:G": "\uFF27",
+ "/fwd:H": "\uFF28",
+ "/fwd:I": "\uFF29",
+ "/fwd:J": "\uFF2A",
+ "/fwd:K": "\uFF2B",
+ "/fwd:L": "\uFF2C",
+ "/fwd:M": "\uFF2D",
+ "/fwd:N": "\uFF2E",
+ "/fwd:O": "\uFF2F",
+ "/fwd:P": "\uFF30",
+ "/fwd:Q": "\uFF31",
+ "/fwd:R": "\uFF32",
+ "/fwd:S": "\uFF33",
+ "/fwd:T": "\uFF34",
+ "/fwd:U": "\uFF35",
+ "/fwd:V": "\uFF36",
+ "/fwd:W": "\uFF37",
+ "/fwd:X": "\uFF38",
+ "/fwd:Y": "\uFF39",
+ "/fwd:Z": "\uFF3A",
+ "/fwd:a": "\uFF41",
+ "/fwd:ampersand": "\uFF06",
+ "/fwd:asciicircum": "\uFF3E",
+ "/fwd:asciitilde": "\uFF5E",
+ "/fwd:asterisk": "\uFF0A",
+ "/fwd:at": "\uFF20",
+ "/fwd:b": "\uFF42",
+ "/fwd:backslash": "\uFF3C",
+ "/fwd:bar": "\uFF5C",
+ "/fwd:braceleft": "\uFF5B",
+ "/fwd:braceright": "\uFF5D",
+ "/fwd:bracketleft": "\uFF3B",
+ "/fwd:bracketright": "\uFF3D",
+ "/fwd:brokenbar": "\uFFE4",
+ "/fwd:c": "\uFF43",
+ "/fwd:centsign": "\uFFE0",
+ "/fwd:colon": "\uFF1A",
+ "/fwd:comma": "\uFF0C",
+ "/fwd:d": "\uFF44",
+ "/fwd:dollar": "\uFF04",
+ "/fwd:e": "\uFF45",
+ "/fwd:eight": "\uFF18",
+ "/fwd:equal": "\uFF1D",
+ "/fwd:exclam": "\uFF01",
+ "/fwd:f": "\uFF46",
+ "/fwd:five": "\uFF15",
+ "/fwd:four": "\uFF14",
+ "/fwd:g": "\uFF47",
+ "/fwd:grave": "\uFF40",
+ "/fwd:greater": "\uFF1E",
+ "/fwd:h": "\uFF48",
+ "/fwd:hyphen": "\uFF0D",
+ "/fwd:i": "\uFF49",
+ "/fwd:j": "\uFF4A",
+ "/fwd:k": "\uFF4B",
+ "/fwd:l": "\uFF4C",
+ "/fwd:leftwhiteparenthesis": "\uFF5F",
+ "/fwd:less": "\uFF1C",
+ "/fwd:m": "\uFF4D",
+ "/fwd:macron": "\uFFE3",
+ "/fwd:n": "\uFF4E",
+ "/fwd:nine": "\uFF19",
+ "/fwd:notsign": "\uFFE2",
+ "/fwd:numbersign": "\uFF03",
+ "/fwd:o": "\uFF4F",
+ "/fwd:one": "\uFF11",
+ "/fwd:p": "\uFF50",
+ "/fwd:parenthesisleft": "\uFF08",
+ "/fwd:parenthesisright": "\uFF09",
+ "/fwd:percent": "\uFF05",
+ "/fwd:period": "\uFF0E",
+ "/fwd:plus": "\uFF0B",
+ "/fwd:poundsign": "\uFFE1",
+ "/fwd:q": "\uFF51",
+ "/fwd:question": "\uFF1F",
+ "/fwd:quotedbl": "\uFF02",
+ "/fwd:quotesingle": "\uFF07",
+ "/fwd:r": "\uFF52",
+ "/fwd:rightwhiteparenthesis": "\uFF60",
+ "/fwd:s": "\uFF53",
+ "/fwd:semicolon": "\uFF1B",
+ "/fwd:seven": "\uFF17",
+ "/fwd:six": "\uFF16",
+ "/fwd:slash": "\uFF0F",
+ "/fwd:t": "\uFF54",
+ "/fwd:three": "\uFF13",
+ "/fwd:two": "\uFF12",
+ "/fwd:u": "\uFF55",
+ "/fwd:underscore": "\uFF3F",
+ "/fwd:v": "\uFF56",
+ "/fwd:w": "\uFF57",
+ "/fwd:wonsign": "\uFFE6",
+ "/fwd:x": "\uFF58",
+ "/fwd:y": "\uFF59",
+ "/fwd:yensign": "\uFFE5",
+ "/fwd:z": "\uFF5A",
+ "/fwd:zero": "\uFF10",
+ "/g": "\u0067",
+ "/gabengali": "\u0997",
+ "/gacute": "\u01F5",
+ "/gadeva": "\u0917",
+ "/gaf": "\u06AF",
+ "/gaf.fina": "\uFB93",
+ "/gaf.init": "\uFB94",
+ "/gaf.isol": "\uFB92",
+ "/gaf.medi": "\uFB95",
+ "/gafarabic": "\u06AF",
+ "/gaffinalarabic": "\uFB93",
+ "/gafinitialarabic": "\uFB94",
+ "/gafmedialarabic": "\uFB95",
+ "/gafring": "\u06B0",
+ "/gafthreedotsabove": "\u06B4",
+ "/gaftwodotsbelow": "\u06B2",
+ "/gagujarati": "\u0A97",
+ "/gagurmukhi": "\u0A17",
+ "/gahiragana": "\u304C",
+ "/gakatakana": "\u30AC",
+ "/galsquare": "\u33FF",
+ "/gameDie": "\u1F3B2",
+ "/gamma": "\u03B3",
+ "/gammadblstruck": "\u213D",
+ "/gammalatinsmall": "\u0263",
+ "/gammasuperior": "\u02E0",
+ "/gammasupmod": "\u02E0",
+ "/gamurda": "\uA993",
+ "/gangiacoptic": "\u03EB",
+ "/ganmasquare": "\u330F",
+ "/garonsquare": "\u330E",
+ "/gbfullwidth": "\u3387",
+ "/gbopomofo": "\u310D",
+ "/gbreve": "\u011F",
+ "/gcaron": "\u01E7",
+ "/gcedilla": "\u0123",
+ "/gcircle": "\u24D6",
+ "/gcircumflex": "\u011D",
+ "/gcommaaccent": "\u0123",
+ "/gdot": "\u0121",
+ "/gdotaccent": "\u0121",
+ "/gear": "\u2699",
+ "/gearhles": "\u26EE",
+ "/gearouthub": "\u26ED",
+ "/gecyr": "\u0433",
+ "/gecyrillic": "\u0433",
+ "/gehiragana": "\u3052",
+ "/gehookcyr": "\u0495",
+ "/gehookstrokecyr": "\u04FB",
+ "/gekatakana": "\u30B2",
+ "/gemStone": "\u1F48E",
+ "/gemini": "\u264A",
+ "/geometricallyequal": "\u2251",
+ "/geometricallyequivalent": "\u224E",
+ "/geometricproportion": "\u223A",
+ "/geresh:hb": "\u05F3",
+ "/gereshMuqdam:hb": "\u059D",
+ "/gereshaccenthebrew": "\u059C",
+ "/gereshhebrew": "\u05F3",
+ "/gereshmuqdamhebrew": "\u059D",
+ "/germandbls": "\u00DF",
+ "/germanpenny": "\u20B0",
+ "/gershayim:hb": "\u05F4",
+ "/gershayimaccenthebrew": "\u059E",
+ "/gershayimhebrew": "\u05F4",
+ "/gestrokecyr": "\u0493",
+ "/getailcyr": "\u04F7",
+ "/getamark": "\u3013",
+ "/geupcyr": "\u0491",
+ "/ghabengali": "\u0998",
+ "/ghadarmenian": "\u0572",
+ "/ghadeva": "\u0918",
+ "/ghagujarati": "\u0A98",
+ "/ghagurmukhi": "\u0A18",
+ "/ghain": "\u063A",
+ "/ghain.fina": "\uFECE",
+ "/ghain.init": "\uFECF",
+ "/ghain.init_alefmaksura.fina": "\uFCF9",
+ "/ghain.init_jeem.fina": "\uFC2B",
+ "/ghain.init_jeem.medi": "\uFCBC",
+ "/ghain.init_meem.fina": "\uFC2C",
+ "/ghain.init_meem.medi": "\uFCBD",
+ "/ghain.init_yeh.fina": "\uFCFA",
+ "/ghain.isol": "\uFECD",
+ "/ghain.medi": "\uFED0",
+ "/ghain.medi_alefmaksura.fina": "\uFD15",
+ "/ghain.medi_meem.medi_alefmaksura.fina": "\uFD7B",
+ "/ghain.medi_meem.medi_meem.fina": "\uFD79",
+ "/ghain.medi_meem.medi_yeh.fina": "\uFD7A",
+ "/ghain.medi_yeh.fina": "\uFD16",
+ "/ghainarabic": "\u063A",
+ "/ghaindotbelow": "\u06FC",
+ "/ghainfinalarabic": "\uFECE",
+ "/ghaininitialarabic": "\uFECF",
+ "/ghainmedialarabic": "\uFED0",
+ "/ghemiddlehookcyrillic": "\u0495",
+ "/ghestrokecyrillic": "\u0493",
+ "/gheupturncyrillic": "\u0491",
+ "/ghhadeva": "\u095A",
+ "/ghhagurmukhi": "\u0A5A",
+ "/ghook": "\u0260",
+ "/ghost": "\u1F47B",
+ "/ghzfullwidth": "\u3393",
+ "/ghzsquare": "\u3393",
+ "/gigasquare": "\u3310",
+ "/gihiragana": "\u304E",
+ "/gikatakana": "\u30AE",
+ "/gimarmenian": "\u0563",
+ "/gimel": "\u05D2",
+ "/gimel:hb": "\u05D2",
+ "/gimeldagesh": "\uFB32",
+ "/gimeldageshhebrew": "\uFB32",
+ "/gimelhebrew": "\u05D2",
+ "/gimelwithdagesh:hb": "\uFB32",
+ "/giniisquare": "\u3311",
+ "/ginsularturned": "\uA77F",
+ "/girl": "\u1F467",
+ "/girls": "\u1F6CA",
+ "/girudaasquare": "\u3313",
+ "/gjecyr": "\u0453",
+ "/gjecyrillic": "\u0453",
+ "/globeMeridians": "\u1F310",
+ "/glottalinvertedstroke": "\u01BE",
+ "/glottalstop": "\u0294",
+ "/glottalstopinverted": "\u0296",
+ "/glottalstopmod": "\u02C0",
+ "/glottalstopreversed": "\u0295",
+ "/glottalstopreversedmod": "\u02C1",
+ "/glottalstopreversedsuperior": "\u02E4",
+ "/glottalstopstroke": "\u02A1",
+ "/glottalstopstrokereversed": "\u02A2",
+ "/glottalstopsupreversedmod": "\u02E4",
+ "/glowingStar": "\u1F31F",
+ "/gmacron": "\u1E21",
+ "/gmonospace": "\uFF47",
+ "/gmtr:diamondblack": "\u25C6",
+ "/gmtr:diamondwhite": "\u25C7",
+ "/gnrl:hyphen": "\u2010",
+ "/goat": "\u1F410",
+ "/gobliquestroke": "\uA7A1",
+ "/gohiragana": "\u3054",
+ "/gokatakana": "\u30B4",
+ "/golfer": "\u1F3CC",
+ "/gpafullwidth": "\u33AC",
+ "/gparen": "\u24A2",
+ "/gparenthesized": "\u24A2",
+ "/gpasquare": "\u33AC",
+ "/gr:acute": "\u1FFD",
+ "/gr:grave": "\u1FEF",
+ "/gr:question": "\u037E",
+ "/gr:tilde": "\u1FC0",
+ "/gradient": "\u2207",
+ "/graduationCap": "\u1F393",
+ "/grapes": "\u1F347",
+ "/grave": "\u0060",
+ "/gravebelowcmb": "\u0316",
+ "/gravecmb": "\u0300",
+ "/gravecomb": "\u0300",
+ "/gravedblmiddlemod": "\u02F5",
+ "/gravedeva": "\u0953",
+ "/gravelowmod": "\u02CE",
+ "/gravemiddlemod": "\u02F4",
+ "/gravemod": "\u02CB",
+ "/gravemonospace": "\uFF40",
+ "/gravetonecmb": "\u0340",
+ "/greater": "\u003E",
+ "/greaterbutnotequal": "\u2269",
+ "/greaterbutnotequivalent": "\u22E7",
+ "/greaterdot": "\u22D7",
+ "/greaterequal": "\u2265",
+ "/greaterequalorless": "\u22DB",
+ "/greatermonospace": "\uFF1E",
+ "/greaterorequivalent": "\u2273",
+ "/greaterorless": "\u2277",
+ "/greateroverequal": "\u2267",
+ "/greatersmall": "\uFE65",
+ "/greenApple": "\u1F34F",
+ "/greenBook": "\u1F4D7",
+ "/greenHeart": "\u1F49A",
+ "/grimacingFace": "\u1F62C",
+ "/grinningCatFaceWithSmilingEyes": "\u1F638",
+ "/grinningFace": "\u1F600",
+ "/grinningFaceWithSmilingEyes": "\u1F601",
+ "/growingHeart": "\u1F497",
+ "/gscript": "\u0261",
+ "/gstroke": "\u01E5",
+ "/guarani": "\u20B2",
+ "/guardsman": "\u1F482",
+ "/gueh": "\u06B3",
+ "/gueh.fina": "\uFB97",
+ "/gueh.init": "\uFB98",
+ "/gueh.isol": "\uFB96",
+ "/gueh.medi": "\uFB99",
+ "/guhiragana": "\u3050",
+ "/guillemetleft": "\u00AB",
+ "/guillemetright": "\u00BB",
+ "/guillemotleft": "\u00AB",
+ "/guillemotright": "\u00BB",
+ "/guilsinglleft": "\u2039",
+ "/guilsinglright": "\u203A",
+ "/guitar": "\u1F3B8",
+ "/gujr:a": "\u0A85",
+ "/gujr:aa": "\u0A86",
+ "/gujr:aasign": "\u0ABE",
+ "/gujr:abbreviation": "\u0AF0",
+ "/gujr:ai": "\u0A90",
+ "/gujr:aisign": "\u0AC8",
+ "/gujr:anusvara": "\u0A82",
+ "/gujr:au": "\u0A94",
+ "/gujr:ausign": "\u0ACC",
+ "/gujr:avagraha": "\u0ABD",
+ "/gujr:ba": "\u0AAC",
+ "/gujr:bha": "\u0AAD",
+ "/gujr:binducandra": "\u0A81",
+ "/gujr:ca": "\u0A9A",
+ "/gujr:cha": "\u0A9B",
+ "/gujr:circlenuktaabove": "\u0AFE",
+ "/gujr:da": "\u0AA6",
+ "/gujr:dda": "\u0AA1",
+ "/gujr:ddha": "\u0AA2",
+ "/gujr:dha": "\u0AA7",
+ "/gujr:e": "\u0A8F",
+ "/gujr:ecandra": "\u0A8D",
+ "/gujr:eight": "\u0AEE",
+ "/gujr:esign": "\u0AC7",
+ "/gujr:esigncandra": "\u0AC5",
+ "/gujr:five": "\u0AEB",
+ "/gujr:four": "\u0AEA",
+ "/gujr:ga": "\u0A97",
+ "/gujr:gha": "\u0A98",
+ "/gujr:ha": "\u0AB9",
+ "/gujr:i": "\u0A87",
+ "/gujr:ii": "\u0A88",
+ "/gujr:iisign": "\u0AC0",
+ "/gujr:isign": "\u0ABF",
+ "/gujr:ja": "\u0A9C",
+ "/gujr:jha": "\u0A9D",
+ "/gujr:ka": "\u0A95",
+ "/gujr:kha": "\u0A96",
+ "/gujr:la": "\u0AB2",
+ "/gujr:lla": "\u0AB3",
+ "/gujr:llvocal": "\u0AE1",
+ "/gujr:llvocalsign": "\u0AE3",
+ "/gujr:lvocal": "\u0A8C",
+ "/gujr:lvocalsign": "\u0AE2",
+ "/gujr:ma": "\u0AAE",
+ "/gujr:maddah": "\u0AFC",
+ "/gujr:na": "\u0AA8",
+ "/gujr:nga": "\u0A99",
+ "/gujr:nine": "\u0AEF",
+ "/gujr:nna": "\u0AA3",
+ "/gujr:nukta": "\u0ABC",
+ "/gujr:nya": "\u0A9E",
+ "/gujr:o": "\u0A93",
+ "/gujr:ocandra": "\u0A91",
+ "/gujr:om": "\u0AD0",
+ "/gujr:one": "\u0AE7",
+ "/gujr:osign": "\u0ACB",
+ "/gujr:osigncandra": "\u0AC9",
+ "/gujr:pa": "\u0AAA",
+ "/gujr:pha": "\u0AAB",
+ "/gujr:ra": "\u0AB0",
+ "/gujr:rrvocal": "\u0AE0",
+ "/gujr:rrvocalsign": "\u0AC4",
+ "/gujr:rupee": "\u0AF1",
+ "/gujr:rvocal": "\u0A8B",
+ "/gujr:rvocalsign": "\u0AC3",
+ "/gujr:sa": "\u0AB8",
+ "/gujr:seven": "\u0AED",
+ "/gujr:sha": "\u0AB6",
+ "/gujr:shadda": "\u0AFB",
+ "/gujr:six": "\u0AEC",
+ "/gujr:ssa": "\u0AB7",
+ "/gujr:sukun": "\u0AFA",
+ "/gujr:ta": "\u0AA4",
+ "/gujr:tha": "\u0AA5",
+ "/gujr:three": "\u0AE9",
+ "/gujr:three-dotnuktaabove": "\u0AFD",
+ "/gujr:tta": "\u0A9F",
+ "/gujr:ttha": "\u0AA0",
+ "/gujr:two": "\u0AE8",
+ "/gujr:two-circlenuktaabove": "\u0AFF",
+ "/gujr:u": "\u0A89",
+ "/gujr:usign": "\u0AC1",
+ "/gujr:uu": "\u0A8A",
+ "/gujr:uusign": "\u0AC2",
+ "/gujr:va": "\u0AB5",
+ "/gujr:virama": "\u0ACD",
+ "/gujr:visarga": "\u0A83",
+ "/gujr:ya": "\u0AAF",
+ "/gujr:zero": "\u0AE6",
+ "/gujr:zha": "\u0AF9",
+ "/gukatakana": "\u30B0",
+ "/guramusquare": "\u3318",
+ "/guramutonsquare": "\u3319",
+ "/guru:a": "\u0A05",
+ "/guru:aa": "\u0A06",
+ "/guru:aasign": "\u0A3E",
+ "/guru:adakbindisign": "\u0A01",
+ "/guru:addak": "\u0A71",
+ "/guru:ai": "\u0A10",
+ "/guru:aisign": "\u0A48",
+ "/guru:au": "\u0A14",
+ "/guru:ausign": "\u0A4C",
+ "/guru:ba": "\u0A2C",
+ "/guru:bha": "\u0A2D",
+ "/guru:bindisign": "\u0A02",
+ "/guru:ca": "\u0A1A",
+ "/guru:cha": "\u0A1B",
+ "/guru:da": "\u0A26",
+ "/guru:dda": "\u0A21",
+ "/guru:ddha": "\u0A22",
+ "/guru:dha": "\u0A27",
+ "/guru:ee": "\u0A0F",
+ "/guru:eesign": "\u0A47",
+ "/guru:eight": "\u0A6E",
+ "/guru:ekonkar": "\u0A74",
+ "/guru:fa": "\u0A5E",
+ "/guru:five": "\u0A6B",
+ "/guru:four": "\u0A6A",
+ "/guru:ga": "\u0A17",
+ "/guru:gha": "\u0A18",
+ "/guru:ghha": "\u0A5A",
+ "/guru:ha": "\u0A39",
+ "/guru:i": "\u0A07",
+ "/guru:ii": "\u0A08",
+ "/guru:iisign": "\u0A40",
+ "/guru:iri": "\u0A72",
+ "/guru:isign": "\u0A3F",
+ "/guru:ja": "\u0A1C",
+ "/guru:jha": "\u0A1D",
+ "/guru:ka": "\u0A15",
+ "/guru:kha": "\u0A16",
+ "/guru:khha": "\u0A59",
+ "/guru:la": "\u0A32",
+ "/guru:lla": "\u0A33",
+ "/guru:ma": "\u0A2E",
+ "/guru:na": "\u0A28",
+ "/guru:nga": "\u0A19",
+ "/guru:nine": "\u0A6F",
+ "/guru:nna": "\u0A23",
+ "/guru:nukta": "\u0A3C",
+ "/guru:nya": "\u0A1E",
+ "/guru:one": "\u0A67",
+ "/guru:oo": "\u0A13",
+ "/guru:oosign": "\u0A4B",
+ "/guru:pa": "\u0A2A",
+ "/guru:pha": "\u0A2B",
+ "/guru:ra": "\u0A30",
+ "/guru:rra": "\u0A5C",
+ "/guru:sa": "\u0A38",
+ "/guru:seven": "\u0A6D",
+ "/guru:sha": "\u0A36",
+ "/guru:six": "\u0A6C",
+ "/guru:ta": "\u0A24",
+ "/guru:tha": "\u0A25",
+ "/guru:three": "\u0A69",
+ "/guru:tippi": "\u0A70",
+ "/guru:tta": "\u0A1F",
+ "/guru:ttha": "\u0A20",
+ "/guru:two": "\u0A68",
+ "/guru:u": "\u0A09",
+ "/guru:udaatsign": "\u0A51",
+ "/guru:ura": "\u0A73",
+ "/guru:usign": "\u0A41",
+ "/guru:uu": "\u0A0A",
+ "/guru:uusign": "\u0A42",
+ "/guru:va": "\u0A35",
+ "/guru:virama": "\u0A4D",
+ "/guru:visarga": "\u0A03",
+ "/guru:ya": "\u0A2F",
+ "/guru:yakashsign": "\u0A75",
+ "/guru:za": "\u0A5B",
+ "/guru:zero": "\u0A66",
+ "/gyfullwidth": "\u33C9",
+ "/gysquare": "\u33C9",
+ "/h": "\u0068",
+ "/h.inferior": "\u2095",
+ "/haabkhasiancyrillic": "\u04A9",
+ "/haabkhcyr": "\u04A9",
+ "/haaltonearabic": "\u06C1",
+ "/habengali": "\u09B9",
+ "/hacirclekatakana": "\u32E9",
+ "/hacyr": "\u0445",
+ "/hadescendercyrillic": "\u04B3",
+ "/hadeva": "\u0939",
+ "/hafullwidth": "\u33CA",
+ "/hagujarati": "\u0AB9",
+ "/hagurmukhi": "\u0A39",
+ "/hah": "\u062D",
+ "/hah.fina": "\uFEA2",
+ "/hah.init": "\uFEA3",
+ "/hah.init_alefmaksura.fina": "\uFCFF",
+ "/hah.init_jeem.fina": "\uFC17",
+ "/hah.init_jeem.medi": "\uFCA9",
+ "/hah.init_meem.fina": "\uFC18",
+ "/hah.init_meem.medi": "\uFCAA",
+ "/hah.init_yeh.fina": "\uFD00",
+ "/hah.isol": "\uFEA1",
+ "/hah.medi": "\uFEA4",
+ "/hah.medi_alefmaksura.fina": "\uFD1B",
+ "/hah.medi_jeem.medi_yeh.fina": "\uFDBF",
+ "/hah.medi_meem.medi_alefmaksura.fina": "\uFD5B",
+ "/hah.medi_meem.medi_yeh.fina": "\uFD5A",
+ "/hah.medi_yeh.fina": "\uFD1C",
+ "/hahDigitFourBelow": "\u077C",
+ "/hahSmallTahAbove": "\u0772",
+ "/hahSmallTahBelow": "\u076E",
+ "/hahSmallTahTwoDots": "\u076F",
+ "/hahThreeDotsUpBelow": "\u0758",
+ "/hahTwoDotsAbove": "\u0757",
+ "/haharabic": "\u062D",
+ "/hahfinalarabic": "\uFEA2",
+ "/hahhamza": "\u0681",
+ "/hahinitialarabic": "\uFEA3",
+ "/hahiragana": "\u306F",
+ "/hahmedialarabic": "\uFEA4",
+ "/hahookcyr": "\u04FD",
+ "/hahthreedotsabove": "\u0685",
+ "/hahtwodotsvertical": "\u0682",
+ "/haircut": "\u1F487",
+ "/hairspace": "\u200A",
+ "/haitusquare": "\u332A",
+ "/hakatakana": "\u30CF",
+ "/hakatakanahalfwidth": "\uFF8A",
+ "/halantgurmukhi": "\u0A4D",
+ "/halfcircleleftblack": "\u25D6",
+ "/halfcirclerightblack": "\u25D7",
+ "/hamburger": "\u1F354",
+ "/hammer": "\u1F528",
+ "/hammerAndWrench": "\u1F6E0",
+ "/hammerpick": "\u2692",
+ "/hammersickle": "\u262D",
+ "/hamsterFace": "\u1F439",
+ "/hamza": "\u0621",
+ "/hamzaIsol": "\uFE80",
+ "/hamzaabove": "\u0654",
+ "/hamzaarabic": "\u0621",
+ "/hamzabelow": "\u0655",
+ "/hamzadammaarabic": "\u0621",
+ "/hamzadammatanarabic": "\u0621",
+ "/hamzafathaarabic": "\u0621",
+ "/hamzafathatanarabic": "\u0621",
+ "/hamzalowarabic": "\u0621",
+ "/hamzalowkasraarabic": "\u0621",
+ "/hamzalowkasratanarabic": "\u0621",
+ "/hamzasukunarabic": "\u0621",
+ "/handbag": "\u1F45C",
+ "/handtailfishhookturned": "\u02AF",
+ "/hangulchieuchaparen": "\u3217",
+ "/hangulchieuchparen": "\u3209",
+ "/hangulcieucaparen": "\u3216",
+ "/hangulcieucparen": "\u3208",
+ "/hangulcieucuparen": "\u321C",
+ "/hanguldottonemarkdbl": "\u302F",
+ "/hangulfiller": "\u3164",
+ "/hangulhieuhaparen": "\u321B",
+ "/hangulhieuhparen": "\u320D",
+ "/hangulieungaparen": "\u3215",
+ "/hangulieungparen": "\u3207",
+ "/hangulkhieukhaparen": "\u3218",
+ "/hangulkhieukhparen": "\u320A",
+ "/hangulkiyeokaparen": "\u320E",
+ "/hangulkiyeokparen": "\u3200",
+ "/hangulmieumaparen": "\u3212",
+ "/hangulmieumparen": "\u3204",
+ "/hangulnieunaparen": "\u320F",
+ "/hangulnieunparen": "\u3201",
+ "/hangulphieuphaparen": "\u321A",
+ "/hangulphieuphparen": "\u320C",
+ "/hangulpieupaparen": "\u3213",
+ "/hangulpieupparen": "\u3205",
+ "/hangulrieulaparen": "\u3211",
+ "/hangulrieulparen": "\u3203",
+ "/hangulsingledottonemark": "\u302E",
+ "/hangulsiosaparen": "\u3214",
+ "/hangulsiosparen": "\u3206",
+ "/hangulthieuthaparen": "\u3219",
+ "/hangulthieuthparen": "\u320B",
+ "/hangultikeutaparen": "\u3210",
+ "/hangultikeutparen": "\u3202",
+ "/happyPersonRaisingOneHand": "\u1F64B",
+ "/hardDisk": "\u1F5B4",
+ "/hardcyr": "\u044A",
+ "/hardsigncyrillic": "\u044A",
+ "/harpoondownbarbleft": "\u21C3",
+ "/harpoondownbarbright": "\u21C2",
+ "/harpoonleftbarbdown": "\u21BD",
+ "/harpoonleftbarbup": "\u21BC",
+ "/harpoonrightbarbdown": "\u21C1",
+ "/harpoonrightbarbup": "\u21C0",
+ "/harpoonupbarbleft": "\u21BF",
+ "/harpoonupbarbright": "\u21BE",
+ "/hasquare": "\u33CA",
+ "/hastrokecyr": "\u04FF",
+ "/hatafPatah:hb": "\u05B2",
+ "/hatafQamats:hb": "\u05B3",
+ "/hatafSegol:hb": "\u05B1",
+ "/hatafpatah": "\u05B2",
+ "/hatafpatah16": "\u05B2",
+ "/hatafpatah23": "\u05B2",
+ "/hatafpatah2f": "\u05B2",
+ "/hatafpatahhebrew": "\u05B2",
+ "/hatafpatahnarrowhebrew": "\u05B2",
+ "/hatafpatahquarterhebrew": "\u05B2",
+ "/hatafpatahwidehebrew": "\u05B2",
+ "/hatafqamats": "\u05B3",
+ "/hatafqamats1b": "\u05B3",
+ "/hatafqamats28": "\u05B3",
+ "/hatafqamats34": "\u05B3",
+ "/hatafqamatshebrew": "\u05B3",
+ "/hatafqamatsnarrowhebrew": "\u05B3",
+ "/hatafqamatsquarterhebrew": "\u05B3",
+ "/hatafqamatswidehebrew": "\u05B3",
+ "/hatafsegol": "\u05B1",
+ "/hatafsegol17": "\u05B1",
+ "/hatafsegol24": "\u05B1",
+ "/hatafsegol30": "\u05B1",
+ "/hatafsegolhebrew": "\u05B1",
+ "/hatafsegolnarrowhebrew": "\u05B1",
+ "/hatafsegolquarterhebrew": "\u05B1",
+ "/hatafsegolwidehebrew": "\u05B1",
+ "/hatchingChick": "\u1F423",
+ "/haveideographiccircled": "\u3292",
+ "/haveideographicparen": "\u3232",
+ "/hbar": "\u0127",
+ "/hbopomofo": "\u310F",
+ "/hbrevebelow": "\u1E2B",
+ "/hcaron": "\u021F",
+ "/hcedilla": "\u1E29",
+ "/hcircle": "\u24D7",
+ "/hcircumflex": "\u0125",
+ "/hcsquare": "\u1F1A6",
+ "/hdescender": "\u2C68",
+ "/hdieresis": "\u1E27",
+ "/hdot": "\u1E23",
+ "/hdotaccent": "\u1E23",
+ "/hdotbelow": "\u1E25",
+ "/hdrsquare": "\u1F1A7",
+ "/he": "\u05D4",
+ "/he:hb": "\u05D4",
+ "/headphone": "\u1F3A7",
+ "/headstonegraveyard": "\u26FC",
+ "/hearNoEvilMonkey": "\u1F649",
+ "/heart": "\u2665",
+ "/heartArrow": "\u1F498",
+ "/heartDecoration": "\u1F49F",
+ "/heartRibbon": "\u1F49D",
+ "/heartTipOnTheLeft": "\u1F394",
+ "/heartblack": "\u2665",
+ "/heartsuitblack": "\u2665",
+ "/heartsuitwhite": "\u2661",
+ "/heartwhite": "\u2661",
+ "/heavyDollarSign": "\u1F4B2",
+ "/heavyLatinCross": "\u1F547",
+ "/heavydbldashhorz": "\u254D",
+ "/heavydbldashvert": "\u254F",
+ "/heavydn": "\u257B",
+ "/heavydnhorz": "\u2533",
+ "/heavydnleft": "\u2513",
+ "/heavydnright": "\u250F",
+ "/heavyhorz": "\u2501",
+ "/heavyleft": "\u2578",
+ "/heavyleftlightright": "\u257E",
+ "/heavyquaddashhorz": "\u2509",
+ "/heavyquaddashvert": "\u250B",
+ "/heavyright": "\u257A",
+ "/heavytrpldashhorz": "\u2505",
+ "/heavytrpldashvert": "\u2507",
+ "/heavyup": "\u2579",
+ "/heavyuphorz": "\u253B",
+ "/heavyupleft": "\u251B",
+ "/heavyuplightdn": "\u257F",
+ "/heavyupright": "\u2517",
+ "/heavyvert": "\u2503",
+ "/heavyverthorz": "\u254B",
+ "/heavyvertleft": "\u252B",
+ "/heavyvertright": "\u2523",
+ "/hecirclekatakana": "\u32EC",
+ "/hedagesh": "\uFB34",
+ "/hedageshhebrew": "\uFB34",
+ "/hedinterlacedpentagramleft": "\u26E6",
+ "/hedinterlacedpentagramright": "\u26E5",
+ "/heh": "\u0647",
+ "/heh.fina": "\uFEEA",
+ "/heh.init": "\uFEEB",
+ "/heh.init_alefmaksura.fina": "\uFC53",
+ "/heh.init_jeem.fina": "\uFC51",
+ "/heh.init_jeem.medi": "\uFCD7",
+ "/heh.init_meem.fina": "\uFC52",
+ "/heh.init_meem.medi": "\uFCD8",
+ "/heh.init_meem.medi_jeem.medi": "\uFD93",
+ "/heh.init_meem.medi_meem.medi": "\uFD94",
+ "/heh.init_superscriptalef.medi": "\uFCD9",
+ "/heh.init_yeh.fina": "\uFC54",
+ "/heh.isol": "\uFEE9",
+ "/heh.medi": "\uFEEC",
+ "/hehaltonearabic": "\u06C1",
+ "/heharabic": "\u0647",
+ "/hehdoachashmee": "\u06BE",
+ "/hehdoachashmee.fina": "\uFBAB",
+ "/hehdoachashmee.init": "\uFBAC",
+ "/hehdoachashmee.isol": "\uFBAA",
+ "/hehdoachashmee.medi": "\uFBAD",
+ "/hehebrew": "\u05D4",
+ "/hehfinalaltonearabic": "\uFBA7",
+ "/hehfinalalttwoarabic": "\uFEEA",
+ "/hehfinalarabic": "\uFEEA",
+ "/hehgoal": "\u06C1",
+ "/hehgoal.fina": "\uFBA7",
+ "/hehgoal.init": "\uFBA8",
+ "/hehgoal.isol": "\uFBA6",
+ "/hehgoal.medi": "\uFBA9",
+ "/hehgoalhamza": "\u06C2",
+ "/hehhamzaabovefinalarabic": "\uFBA5",
+ "/hehhamzaaboveisolatedarabic": "\uFBA4",
+ "/hehinitialaltonearabic": "\uFBA8",
+ "/hehinitialarabic": "\uFEEB",
+ "/hehinvertedV": "\u06FF",
+ "/hehiragana": "\u3078",
+ "/hehmedialaltonearabic": "\uFBA9",
+ "/hehmedialarabic": "\uFEEC",
+ "/hehyeh": "\u06C0",
+ "/hehyeh.fina": "\uFBA5",
+ "/hehyeh.isol": "\uFBA4",
+ "/heiseierasquare": "\u337B",
+ "/hekatakana": "\u30D8",
+ "/hekatakanahalfwidth": "\uFF8D",
+ "/hekutaarusquare": "\u3336",
+ "/helicopter": "\u1F681",
+ "/helm": "\u2388",
+ "/helmetcrosswhite": "\u26D1",
+ "/heng": "\uA727",
+ "/henghook": "\u0267",
+ "/herb": "\u1F33F",
+ "/hermitianconjugatematrix": "\u22B9",
+ "/herutusquare": "\u3339",
+ "/het": "\u05D7",
+ "/het:hb": "\u05D7",
+ "/heta": "\u0371",
+ "/hethebrew": "\u05D7",
+ "/hewide:hb": "\uFB23",
+ "/hewithmapiq:hb": "\uFB34",
+ "/hfishhookturned": "\u02AE",
+ "/hhalf": "\u2C76",
+ "/hhook": "\u0266",
+ "/hhooksuperior": "\u02B1",
+ "/hhooksupmod": "\u02B1",
+ "/hi-ressquare": "\u1F1A8",
+ "/hibiscus": "\u1F33A",
+ "/hicirclekatakana": "\u32EA",
+ "/hieuhacirclekorean": "\u327B",
+ "/hieuhaparenkorean": "\u321B",
+ "/hieuhcirclekorean": "\u326D",
+ "/hieuhkorean": "\u314E",
+ "/hieuhparenkorean": "\u320D",
+ "/high-heeledShoe": "\u1F460",
+ "/highBrightness": "\u1F506",
+ "/highSpeedTrain": "\u1F684",
+ "/highSpeedTrainWithBulletNose": "\u1F685",
+ "/highhamza": "\u0674",
+ "/highideographiccircled": "\u32A4",
+ "/highvoltage": "\u26A1",
+ "/hihiragana": "\u3072",
+ "/hikatakana": "\u30D2",
+ "/hikatakanahalfwidth": "\uFF8B",
+ "/hira:a": "\u3042",
+ "/hira:asmall": "\u3041",
+ "/hira:ba": "\u3070",
+ "/hira:be": "\u3079",
+ "/hira:bi": "\u3073",
+ "/hira:bo": "\u307C",
+ "/hira:bu": "\u3076",
+ "/hira:da": "\u3060",
+ "/hira:de": "\u3067",
+ "/hira:di": "\u3062",
+ "/hira:digraphyori": "\u309F",
+ "/hira:do": "\u3069",
+ "/hira:du": "\u3065",
+ "/hira:e": "\u3048",
+ "/hira:esmall": "\u3047",
+ "/hira:ga": "\u304C",
+ "/hira:ge": "\u3052",
+ "/hira:gi": "\u304E",
+ "/hira:go": "\u3054",
+ "/hira:gu": "\u3050",
+ "/hira:ha": "\u306F",
+ "/hira:he": "\u3078",
+ "/hira:hi": "\u3072",
+ "/hira:ho": "\u307B",
+ "/hira:hu": "\u3075",
+ "/hira:i": "\u3044",
+ "/hira:ismall": "\u3043",
+ "/hira:iterationhiragana": "\u309D",
+ "/hira:ka": "\u304B",
+ "/hira:kasmall": "\u3095",
+ "/hira:ke": "\u3051",
+ "/hira:kesmall": "\u3096",
+ "/hira:ki": "\u304D",
+ "/hira:ko": "\u3053",
+ "/hira:ku": "\u304F",
+ "/hira:ma": "\u307E",
+ "/hira:me": "\u3081",
+ "/hira:mi": "\u307F",
+ "/hira:mo": "\u3082",
+ "/hira:mu": "\u3080",
+ "/hira:n": "\u3093",
+ "/hira:na": "\u306A",
+ "/hira:ne": "\u306D",
+ "/hira:ni": "\u306B",
+ "/hira:no": "\u306E",
+ "/hira:nu": "\u306C",
+ "/hira:o": "\u304A",
+ "/hira:osmall": "\u3049",
+ "/hira:pa": "\u3071",
+ "/hira:pe": "\u307A",
+ "/hira:pi": "\u3074",
+ "/hira:po": "\u307D",
+ "/hira:pu": "\u3077",
+ "/hira:ra": "\u3089",
+ "/hira:re": "\u308C",
+ "/hira:ri": "\u308A",
+ "/hira:ro": "\u308D",
+ "/hira:ru": "\u308B",
+ "/hira:sa": "\u3055",
+ "/hira:se": "\u305B",
+ "/hira:semivoicedmarkkana": "\u309C",
+ "/hira:semivoicedmarkkanacmb": "\u309A",
+ "/hira:si": "\u3057",
+ "/hira:so": "\u305D",
+ "/hira:su": "\u3059",
+ "/hira:ta": "\u305F",
+ "/hira:te": "\u3066",
+ "/hira:ti": "\u3061",
+ "/hira:to": "\u3068",
+ "/hira:tu": "\u3064",
+ "/hira:tusmall": "\u3063",
+ "/hira:u": "\u3046",
+ "/hira:usmall": "\u3045",
+ "/hira:voicediterationhiragana": "\u309E",
+ "/hira:voicedmarkkana": "\u309B",
+ "/hira:voicedmarkkanacmb": "\u3099",
+ "/hira:vu": "\u3094",
+ "/hira:wa": "\u308F",
+ "/hira:wasmall": "\u308E",
+ "/hira:we": "\u3091",
+ "/hira:wi": "\u3090",
+ "/hira:wo": "\u3092",
+ "/hira:ya": "\u3084",
+ "/hira:yasmall": "\u3083",
+ "/hira:yo": "\u3088",
+ "/hira:yosmall": "\u3087",
+ "/hira:yu": "\u3086",
+ "/hira:yusmall": "\u3085",
+ "/hira:za": "\u3056",
+ "/hira:ze": "\u305C",
+ "/hira:zi": "\u3058",
+ "/hira:zo": "\u305E",
+ "/hira:zu": "\u305A",
+ "/hiriq": "\u05B4",
+ "/hiriq14": "\u05B4",
+ "/hiriq21": "\u05B4",
+ "/hiriq2d": "\u05B4",
+ "/hiriq:hb": "\u05B4",
+ "/hiriqhebrew": "\u05B4",
+ "/hiriqnarrowhebrew": "\u05B4",
+ "/hiriqquarterhebrew": "\u05B4",
+ "/hiriqwidehebrew": "\u05B4",
+ "/historicsite": "\u26EC",
+ "/hlinebelow": "\u1E96",
+ "/hmonospace": "\uFF48",
+ "/hoarmenian": "\u0570",
+ "/hocho": "\u1F52A",
+ "/hocirclekatakana": "\u32ED",
+ "/hohipthai": "\u0E2B",
+ "/hohiragana": "\u307B",
+ "/hokatakana": "\u30DB",
+ "/hokatakanahalfwidth": "\uFF8E",
+ "/holam": "\u05B9",
+ "/holam19": "\u05B9",
+ "/holam26": "\u05B9",
+ "/holam32": "\u05B9",
+ "/holam:hb": "\u05B9",
+ "/holamHaser:hb": "\u05BA",
+ "/holamhebrew": "\u05B9",
+ "/holamnarrowhebrew": "\u05B9",
+ "/holamquarterhebrew": "\u05B9",
+ "/holamwidehebrew": "\u05B9",
+ "/hole": "\u1F573",
+ "/homotic": "\u223B",
+ "/honeyPot": "\u1F36F",
+ "/honeybee": "\u1F41D",
+ "/honokhukthai": "\u0E2E",
+ "/honsquare": "\u333F",
+ "/hook": "\u2440",
+ "/hookabovecomb": "\u0309",
+ "/hookcmb": "\u0309",
+ "/hookpalatalizedbelowcmb": "\u0321",
+ "/hookretroflexbelowcmb": "\u0322",
+ "/hoonsquare": "\u3342",
+ "/hoorusquare": "\u3341",
+ "/horicoptic": "\u03E9",
+ "/horizontalTrafficLight": "\u1F6A5",
+ "/horizontalbar": "\u2015",
+ "/horizontalbarwhitearrowonpedestalup": "\u21EC",
+ "/horizontalmalestroke": "\u26A9",
+ "/horncmb": "\u031B",
+ "/horse": "\u1F40E",
+ "/horseFace": "\u1F434",
+ "/horseRacing": "\u1F3C7",
+ "/hospital": "\u1F3E5",
+ "/hotDog": "\u1F32D",
+ "/hotPepper": "\u1F336",
+ "/hotbeverage": "\u2615",
+ "/hotel": "\u1F3E8",
+ "/hotsprings": "\u2668",
+ "/hourglass": "\u231B",
+ "/hourglassflowings": "\u23F3",
+ "/house": "\u2302",
+ "/houseBuilding": "\u1F3E0",
+ "/houseBuildings": "\u1F3D8",
+ "/houseGarden": "\u1F3E1",
+ "/hpafullwidth": "\u3371",
+ "/hpalatalhook": "\uA795",
+ "/hparen": "\u24A3",
+ "/hparenthesized": "\u24A3",
+ "/hpfullwidth": "\u33CB",
+ "/hryvnia": "\u20B4",
+ "/hsuperior": "\u02B0",
+ "/hsupmod": "\u02B0",
+ "/hturned": "\u0265",
+ "/htypeopencircuit": "\u238F",
+ "/huaraddosquare": "\u3332",
+ "/hucirclekatakana": "\u32EB",
+ "/huhiragana": "\u3075",
+ "/huiitosquare": "\u3333",
+ "/hukatakana": "\u30D5",
+ "/hukatakanahalfwidth": "\uFF8C",
+ "/hundredPoints": "\u1F4AF",
+ "/hundredthousandscmbcyr": "\u0488",
+ "/hungarumlaut": "\u02DD",
+ "/hungarumlautcmb": "\u030B",
+ "/huransquare": "\u3335",
+ "/hushedFace": "\u1F62F",
+ "/hv": "\u0195",
+ "/hwd:a": "\uFFC2",
+ "/hwd:ae": "\uFFC3",
+ "/hwd:blacksquare": "\uFFED",
+ "/hwd:chieuch": "\uFFBA",
+ "/hwd:cieuc": "\uFFB8",
+ "/hwd:downwardsarrow": "\uFFEC",
+ "/hwd:e": "\uFFC7",
+ "/hwd:eo": "\uFFC6",
+ "/hwd:eu": "\uFFDA",
+ "/hwd:formslightvertical": "\uFFE8",
+ "/hwd:hangulfiller": "\uFFA0",
+ "/hwd:hieuh": "\uFFBE",
+ "/hwd:i": "\uFFDC",
+ "/hwd:ideographiccomma": "\uFF64",
+ "/hwd:ideographicfullstop": "\uFF61",
+ "/hwd:ieung": "\uFFB7",
+ "/hwd:kata:a": "\uFF71",
+ "/hwd:kata:asmall": "\uFF67",
+ "/hwd:kata:e": "\uFF74",
+ "/hwd:kata:esmall": "\uFF6A",
+ "/hwd:kata:ha": "\uFF8A",
+ "/hwd:kata:he": "\uFF8D",
+ "/hwd:kata:hi": "\uFF8B",
+ "/hwd:kata:ho": "\uFF8E",
+ "/hwd:kata:hu": "\uFF8C",
+ "/hwd:kata:i": "\uFF72",
+ "/hwd:kata:ismall": "\uFF68",
+ "/hwd:kata:ka": "\uFF76",
+ "/hwd:kata:ke": "\uFF79",
+ "/hwd:kata:ki": "\uFF77",
+ "/hwd:kata:ko": "\uFF7A",
+ "/hwd:kata:ku": "\uFF78",
+ "/hwd:kata:ma": "\uFF8F",
+ "/hwd:kata:me": "\uFF92",
+ "/hwd:kata:mi": "\uFF90",
+ "/hwd:kata:middledot": "\uFF65",
+ "/hwd:kata:mo": "\uFF93",
+ "/hwd:kata:mu": "\uFF91",
+ "/hwd:kata:n": "\uFF9D",
+ "/hwd:kata:na": "\uFF85",
+ "/hwd:kata:ne": "\uFF88",
+ "/hwd:kata:ni": "\uFF86",
+ "/hwd:kata:no": "\uFF89",
+ "/hwd:kata:nu": "\uFF87",
+ "/hwd:kata:o": "\uFF75",
+ "/hwd:kata:osmall": "\uFF6B",
+ "/hwd:kata:prolongedkana": "\uFF70",
+ "/hwd:kata:ra": "\uFF97",
+ "/hwd:kata:re": "\uFF9A",
+ "/hwd:kata:ri": "\uFF98",
+ "/hwd:kata:ro": "\uFF9B",
+ "/hwd:kata:ru": "\uFF99",
+ "/hwd:kata:sa": "\uFF7B",
+ "/hwd:kata:se": "\uFF7E",
+ "/hwd:kata:semi-voiced": "\uFF9F",
+ "/hwd:kata:si": "\uFF7C",
+ "/hwd:kata:so": "\uFF7F",
+ "/hwd:kata:su": "\uFF7D",
+ "/hwd:kata:ta": "\uFF80",
+ "/hwd:kata:te": "\uFF83",
+ "/hwd:kata:ti": "\uFF81",
+ "/hwd:kata:to": "\uFF84",
+ "/hwd:kata:tu": "\uFF82",
+ "/hwd:kata:tusmall": "\uFF6F",
+ "/hwd:kata:u": "\uFF73",
+ "/hwd:kata:usmall": "\uFF69",
+ "/hwd:kata:voiced": "\uFF9E",
+ "/hwd:kata:wa": "\uFF9C",
+ "/hwd:kata:wo": "\uFF66",
+ "/hwd:kata:ya": "\uFF94",
+ "/hwd:kata:yasmall": "\uFF6C",
+ "/hwd:kata:yo": "\uFF96",
+ "/hwd:kata:yosmall": "\uFF6E",
+ "/hwd:kata:yu": "\uFF95",
+ "/hwd:kata:yusmall": "\uFF6D",
+ "/hwd:khieukh": "\uFFBB",
+ "/hwd:kiyeok": "\uFFA1",
+ "/hwd:kiyeoksios": "\uFFA3",
+ "/hwd:leftcornerbracket": "\uFF62",
+ "/hwd:leftwardsarrow": "\uFFE9",
+ "/hwd:mieum": "\uFFB1",
+ "/hwd:nieun": "\uFFA4",
+ "/hwd:nieuncieuc": "\uFFA5",
+ "/hwd:nieunhieuh": "\uFFA6",
+ "/hwd:o": "\uFFCC",
+ "/hwd:oe": "\uFFCF",
+ "/hwd:phieuph": "\uFFBD",
+ "/hwd:pieup": "\uFFB2",
+ "/hwd:pieupsios": "\uFFB4",
+ "/hwd:rieul": "\uFFA9",
+ "/hwd:rieulhieuh": "\uFFB0",
+ "/hwd:rieulkiyeok": "\uFFAA",
+ "/hwd:rieulmieum": "\uFFAB",
+ "/hwd:rieulphieuph": "\uFFAF",
+ "/hwd:rieulpieup": "\uFFAC",
+ "/hwd:rieulsios": "\uFFAD",
+ "/hwd:rieulthieuth": "\uFFAE",
+ "/hwd:rightcornerbracket": "\uFF63",
+ "/hwd:rightwardsarrow": "\uFFEB",
+ "/hwd:sios": "\uFFB5",
+ "/hwd:ssangcieuc": "\uFFB9",
+ "/hwd:ssangkiyeok": "\uFFA2",
+ "/hwd:ssangpieup": "\uFFB3",
+ "/hwd:ssangsios": "\uFFB6",
+ "/hwd:ssangtikeut": "\uFFA8",
+ "/hwd:thieuth": "\uFFBC",
+ "/hwd:tikeut": "\uFFA7",
+ "/hwd:u": "\uFFD3",
+ "/hwd:upwardsarrow": "\uFFEA",
+ "/hwd:wa": "\uFFCD",
+ "/hwd:wae": "\uFFCE",
+ "/hwd:we": "\uFFD5",
+ "/hwd:weo": "\uFFD4",
+ "/hwd:whitecircle": "\uFFEE",
+ "/hwd:wi": "\uFFD6",
+ "/hwd:ya": "\uFFC4",
+ "/hwd:yae": "\uFFC5",
+ "/hwd:ye": "\uFFCB",
+ "/hwd:yeo": "\uFFCA",
+ "/hwd:yi": "\uFFDB",
+ "/hwd:yo": "\uFFD2",
+ "/hwd:yu": "\uFFD7",
+ "/hyphen": "\u002D",
+ "/hyphenationpoint": "\u2027",
+ "/hyphenbullet": "\u2043",
+ "/hyphendbl": "\u2E40",
+ "/hyphendbloblique": "\u2E17",
+ "/hyphendieresis": "\u2E1A",
+ "/hypheninferior": "\uF6E5",
+ "/hyphenminus": "\u002D",
+ "/hyphenmonospace": "\uFF0D",
+ "/hyphensmall": "\uFE63",
+ "/hyphensoft": "\u00AD",
+ "/hyphensuperior": "\uF6E6",
+ "/hyphentwo": "\u2010",
+ "/hypodiastole": "\u2E12",
+ "/hysteresis": "\u238E",
+ "/hzfullwidth": "\u3390",
+ "/i": "\u0069",
+ "/i.superior": "\u2071",
+ "/iacute": "\u00ED",
+ "/iacyrillic": "\u044F",
+ "/iaepigraphic": "\uA7FE",
+ "/ibengali": "\u0987",
+ "/ibopomofo": "\u3127",
+ "/ibreve": "\u012D",
+ "/icaron": "\u01D0",
+ "/iceCream": "\u1F368",
+ "/iceHockeyStickAndPuck": "\u1F3D2",
+ "/iceskate": "\u26F8",
+ "/icircle": "\u24D8",
+ "/icirclekatakana": "\u32D1",
+ "/icircumflex": "\u00EE",
+ "/icyr": "\u0438",
+ "/icyrillic": "\u0456",
+ "/idblgrave": "\u0209",
+ "/idblstruckitalic": "\u2148",
+ "/ideographearthcircle": "\u328F",
+ "/ideographfirecircle": "\u328B",
+ "/ideographicallianceparen": "\u323F",
+ "/ideographiccallparen": "\u323A",
+ "/ideographiccentrecircle": "\u32A5",
+ "/ideographicclose": "\u3006",
+ "/ideographiccomma": "\u3001",
+ "/ideographiccommaleft": "\uFF64",
+ "/ideographiccongratulationparen": "\u3237",
+ "/ideographiccorrectcircle": "\u32A3",
+ "/ideographicdepartingtonemark": "\u302C",
+ "/ideographicearthparen": "\u322F",
+ "/ideographicenteringtonemark": "\u302D",
+ "/ideographicenterpriseparen": "\u323D",
+ "/ideographicexcellentcircle": "\u329D",
+ "/ideographicfestivalparen": "\u3240",
+ "/ideographicfinancialcircle": "\u3296",
+ "/ideographicfinancialparen": "\u3236",
+ "/ideographicfireparen": "\u322B",
+ "/ideographichalffillspace": "\u303F",
+ "/ideographichaveparen": "\u3232",
+ "/ideographichighcircle": "\u32A4",
+ "/ideographiciterationmark": "\u3005",
+ "/ideographiclaborcircle": "\u3298",
+ "/ideographiclaborparen": "\u3238",
+ "/ideographicleftcircle": "\u32A7",
+ "/ideographicleveltonemark": "\u302A",
+ "/ideographiclowcircle": "\u32A6",
+ "/ideographicmedicinecircle": "\u32A9",
+ "/ideographicmetalparen": "\u322E",
+ "/ideographicmoonparen": "\u322A",
+ "/ideographicnameparen": "\u3234",
+ "/ideographicperiod": "\u3002",
+ "/ideographicprintcircle": "\u329E",
+ "/ideographicreachparen": "\u3243",
+ "/ideographicrepresentparen": "\u3239",
+ "/ideographicresourceparen": "\u323E",
+ "/ideographicrightcircle": "\u32A8",
+ "/ideographicrisingtonemark": "\u302B",
+ "/ideographicsecretcircle": "\u3299",
+ "/ideographicselfparen": "\u3242",
+ "/ideographicsocietyparen": "\u3233",
+ "/ideographicspace": "\u3000",
+ "/ideographicspecialparen": "\u3235",
+ "/ideographicstockparen": "\u3231",
+ "/ideographicstudyparen": "\u323B",
+ "/ideographicsunparen": "\u3230",
+ "/ideographicsuperviseparen": "\u323C",
+ "/ideographictelegraphlinefeedseparatorsymbol": "\u3037",
+ "/ideographictelegraphsymbolforhoureight": "\u3360",
+ "/ideographictelegraphsymbolforhoureighteen": "\u336A",
+ "/ideographictelegraphsymbolforhoureleven": "\u3363",
+ "/ideographictelegraphsymbolforhourfifteen": "\u3367",
+ "/ideographictelegraphsymbolforhourfive": "\u335D",
+ "/ideographictelegraphsymbolforhourfour": "\u335C",
+ "/ideographictelegraphsymbolforhourfourteen": "\u3366",
+ "/ideographictelegraphsymbolforhournine": "\u3361",
+ "/ideographictelegraphsymbolforhournineteen": "\u336B",
+ "/ideographictelegraphsymbolforhourone": "\u3359",
+ "/ideographictelegraphsymbolforhourseven": "\u335F",
+ "/ideographictelegraphsymbolforhourseventeen": "\u3369",
+ "/ideographictelegraphsymbolforhoursix": "\u335E",
+ "/ideographictelegraphsymbolforhoursixteen": "\u3368",
+ "/ideographictelegraphsymbolforhourten": "\u3362",
+ "/ideographictelegraphsymbolforhourthirteen": "\u3365",
+ "/ideographictelegraphsymbolforhourthree": "\u335B",
+ "/ideographictelegraphsymbolforhourtwelve": "\u3364",
+ "/ideographictelegraphsymbolforhourtwenty": "\u336C",
+ "/ideographictelegraphsymbolforhourtwentyfour": "\u3370",
+ "/ideographictelegraphsymbolforhourtwentyone": "\u336D",
+ "/ideographictelegraphsymbolforhourtwentythree": "\u336F",
+ "/ideographictelegraphsymbolforhourtwentytwo": "\u336E",
+ "/ideographictelegraphsymbolforhourtwo": "\u335A",
+ "/ideographictelegraphsymbolforhourzero": "\u3358",
+ "/ideographicvariationindicator": "\u303E",
+ "/ideographicwaterparen": "\u322C",
+ "/ideographicwoodparen": "\u322D",
+ "/ideographiczero": "\u3007",
+ "/ideographmetalcircle": "\u328E",
+ "/ideographmooncircle": "\u328A",
+ "/ideographnamecircle": "\u3294",
+ "/ideographsuncircle": "\u3290",
+ "/ideographwatercircle": "\u328C",
+ "/ideographwoodcircle": "\u328D",
+ "/ideva": "\u0907",
+ "/idieresis": "\u00EF",
+ "/idieresisacute": "\u1E2F",
+ "/idieresiscyr": "\u04E5",
+ "/idieresiscyrillic": "\u04E5",
+ "/idotbelow": "\u1ECB",
+ "/idsquare": "\u1F194",
+ "/iebrevecyr": "\u04D7",
+ "/iebrevecyrillic": "\u04D7",
+ "/iecyr": "\u0435",
+ "/iecyrillic": "\u0435",
+ "/iegravecyr": "\u0450",
+ "/iepigraphicsideways": "\uA7F7",
+ "/ieungacirclekorean": "\u3275",
+ "/ieungaparenkorean": "\u3215",
+ "/ieungcirclekorean": "\u3267",
+ "/ieungkorean": "\u3147",
+ "/ieungparenkorean": "\u3207",
+ "/ieungucirclekorean": "\u327E",
+ "/igrave": "\u00EC",
+ "/igravecyr": "\u045D",
+ "/igravedbl": "\u0209",
+ "/igujarati": "\u0A87",
+ "/igurmukhi": "\u0A07",
+ "/ihiragana": "\u3044",
+ "/ihoi": "\u1EC9",
+ "/ihookabove": "\u1EC9",
+ "/iibengali": "\u0988",
+ "/iicyrillic": "\u0438",
+ "/iideva": "\u0908",
+ "/iigujarati": "\u0A88",
+ "/iigurmukhi": "\u0A08",
+ "/iimatragurmukhi": "\u0A40",
+ "/iinvertedbreve": "\u020B",
+ "/iishortcyrillic": "\u0439",
+ "/iivowelsignbengali": "\u09C0",
+ "/iivowelsigndeva": "\u0940",
+ "/iivowelsigngujarati": "\u0AC0",
+ "/ij": "\u0133",
+ "/ikatakana": "\u30A4",
+ "/ikatakanahalfwidth": "\uFF72",
+ "/ikawi": "\uA985",
+ "/ikorean": "\u3163",
+ "/ilde": "\u02DC",
+ "/iluy:hb": "\u05AC",
+ "/iluyhebrew": "\u05AC",
+ "/imacron": "\u012B",
+ "/imacroncyr": "\u04E3",
+ "/imacroncyrillic": "\u04E3",
+ "/image": "\u22B7",
+ "/imageorapproximatelyequal": "\u2253",
+ "/imatragurmukhi": "\u0A3F",
+ "/imonospace": "\uFF49",
+ "/imp": "\u1F47F",
+ "/inboxTray": "\u1F4E5",
+ "/incomingEnvelope": "\u1F4E8",
+ "/increaseFontSize": "\u1F5DA",
+ "/increment": "\u2206",
+ "/indianrupee": "\u20B9",
+ "/infinity": "\u221E",
+ "/information": "\u2139",
+ "/infullwidth": "\u33CC",
+ "/inhibitarabicformshaping": "\u206C",
+ "/inhibitsymmetricswapping": "\u206A",
+ "/iniarmenian": "\u056B",
+ "/iningusquare": "\u3304",
+ "/inmationDeskPerson": "\u1F481",
+ "/inputLatinCapitalLetters": "\u1F520",
+ "/inputLatinLetters": "\u1F524",
+ "/inputLatinSmallLetters": "\u1F521",
+ "/inputNumbers": "\u1F522",
+ "/inputS": "\u1F523",
+ "/insertion": "\u2380",
+ "/integral": "\u222B",
+ "/integralbottom": "\u2321",
+ "/integralbt": "\u2321",
+ "/integralclockwise": "\u2231",
+ "/integralcontour": "\u222E",
+ "/integralcontouranticlockwise": "\u2233",
+ "/integralcontourclockwise": "\u2232",
+ "/integraldbl": "\u222C",
+ "/integralex": "\uF8F5",
+ "/integralextension": "\u23AE",
+ "/integralsurface": "\u222F",
+ "/integraltop": "\u2320",
+ "/integraltp": "\u2320",
+ "/integraltpl": "\u222D",
+ "/integralvolume": "\u2230",
+ "/intercalate": "\u22BA",
+ "/interlinearanchor": "\uFFF9",
+ "/interlinearseparator": "\uFFFA",
+ "/interlinearterminator": "\uFFFB",
+ "/interlockedfemalemale": "\u26A4",
+ "/interrobang": "\u203D",
+ "/interrobanginverted": "\u2E18",
+ "/intersection": "\u2229",
+ "/intersectionarray": "\u22C2",
+ "/intersectiondbl": "\u22D2",
+ "/intisquare": "\u3305",
+ "/invbullet": "\u25D8",
+ "/invcircle": "\u25D9",
+ "/inverteddamma": "\u0657",
+ "/invertedfork": "\u2443",
+ "/invertedpentagram": "\u26E7",
+ "/invertedundertie": "\u2054",
+ "/invisibleplus": "\u2064",
+ "/invisibleseparator": "\u2063",
+ "/invisibletimes": "\u2062",
+ "/invsmileface": "\u263B",
+ "/iocyr": "\u0451",
+ "/iocyrillic": "\u0451",
+ "/iogonek": "\u012F",
+ "/iota": "\u03B9",
+ "/iotaacute": "\u1F77",
+ "/iotaadscript": "\u1FBE",
+ "/iotaasper": "\u1F31",
+ "/iotaasperacute": "\u1F35",
+ "/iotaaspergrave": "\u1F33",
+ "/iotaaspertilde": "\u1F37",
+ "/iotabreve": "\u1FD0",
+ "/iotadieresis": "\u03CA",
+ "/iotadieresisacute": "\u1FD3",
+ "/iotadieresisgrave": "\u1FD2",
+ "/iotadieresistilde": "\u1FD7",
+ "/iotadieresistonos": "\u0390",
+ "/iotafunc": "\u2373",
+ "/iotagrave": "\u1F76",
+ "/iotalatin": "\u0269",
+ "/iotalenis": "\u1F30",
+ "/iotalenisacute": "\u1F34",
+ "/iotalenisgrave": "\u1F32",
+ "/iotalenistilde": "\u1F36",
+ "/iotasub": "\u037A",
+ "/iotatilde": "\u1FD6",
+ "/iotatonos": "\u03AF",
+ "/iotaturned": "\u2129",
+ "/iotaunderlinefunc": "\u2378",
+ "/iotawithmacron": "\u1FD1",
+ "/ipa:Ismall": "\u026A",
+ "/ipa:alpha": "\u0251",
+ "/ipa:ereversed": "\u0258",
+ "/ipa:esh": "\u0283",
+ "/ipa:gamma": "\u0263",
+ "/ipa:glottalstop": "\u0294",
+ "/ipa:gscript": "\u0261",
+ "/ipa:iota": "\u0269",
+ "/ipa:phi": "\u0278",
+ "/ipa:rtail": "\u027D",
+ "/ipa:schwa": "\u0259",
+ "/ipa:upsilon": "\u028A",
+ "/iparen": "\u24A4",
+ "/iparenthesized": "\u24A4",
+ "/irigurmukhi": "\u0A72",
+ "/is": "\uA76D",
+ "/isen-isenpada": "\uA9DF",
+ "/ishortcyr": "\u0439",
+ "/ishortsharptailcyr": "\u048B",
+ "/ismallhiragana": "\u3043",
+ "/ismallkatakana": "\u30A3",
+ "/ismallkatakanahalfwidth": "\uFF68",
+ "/issharbengali": "\u09FA",
+ "/istroke": "\u0268",
+ "/isuperior": "\uF6ED",
+ "/itemideographiccircled": "\u32A0",
+ "/iterationhiragana": "\u309D",
+ "/iterationkatakana": "\u30FD",
+ "/itilde": "\u0129",
+ "/itildebelow": "\u1E2D",
+ "/iubopomofo": "\u3129",
+ "/iucyrillic": "\u044E",
+ "/iufullwidth": "\u337A",
+ "/iukrcyr": "\u0456",
+ "/ivowelsignbengali": "\u09BF",
+ "/ivowelsigndeva": "\u093F",
+ "/ivowelsigngujarati": "\u0ABF",
+ "/izakayaLantern": "\u1F3EE",
+ "/izhitsacyr": "\u0475",
+ "/izhitsacyrillic": "\u0475",
+ "/izhitsadblgravecyrillic": "\u0477",
+ "/izhitsagravedblcyr": "\u0477",
+ "/j": "\u006A",
+ "/j.inferior": "\u2C7C",
+ "/jaarmenian": "\u0571",
+ "/jabengali": "\u099C",
+ "/jackOLantern": "\u1F383",
+ "/jadeva": "\u091C",
+ "/jagujarati": "\u0A9C",
+ "/jagurmukhi": "\u0A1C",
+ "/jamahaprana": "\uA999",
+ "/januarytelegraph": "\u32C0",
+ "/japaneseBeginner": "\u1F530",
+ "/japaneseCastle": "\u1F3EF",
+ "/japaneseDolls": "\u1F38E",
+ "/japaneseGoblin": "\u1F47A",
+ "/japaneseOgre": "\u1F479",
+ "/japanesePostOffice": "\u1F3E3",
+ "/japanesebank": "\u26FB",
+ "/java:a": "\uA984",
+ "/java:ai": "\uA98D",
+ "/java:ba": "\uA9A7",
+ "/java:ca": "\uA995",
+ "/java:da": "\uA9A2",
+ "/java:dda": "\uA99D",
+ "/java:e": "\uA98C",
+ "/java:eight": "\uA9D8",
+ "/java:five": "\uA9D5",
+ "/java:four": "\uA9D4",
+ "/java:ga": "\uA992",
+ "/java:ha": "\uA9B2",
+ "/java:i": "\uA986",
+ "/java:ii": "\uA987",
+ "/java:ja": "\uA997",
+ "/java:ka": "\uA98F",
+ "/java:la": "\uA9AD",
+ "/java:ma": "\uA9A9",
+ "/java:na": "\uA9A4",
+ "/java:nga": "\uA994",
+ "/java:nine": "\uA9D9",
+ "/java:nya": "\uA99A",
+ "/java:o": "\uA98E",
+ "/java:one": "\uA9D1",
+ "/java:pa": "\uA9A5",
+ "/java:ra": "\uA9AB",
+ "/java:sa": "\uA9B1",
+ "/java:seven": "\uA9D7",
+ "/java:six": "\uA9D6",
+ "/java:ta": "\uA9A0",
+ "/java:three": "\uA9D3",
+ "/java:tta": "\uA99B",
+ "/java:two": "\uA9D2",
+ "/java:u": "\uA988",
+ "/java:wa": "\uA9AE",
+ "/java:ya": "\uA9AA",
+ "/java:zero": "\uA9D0",
+ "/jbopomofo": "\u3110",
+ "/jcaron": "\u01F0",
+ "/jcircle": "\u24D9",
+ "/jcircumflex": "\u0135",
+ "/jcrossedtail": "\u029D",
+ "/jdblstruckitalic": "\u2149",
+ "/jdotlessstroke": "\u025F",
+ "/jeans": "\u1F456",
+ "/jecyr": "\u0458",
+ "/jecyrillic": "\u0458",
+ "/jeem": "\u062C",
+ "/jeem.fina": "\uFE9E",
+ "/jeem.init": "\uFE9F",
+ "/jeem.init_alefmaksura.fina": "\uFD01",
+ "/jeem.init_hah.fina": "\uFC15",
+ "/jeem.init_hah.medi": "\uFCA7",
+ "/jeem.init_meem.fina": "\uFC16",
+ "/jeem.init_meem.medi": "\uFCA8",
+ "/jeem.init_meem.medi_hah.medi": "\uFD59",
+ "/jeem.init_yeh.fina": "\uFD02",
+ "/jeem.isol": "\uFE9D",
+ "/jeem.medi": "\uFEA0",
+ "/jeem.medi_alefmaksura.fina": "\uFD1D",
+ "/jeem.medi_hah.medi_alefmaksura.fina": "\uFDA6",
+ "/jeem.medi_hah.medi_yeh.fina": "\uFDBE",
+ "/jeem.medi_meem.medi_alefmaksura.fina": "\uFDA7",
+ "/jeem.medi_meem.medi_hah.fina": "\uFD58",
+ "/jeem.medi_meem.medi_yeh.fina": "\uFDA5",
+ "/jeem.medi_yeh.fina": "\uFD1E",
+ "/jeemabove": "\u06DA",
+ "/jeemarabic": "\u062C",
+ "/jeemfinalarabic": "\uFE9E",
+ "/jeeminitialarabic": "\uFE9F",
+ "/jeemmedialarabic": "\uFEA0",
+ "/jeh": "\u0698",
+ "/jeh.fina": "\uFB8B",
+ "/jeh.isol": "\uFB8A",
+ "/jeharabic": "\u0698",
+ "/jehfinalarabic": "\uFB8B",
+ "/jhabengali": "\u099D",
+ "/jhadeva": "\u091D",
+ "/jhagujarati": "\u0A9D",
+ "/jhagurmukhi": "\u0A1D",
+ "/jheharmenian": "\u057B",
+ "/jis": "\u3004",
+ "/jiterup": "\u2643",
+ "/jmonospace": "\uFF4A",
+ "/jotdiaeresisfunc": "\u2364",
+ "/jotunderlinefunc": "\u235B",
+ "/joystick": "\u1F579",
+ "/jparen": "\u24A5",
+ "/jparenthesized": "\u24A5",
+ "/jstroke": "\u0249",
+ "/jsuperior": "\u02B2",
+ "/jsupmod": "\u02B2",
+ "/jueuicircle": "\u327D",
+ "/julytelegraph": "\u32C6",
+ "/junetelegraph": "\u32C5",
+ "/juno": "\u26B5",
+ "/k": "\u006B",
+ "/k.inferior": "\u2096",
+ "/kaaba": "\u1F54B",
+ "/kaaleutcyr": "\u051F",
+ "/kabashkcyr": "\u04A1",
+ "/kabashkircyrillic": "\u04A1",
+ "/kabengali": "\u0995",
+ "/kacirclekatakana": "\u32D5",
+ "/kacute": "\u1E31",
+ "/kacyr": "\u043A",
+ "/kacyrillic": "\u043A",
+ "/kadescendercyrillic": "\u049B",
+ "/kadeva": "\u0915",
+ "/kaf": "\u05DB",
+ "/kaf.fina": "\uFEDA",
+ "/kaf.init": "\uFEDB",
+ "/kaf.init_alef.fina": "\uFC37",
+ "/kaf.init_alefmaksura.fina": "\uFC3D",
+ "/kaf.init_hah.fina": "\uFC39",
+ "/kaf.init_hah.medi": "\uFCC5",
+ "/kaf.init_jeem.fina": "\uFC38",
+ "/kaf.init_jeem.medi": "\uFCC4",
+ "/kaf.init_khah.fina": "\uFC3A",
+ "/kaf.init_khah.medi": "\uFCC6",
+ "/kaf.init_lam.fina": "\uFC3B",
+ "/kaf.init_lam.medi": "\uFCC7",
+ "/kaf.init_meem.fina": "\uFC3C",
+ "/kaf.init_meem.medi": "\uFCC8",
+ "/kaf.init_meem.medi_meem.medi": "\uFDC3",
+ "/kaf.init_yeh.fina": "\uFC3E",
+ "/kaf.isol": "\uFED9",
+ "/kaf.medi": "\uFEDC",
+ "/kaf.medi_alef.fina": "\uFC80",
+ "/kaf.medi_alefmaksura.fina": "\uFC83",
+ "/kaf.medi_lam.fina": "\uFC81",
+ "/kaf.medi_lam.medi": "\uFCEB",
+ "/kaf.medi_meem.fina": "\uFC82",
+ "/kaf.medi_meem.medi": "\uFCEC",
+ "/kaf.medi_meem.medi_meem.fina": "\uFDBB",
+ "/kaf.medi_meem.medi_yeh.fina": "\uFDB7",
+ "/kaf.medi_yeh.fina": "\uFC84",
+ "/kaf:hb": "\u05DB",
+ "/kafTwoDotsAbove": "\u077F",
+ "/kafarabic": "\u0643",
+ "/kafdagesh": "\uFB3B",
+ "/kafdageshhebrew": "\uFB3B",
+ "/kafdotabove": "\u06AC",
+ "/kaffinalarabic": "\uFEDA",
+ "/kafhebrew": "\u05DB",
+ "/kafinitialarabic": "\uFEDB",
+ "/kafmedialarabic": "\uFEDC",
+ "/kafrafehebrew": "\uFB4D",
+ "/kafring": "\u06AB",
+ "/kafswash": "\u06AA",
+ "/kafthreedotsbelow": "\u06AE",
+ "/kafullwidth": "\u3384",
+ "/kafwide:hb": "\uFB24",
+ "/kafwithdagesh:hb": "\uFB3B",
+ "/kafwithrafe:hb": "\uFB4D",
+ "/kagujarati": "\u0A95",
+ "/kagurmukhi": "\u0A15",
+ "/kahiragana": "\u304B",
+ "/kahookcyr": "\u04C4",
+ "/kahookcyrillic": "\u04C4",
+ "/kairisquare": "\u330B",
+ "/kaisymbol": "\u03D7",
+ "/kakatakana": "\u30AB",
+ "/kakatakanahalfwidth": "\uFF76",
+ "/kamurda": "\uA991",
+ "/kappa": "\u03BA",
+ "/kappa.math": "\u03F0",
+ "/kappasymbolgreek": "\u03F0",
+ "/kapyeounmieumkorean": "\u3171",
+ "/kapyeounphieuphkorean": "\u3184",
+ "/kapyeounpieupkorean": "\u3178",
+ "/kapyeounssangpieupkorean": "\u3179",
+ "/karattosquare": "\u330C",
+ "/karoriisquare": "\u330D",
+ "/kasasak": "\uA990",
+ "/kashida": "\u0640",
+ "/kashidaFina": "\uFE73",
+ "/kashidaautoarabic": "\u0640",
+ "/kashidaautonosidebearingarabic": "\u0640",
+ "/kashmiriyeh": "\u0620",
+ "/kasmallkatakana": "\u30F5",
+ "/kasquare": "\u3384",
+ "/kasra": "\u0650",
+ "/kasraIsol": "\uFE7A",
+ "/kasraMedi": "\uFE7B",
+ "/kasraarabic": "\u0650",
+ "/kasrasmall": "\u061A",
+ "/kasratan": "\u064D",
+ "/kasratanIsol": "\uFE74",
+ "/kasratanarabic": "\u064D",
+ "/kastrokecyr": "\u049F",
+ "/kastrokecyrillic": "\u049F",
+ "/kata:a": "\u30A2",
+ "/kata:asmall": "\u30A1",
+ "/kata:ba": "\u30D0",
+ "/kata:be": "\u30D9",
+ "/kata:bi": "\u30D3",
+ "/kata:bo": "\u30DC",
+ "/kata:bu": "\u30D6",
+ "/kata:da": "\u30C0",
+ "/kata:de": "\u30C7",
+ "/kata:di": "\u30C2",
+ "/kata:digraphkoto": "\u30FF",
+ "/kata:do": "\u30C9",
+ "/kata:doublehyphenkana": "\u30A0",
+ "/kata:du": "\u30C5",
+ "/kata:e": "\u30A8",
+ "/kata:esmall": "\u30A7",
+ "/kata:ga": "\u30AC",
+ "/kata:ge": "\u30B2",
+ "/kata:gi": "\u30AE",
+ "/kata:go": "\u30B4",
+ "/kata:gu": "\u30B0",
+ "/kata:ha": "\u30CF",
+ "/kata:he": "\u30D8",
+ "/kata:hi": "\u30D2",
+ "/kata:ho": "\u30DB",
+ "/kata:hu": "\u30D5",
+ "/kata:i": "\u30A4",
+ "/kata:ismall": "\u30A3",
+ "/kata:iteration": "\u30FD",
+ "/kata:ka": "\u30AB",
+ "/kata:kasmall": "\u30F5",
+ "/kata:ke": "\u30B1",
+ "/kata:kesmall": "\u30F6",
+ "/kata:ki": "\u30AD",
+ "/kata:ko": "\u30B3",
+ "/kata:ku": "\u30AF",
+ "/kata:ma": "\u30DE",
+ "/kata:me": "\u30E1",
+ "/kata:mi": "\u30DF",
+ "/kata:middledot": "\u30FB",
+ "/kata:mo": "\u30E2",
+ "/kata:mu": "\u30E0",
+ "/kata:n": "\u30F3",
+ "/kata:na": "\u30CA",
+ "/kata:ne": "\u30CD",
+ "/kata:ni": "\u30CB",
+ "/kata:no": "\u30CE",
+ "/kata:nu": "\u30CC",
+ "/kata:o": "\u30AA",
+ "/kata:osmall": "\u30A9",
+ "/kata:pa": "\u30D1",
+ "/kata:pe": "\u30DA",
+ "/kata:pi": "\u30D4",
+ "/kata:po": "\u30DD",
+ "/kata:prolongedkana": "\u30FC",
+ "/kata:pu": "\u30D7",
+ "/kata:ra": "\u30E9",
+ "/kata:re": "\u30EC",
+ "/kata:ri": "\u30EA",
+ "/kata:ro": "\u30ED",
+ "/kata:ru": "\u30EB",
+ "/kata:sa": "\u30B5",
+ "/kata:se": "\u30BB",
+ "/kata:si": "\u30B7",
+ "/kata:so": "\u30BD",
+ "/kata:su": "\u30B9",
+ "/kata:ta": "\u30BF",
+ "/kata:te": "\u30C6",
+ "/kata:ti": "\u30C1",
+ "/kata:to": "\u30C8",
+ "/kata:tu": "\u30C4",
+ "/kata:tusmall": "\u30C3",
+ "/kata:u": "\u30A6",
+ "/kata:usmall": "\u30A5",
+ "/kata:va": "\u30F7",
+ "/kata:ve": "\u30F9",
+ "/kata:vi": "\u30F8",
+ "/kata:vo": "\u30FA",
+ "/kata:voicediteration": "\u30FE",
+ "/kata:vu": "\u30F4",
+ "/kata:wa": "\u30EF",
+ "/kata:wasmall": "\u30EE",
+ "/kata:we": "\u30F1",
+ "/kata:wi": "\u30F0",
+ "/kata:wo": "\u30F2",
+ "/kata:ya": "\u30E4",
+ "/kata:yasmall": "\u30E3",
+ "/kata:yo": "\u30E8",
+ "/kata:yosmall": "\u30E7",
+ "/kata:yu": "\u30E6",
+ "/kata:yusmall": "\u30E5",
+ "/kata:za": "\u30B6",
+ "/kata:ze": "\u30BC",
+ "/kata:zi": "\u30B8",
+ "/kata:zo": "\u30BE",
+ "/kata:zu": "\u30BA",
+ "/katahiraprolongmarkhalfwidth": "\uFF70",
+ "/katailcyr": "\u049B",
+ "/kaverticalstrokecyr": "\u049D",
+ "/kaverticalstrokecyrillic": "\u049D",
+ "/kavykainvertedlow": "\u2E45",
+ "/kavykalow": "\u2E47",
+ "/kavykawithdotlow": "\u2E48",
+ "/kavykawithkavykaaboveinvertedlow": "\u2E46",
+ "/kbfullwidth": "\u3385",
+ "/kbopomofo": "\u310E",
+ "/kcalfullwidth": "\u3389",
+ "/kcalsquare": "\u3389",
+ "/kcaron": "\u01E9",
+ "/kcedilla": "\u0137",
+ "/kcircle": "\u24DA",
+ "/kcommaaccent": "\u0137",
+ "/kdescender": "\u2C6A",
+ "/kdiagonalstroke": "\uA743",
+ "/kdotbelow": "\u1E33",
+ "/kecirclekatakana": "\u32D8",
+ "/keesusquare": "\u331C",
+ "/keharmenian": "\u0584",
+ "/keheh": "\u06A9",
+ "/keheh.fina": "\uFB8F",
+ "/keheh.init": "\uFB90",
+ "/keheh.isol": "\uFB8E",
+ "/keheh.medi": "\uFB91",
+ "/kehehDotAbove": "\u0762",
+ "/kehehThreeDotsAbove": "\u0763",
+ "/kehehThreeDotsUpBelow": "\u0764",
+ "/kehehthreedotsbelow": "\u063C",
+ "/kehehtwodotsabove": "\u063B",
+ "/kehiragana": "\u3051",
+ "/kekatakana": "\u30B1",
+ "/kekatakanahalfwidth": "\uFF79",
+ "/kelvin": "\u212A",
+ "/kenarmenian": "\u056F",
+ "/keretconsonant": "\uA9BD",
+ "/kesmallkatakana": "\u30F6",
+ "/key": "\u1F511",
+ "/keyboardAndMouse": "\u1F5A6",
+ "/keycapTen": "\u1F51F",
+ "/kgfullwidth": "\u338F",
+ "/kgreenlandic": "\u0138",
+ "/khabengali": "\u0996",
+ "/khacyrillic": "\u0445",
+ "/khadeva": "\u0916",
+ "/khagujarati": "\u0A96",
+ "/khagurmukhi": "\u0A16",
+ "/khah": "\u062E",
+ "/khah.fina": "\uFEA6",
+ "/khah.init": "\uFEA7",
+ "/khah.init_alefmaksura.fina": "\uFD03",
+ "/khah.init_hah.fina": "\uFC1A",
+ "/khah.init_jeem.fina": "\uFC19",
+ "/khah.init_jeem.medi": "\uFCAB",
+ "/khah.init_meem.fina": "\uFC1B",
+ "/khah.init_meem.medi": "\uFCAC",
+ "/khah.init_yeh.fina": "\uFD04",
+ "/khah.isol": "\uFEA5",
+ "/khah.medi": "\uFEA8",
+ "/khah.medi_alefmaksura.fina": "\uFD1F",
+ "/khah.medi_yeh.fina": "\uFD20",
+ "/khaharabic": "\u062E",
+ "/khahfinalarabic": "\uFEA6",
+ "/khahinitialarabic": "\uFEA7",
+ "/khahmedialarabic": "\uFEA8",
+ "/kheicoptic": "\u03E7",
+ "/khhadeva": "\u0959",
+ "/khhagurmukhi": "\u0A59",
+ "/khieukhacirclekorean": "\u3278",
+ "/khieukhaparenkorean": "\u3218",
+ "/khieukhcirclekorean": "\u326A",
+ "/khieukhkorean": "\u314B",
+ "/khieukhparenkorean": "\u320A",
+ "/khokhaithai": "\u0E02",
+ "/khokhonthai": "\u0E05",
+ "/khokhuatthai": "\u0E03",
+ "/khokhwaithai": "\u0E04",
+ "/khomutthai": "\u0E5B",
+ "/khook": "\u0199",
+ "/khorakhangthai": "\u0E06",
+ "/khzfullwidth": "\u3391",
+ "/khzsquare": "\u3391",
+ "/kicirclekatakana": "\u32D6",
+ "/kihiragana": "\u304D",
+ "/kikatakana": "\u30AD",
+ "/kikatakanahalfwidth": "\uFF77",
+ "/kimono": "\u1F458",
+ "/kindergartenideographiccircled": "\u3245",
+ "/kingblack": "\u265A",
+ "/kingwhite": "\u2654",
+ "/kip": "\u20AD",
+ "/kiroguramusquare": "\u3315",
+ "/kiromeetorusquare": "\u3316",
+ "/kirosquare": "\u3314",
+ "/kirowattosquare": "\u3317",
+ "/kiss": "\u1F48F",
+ "/kissMark": "\u1F48B",
+ "/kissingCatFaceWithClosedEyes": "\u1F63D",
+ "/kissingFace": "\u1F617",
+ "/kissingFaceWithClosedEyes": "\u1F61A",
+ "/kissingFaceWithSmilingEyes": "\u1F619",
+ "/kiyeokacirclekorean": "\u326E",
+ "/kiyeokaparenkorean": "\u320E",
+ "/kiyeokcirclekorean": "\u3260",
+ "/kiyeokkorean": "\u3131",
+ "/kiyeokparenkorean": "\u3200",
+ "/kiyeoksioskorean": "\u3133",
+ "/kjecyr": "\u045C",
+ "/kjecyrillic": "\u045C",
+ "/kkfullwidth": "\u33CD",
+ "/klfullwidth": "\u3398",
+ "/klinebelow": "\u1E35",
+ "/klsquare": "\u3398",
+ "/km2fullwidth": "\u33A2",
+ "/km3fullwidth": "\u33A6",
+ "/kmcapitalfullwidth": "\u33CE",
+ "/kmcubedsquare": "\u33A6",
+ "/kmfullwidth": "\u339E",
+ "/kmonospace": "\uFF4B",
+ "/kmsquaredsquare": "\u33A2",
+ "/knda:a": "\u0C85",
+ "/knda:aa": "\u0C86",
+ "/knda:aasign": "\u0CBE",
+ "/knda:ai": "\u0C90",
+ "/knda:ailength": "\u0CD6",
+ "/knda:aisign": "\u0CC8",
+ "/knda:anusvara": "\u0C82",
+ "/knda:au": "\u0C94",
+ "/knda:ausign": "\u0CCC",
+ "/knda:avagraha": "\u0CBD",
+ "/knda:ba": "\u0CAC",
+ "/knda:bha": "\u0CAD",
+ "/knda:ca": "\u0C9A",
+ "/knda:cha": "\u0C9B",
+ "/knda:da": "\u0CA6",
+ "/knda:dda": "\u0CA1",
+ "/knda:ddha": "\u0CA2",
+ "/knda:dha": "\u0CA7",
+ "/knda:e": "\u0C8E",
+ "/knda:ee": "\u0C8F",
+ "/knda:eesign": "\u0CC7",
+ "/knda:eight": "\u0CEE",
+ "/knda:esign": "\u0CC6",
+ "/knda:fa": "\u0CDE",
+ "/knda:five": "\u0CEB",
+ "/knda:four": "\u0CEA",
+ "/knda:ga": "\u0C97",
+ "/knda:gha": "\u0C98",
+ "/knda:ha": "\u0CB9",
+ "/knda:i": "\u0C87",
+ "/knda:ii": "\u0C88",
+ "/knda:iisign": "\u0CC0",
+ "/knda:isign": "\u0CBF",
+ "/knda:ja": "\u0C9C",
+ "/knda:jha": "\u0C9D",
+ "/knda:jihvamuliya": "\u0CF1",
+ "/knda:ka": "\u0C95",
+ "/knda:kha": "\u0C96",
+ "/knda:la": "\u0CB2",
+ "/knda:length": "\u0CD5",
+ "/knda:lla": "\u0CB3",
+ "/knda:llvocal": "\u0CE1",
+ "/knda:llvocalsign": "\u0CE3",
+ "/knda:lvocal": "\u0C8C",
+ "/knda:lvocalsign": "\u0CE2",
+ "/knda:ma": "\u0CAE",
+ "/knda:na": "\u0CA8",
+ "/knda:nga": "\u0C99",
+ "/knda:nine": "\u0CEF",
+ "/knda:nna": "\u0CA3",
+ "/knda:nukta": "\u0CBC",
+ "/knda:nya": "\u0C9E",
+ "/knda:o": "\u0C92",
+ "/knda:one": "\u0CE7",
+ "/knda:oo": "\u0C93",
+ "/knda:oosign": "\u0CCB",
+ "/knda:osign": "\u0CCA",
+ "/knda:pa": "\u0CAA",
+ "/knda:pha": "\u0CAB",
+ "/knda:ra": "\u0CB0",
+ "/knda:rra": "\u0CB1",
+ "/knda:rrvocal": "\u0CE0",
+ "/knda:rrvocalsign": "\u0CC4",
+ "/knda:rvocal": "\u0C8B",
+ "/knda:rvocalsign": "\u0CC3",
+ "/knda:sa": "\u0CB8",
+ "/knda:seven": "\u0CED",
+ "/knda:sha": "\u0CB6",
+ "/knda:signcandrabindu": "\u0C81",
+ "/knda:signspacingcandrabindu": "\u0C80",
+ "/knda:six": "\u0CEC",
+ "/knda:ssa": "\u0CB7",
+ "/knda:ta": "\u0CA4",
+ "/knda:tha": "\u0CA5",
+ "/knda:three": "\u0CE9",
+ "/knda:tta": "\u0C9F",
+ "/knda:ttha": "\u0CA0",
+ "/knda:two": "\u0CE8",
+ "/knda:u": "\u0C89",
+ "/knda:upadhmaniya": "\u0CF2",
+ "/knda:usign": "\u0CC1",
+ "/knda:uu": "\u0C8A",
+ "/knda:uusign": "\u0CC2",
+ "/knda:va": "\u0CB5",
+ "/knda:virama": "\u0CCD",
+ "/knda:visarga": "\u0C83",
+ "/knda:ya": "\u0CAF",
+ "/knda:zero": "\u0CE6",
+ "/knightblack": "\u265E",
+ "/knightwhite": "\u2658",
+ "/ko:a": "\u314F",
+ "/ko:ae": "\u3150",
+ "/ko:aejungseong": "\u1162",
+ "/ko:aeujungseong": "\u11A3",
+ "/ko:ajungseong": "\u1161",
+ "/ko:aojungseong": "\u1176",
+ "/ko:araea": "\u318D",
+ "/ko:araeae": "\u318E",
+ "/ko:araeaeojungseong": "\u119F",
+ "/ko:araeaijungseong": "\u11A1",
+ "/ko:araeajungseong": "\u119E",
+ "/ko:araeaujungseong": "\u11A0",
+ "/ko:aujungseong": "\u1177",
+ "/ko:ceongchieumchieuchchoseong": "\u1155",
+ "/ko:ceongchieumcieucchoseong": "\u1150",
+ "/ko:ceongchieumsioschoseong": "\u113E",
+ "/ko:ceongchieumssangcieucchoseong": "\u1151",
+ "/ko:ceongchieumssangsioschoseong": "\u113F",
+ "/ko:chieuch": "\u314A",
+ "/ko:chieuchchoseong": "\u110E",
+ "/ko:chieuchhieuhchoseong": "\u1153",
+ "/ko:chieuchjongseong": "\u11BE",
+ "/ko:chieuchkhieukhchoseong": "\u1152",
+ "/ko:chitueumchieuchchoseong": "\u1154",
+ "/ko:chitueumcieucchoseong": "\u114E",
+ "/ko:chitueumsioschoseong": "\u113C",
+ "/ko:chitueumssangcieucchoseong": "\u114F",
+ "/ko:chitueumssangsioschoseong": "\u113D",
+ "/ko:cieuc": "\u3148",
+ "/ko:cieucchoseong": "\u110C",
+ "/ko:cieucieungchoseong": "\u114D",
+ "/ko:cieucjongseong": "\u11BD",
+ "/ko:e": "\u3154",
+ "/ko:ejungseong": "\u1166",
+ "/ko:eo": "\u3153",
+ "/ko:eo_eujungseong": "\u117C",
+ "/ko:eojungseong": "\u1165",
+ "/ko:eoojungseong": "\u117A",
+ "/ko:eoujungseong": "\u117B",
+ "/ko:eu": "\u3161",
+ "/ko:eueujungseong": "\u1196",
+ "/ko:eujungseong": "\u1173",
+ "/ko:euujungseong": "\u1195",
+ "/ko:filler": "\u3164",
+ "/ko:fillerchoseong": "\u115F",
+ "/ko:fillerjungseong": "\u1160",
+ "/ko:hieuh": "\u314E",
+ "/ko:hieuhchoseong": "\u1112",
+ "/ko:hieuhjongseong": "\u11C2",
+ "/ko:hieuhmieumjongseong": "\u11F7",
+ "/ko:hieuhnieunjongseong": "\u11F5",
+ "/ko:hieuhpieupjongseong": "\u11F8",
+ "/ko:hieuhrieuljongseong": "\u11F6",
+ "/ko:i": "\u3163",
+ "/ko:iajungseong": "\u1198",
+ "/ko:iaraeajungseong": "\u119D",
+ "/ko:ieujungseong": "\u119C",
+ "/ko:ieung": "\u3147",
+ "/ko:ieungchieuchchoseong": "\u1149",
+ "/ko:ieungchoseong": "\u110B",
+ "/ko:ieungcieucchoseong": "\u1148",
+ "/ko:ieungjongseong": "\u11BC",
+ "/ko:ieungkhieukhjongseong": "\u11EF",
+ "/ko:ieungkiyeokchoseong": "\u1141",
+ "/ko:ieungkiyeokjongseong": "\u11EC",
+ "/ko:ieungmieumchoseong": "\u1143",
+ "/ko:ieungpansioschoseong": "\u1146",
+ "/ko:ieungphieuphchoseong": "\u114B",
+ "/ko:ieungpieupchoseong": "\u1144",
+ "/ko:ieungsioschoseong": "\u1145",
+ "/ko:ieungssangkiyeokjongseong": "\u11ED",
+ "/ko:ieungthieuthchoseong": "\u114A",
+ "/ko:ieungtikeutchoseong": "\u1142",
+ "/ko:ijungseong": "\u1175",
+ "/ko:iojungseong": "\u119A",
+ "/ko:iujungseong": "\u119B",
+ "/ko:iyajungseong": "\u1199",
+ "/ko:kapyeounmieum": "\u3171",
+ "/ko:kapyeounmieumchoseong": "\u111D",
+ "/ko:kapyeounmieumjongseong": "\u11E2",
+ "/ko:kapyeounphieuph": "\u3184",
+ "/ko:kapyeounphieuphchoseong": "\u1157",
+ "/ko:kapyeounphieuphjongseong": "\u11F4",
+ "/ko:kapyeounpieup": "\u3178",
+ "/ko:kapyeounpieupchoseong": "\u112B",
+ "/ko:kapyeounpieupjongseong": "\u11E6",
+ "/ko:kapyeounrieulchoseong": "\u111B",
+ "/ko:kapyeounssangpieup": "\u3179",
+ "/ko:kapyeounssangpieupchoseong": "\u112C",
+ "/ko:khieukh": "\u314B",
+ "/ko:khieukhchoseong": "\u110F",
+ "/ko:khieukhjongseong": "\u11BF",
+ "/ko:kiyeok": "\u3131",
+ "/ko:kiyeokchieuchjongseong": "\u11FC",
+ "/ko:kiyeokchoseong": "\u1100",
+ "/ko:kiyeokhieuhjongseong": "\u11FE",
+ "/ko:kiyeokjongseong": "\u11A8",
+ "/ko:kiyeokkhieukhjongseong": "\u11FD",
+ "/ko:kiyeoknieunjongseong": "\u11FA",
+ "/ko:kiyeokpieupjongseong": "\u11FB",
+ "/ko:kiyeokrieuljongseong": "\u11C3",
+ "/ko:kiyeoksios": "\u3133",
+ "/ko:kiyeoksiosjongseong": "\u11AA",
+ "/ko:kiyeoksioskiyeokjongseong": "\u11C4",
+ "/ko:kiyeoktikeutchoseong": "\u115A",
+ "/ko:mieum": "\u3141",
+ "/ko:mieumchieuchjongseong": "\u11E0",
+ "/ko:mieumchoseong": "\u1106",
+ "/ko:mieumhieuhjongseong": "\u11E1",
+ "/ko:mieumjongseong": "\u11B7",
+ "/ko:mieumkiyeokjongseong": "\u11DA",
+ "/ko:mieumpansios": "\u3170",
+ "/ko:mieumpansiosjongseong": "\u11DF",
+ "/ko:mieumpieup": "\u316E",
+ "/ko:mieumpieupchoseong": "\u111C",
+ "/ko:mieumpieupjongseong": "\u11DC",
+ "/ko:mieumrieuljongseong": "\u11DB",
+ "/ko:mieumsios": "\u316F",
+ "/ko:mieumsiosjongseong": "\u11DD",
+ "/ko:mieumssangsiosjongseong": "\u11DE",
+ "/ko:nieun": "\u3134",
+ "/ko:nieunchoseong": "\u1102",
+ "/ko:nieuncieuc": "\u3135",
+ "/ko:nieuncieucchoseong": "\u115C",
+ "/ko:nieuncieucjongseong": "\u11AC",
+ "/ko:nieunhieuh": "\u3136",
+ "/ko:nieunhieuhchoseong": "\u115D",
+ "/ko:nieunhieuhjongseong": "\u11AD",
+ "/ko:nieunjongseong": "\u11AB",
+ "/ko:nieunkiyeokchoseong": "\u1113",
+ "/ko:nieunkiyeokjongseong": "\u11C5",
+ "/ko:nieunpansios": "\u3168",
+ "/ko:nieunpansiosjongseong": "\u11C8",
+ "/ko:nieunpieupchoseong": "\u1116",
+ "/ko:nieunsios": "\u3167",
+ "/ko:nieunsioschoseong": "\u115B",
+ "/ko:nieunsiosjongseong": "\u11C7",
+ "/ko:nieunthieuthjongseong": "\u11C9",
+ "/ko:nieuntikeut": "\u3166",
+ "/ko:nieuntikeutchoseong": "\u1115",
+ "/ko:nieuntikeutjongseong": "\u11C6",
+ "/ko:o": "\u3157",
+ "/ko:o_ejungseong": "\u1180",
+ "/ko:o_eojungseong": "\u117F",
+ "/ko:oe": "\u315A",
+ "/ko:oejungseong": "\u116C",
+ "/ko:ojungseong": "\u1169",
+ "/ko:oojungseong": "\u1182",
+ "/ko:oujungseong": "\u1183",
+ "/ko:oyaejungseong": "\u11A7",
+ "/ko:oyajungseong": "\u11A6",
+ "/ko:oyejungseong": "\u1181",
+ "/ko:pansios": "\u317F",
+ "/ko:pansioschoseong": "\u1140",
+ "/ko:pansiosjongseong": "\u11EB",
+ "/ko:phieuph": "\u314D",
+ "/ko:phieuphchoseong": "\u1111",
+ "/ko:phieuphjongseong": "\u11C1",
+ "/ko:phieuphpieupchoseong": "\u1156",
+ "/ko:phieuphpieupjongseong": "\u11F3",
+ "/ko:pieup": "\u3142",
+ "/ko:pieupchieuchchoseong": "\u1128",
+ "/ko:pieupchoseong": "\u1107",
+ "/ko:pieupcieuc": "\u3176",
+ "/ko:pieupcieucchoseong": "\u1127",
+ "/ko:pieuphieuhjongseong": "\u11E5",
+ "/ko:pieupjongseong": "\u11B8",
+ "/ko:pieupkiyeok": "\u3172",
+ "/ko:pieupkiyeokchoseong": "\u111E",
+ "/ko:pieupnieunchoseong": "\u111F",
+ "/ko:pieupphieuphchoseong": "\u112A",
+ "/ko:pieupphieuphjongseong": "\u11E4",
+ "/ko:pieuprieuljongseong": "\u11E3",
+ "/ko:pieupsios": "\u3144",
+ "/ko:pieupsioschoseong": "\u1121",
+ "/ko:pieupsioscieucchoseong": "\u1126",
+ "/ko:pieupsiosjongseong": "\u11B9",
+ "/ko:pieupsioskiyeok": "\u3174",
+ "/ko:pieupsioskiyeokchoseong": "\u1122",
+ "/ko:pieupsiospieupchoseong": "\u1124",
+ "/ko:pieupsiostikeut": "\u3175",
+ "/ko:pieupsiostikeutchoseong": "\u1123",
+ "/ko:pieupssangsioschoseong": "\u1125",
+ "/ko:pieupthieuth": "\u3177",
+ "/ko:pieupthieuthchoseong": "\u1129",
+ "/ko:pieuptikeut": "\u3173",
+ "/ko:pieuptikeutchoseong": "\u1120",
+ "/ko:rieul": "\u3139",
+ "/ko:rieulchoseong": "\u1105",
+ "/ko:rieulhieuh": "\u3140",
+ "/ko:rieulhieuhchoseong": "\u111A",
+ "/ko:rieulhieuhjongseong": "\u11B6",
+ "/ko:rieuljongseong": "\u11AF",
+ "/ko:rieulkapyeounpieupjongseong": "\u11D5",
+ "/ko:rieulkhieukhjongseong": "\u11D8",
+ "/ko:rieulkiyeok": "\u313A",
+ "/ko:rieulkiyeokjongseong": "\u11B0",
+ "/ko:rieulkiyeoksios": "\u3169",
+ "/ko:rieulkiyeoksiosjongseong": "\u11CC",
+ "/ko:rieulmieum": "\u313B",
+ "/ko:rieulmieumjongseong": "\u11B1",
+ "/ko:rieulmieumkiyeokjongseong": "\u11D1",
+ "/ko:rieulmieumsiosjongseong": "\u11D2",
+ "/ko:rieulnieunchoseong": "\u1118",
+ "/ko:rieulnieunjongseong": "\u11CD",
+ "/ko:rieulpansios": "\u316C",
+ "/ko:rieulpansiosjongseong": "\u11D7",
+ "/ko:rieulphieuph": "\u313F",
+ "/ko:rieulphieuphjongseong": "\u11B5",
+ "/ko:rieulpieup": "\u313C",
+ "/ko:rieulpieuphieuhjongseong": "\u11D4",
+ "/ko:rieulpieupjongseong": "\u11B2",
+ "/ko:rieulpieupsios": "\u316B",
+ "/ko:rieulpieupsiosjongseong": "\u11D3",
+ "/ko:rieulsios": "\u313D",
+ "/ko:rieulsiosjongseong": "\u11B3",
+ "/ko:rieulssangsiosjongseong": "\u11D6",
+ "/ko:rieulthieuth": "\u313E",
+ "/ko:rieulthieuthjongseong": "\u11B4",
+ "/ko:rieultikeut": "\u316A",
+ "/ko:rieultikeuthieuhjongseong": "\u11CF",
+ "/ko:rieultikeutjongseong": "\u11CE",
+ "/ko:rieulyeorinhieuh": "\u316D",
+ "/ko:rieulyeorinhieuhjongseong": "\u11D9",
+ "/ko:sios": "\u3145",
+ "/ko:sioschieuchchoseong": "\u1137",
+ "/ko:sioschoseong": "\u1109",
+ "/ko:sioscieuc": "\u317E",
+ "/ko:sioscieucchoseong": "\u1136",
+ "/ko:sioshieuhchoseong": "\u113B",
+ "/ko:siosieungchoseong": "\u1135",
+ "/ko:siosjongseong": "\u11BA",
+ "/ko:sioskhieukhchoseong": "\u1138",
+ "/ko:sioskiyeok": "\u317A",
+ "/ko:sioskiyeokchoseong": "\u112D",
+ "/ko:sioskiyeokjongseong": "\u11E7",
+ "/ko:siosmieumchoseong": "\u1131",
+ "/ko:siosnieun": "\u317B",
+ "/ko:siosnieunchoseong": "\u112E",
+ "/ko:siosphieuphchoseong": "\u113A",
+ "/ko:siospieup": "\u317D",
+ "/ko:siospieupchoseong": "\u1132",
+ "/ko:siospieupjongseong": "\u11EA",
+ "/ko:siospieupkiyeokchoseong": "\u1133",
+ "/ko:siosrieulchoseong": "\u1130",
+ "/ko:siosrieuljongseong": "\u11E9",
+ "/ko:siosssangsioschoseong": "\u1134",
+ "/ko:siosthieuthchoseong": "\u1139",
+ "/ko:siostikeut": "\u317C",
+ "/ko:siostikeutchoseong": "\u112F",
+ "/ko:siostikeutjongseong": "\u11E8",
+ "/ko:ssangaraeajungseong": "\u11A2",
+ "/ko:ssangcieuc": "\u3149",
+ "/ko:ssangcieucchoseong": "\u110D",
+ "/ko:ssanghieuh": "\u3185",
+ "/ko:ssanghieuhchoseong": "\u1158",
+ "/ko:ssangieung": "\u3180",
+ "/ko:ssangieungchoseong": "\u1147",
+ "/ko:ssangieungjongseong": "\u11EE",
+ "/ko:ssangkiyeok": "\u3132",
+ "/ko:ssangkiyeokchoseong": "\u1101",
+ "/ko:ssangkiyeokjongseong": "\u11A9",
+ "/ko:ssangnieun": "\u3165",
+ "/ko:ssangnieunchoseong": "\u1114",
+ "/ko:ssangnieunjongseong": "\u11FF",
+ "/ko:ssangpieup": "\u3143",
+ "/ko:ssangpieupchoseong": "\u1108",
+ "/ko:ssangrieulchoseong": "\u1119",
+ "/ko:ssangrieuljongseong": "\u11D0",
+ "/ko:ssangsios": "\u3146",
+ "/ko:ssangsioschoseong": "\u110A",
+ "/ko:ssangsiosjongseong": "\u11BB",
+ "/ko:ssangtikeut": "\u3138",
+ "/ko:ssangtikeutchoseong": "\u1104",
+ "/ko:thieuth": "\u314C",
+ "/ko:thieuthchoseong": "\u1110",
+ "/ko:thieuthjongseong": "\u11C0",
+ "/ko:tikeut": "\u3137",
+ "/ko:tikeutchoseong": "\u1103",
+ "/ko:tikeutjongseong": "\u11AE",
+ "/ko:tikeutkiyeokchoseong": "\u1117",
+ "/ko:tikeutkiyeokjongseong": "\u11CA",
+ "/ko:tikeutrieulchoseong": "\u115E",
+ "/ko:tikeutrieuljongseong": "\u11CB",
+ "/ko:u": "\u315C",
+ "/ko:uaejungseong": "\u118A",
+ "/ko:uajungseong": "\u1189",
+ "/ko:ueo_eujungseong": "\u118B",
+ "/ko:ujungseong": "\u116E",
+ "/ko:uujungseong": "\u118D",
+ "/ko:uyejungseong": "\u118C",
+ "/ko:wa": "\u3158",
+ "/ko:wae": "\u3159",
+ "/ko:waejungseong": "\u116B",
+ "/ko:wajungseong": "\u116A",
+ "/ko:we": "\u315E",
+ "/ko:wejungseong": "\u1170",
+ "/ko:weo": "\u315D",
+ "/ko:weojungseong": "\u116F",
+ "/ko:wi": "\u315F",
+ "/ko:wijungseong": "\u1171",
+ "/ko:ya": "\u3151",
+ "/ko:yae": "\u3152",
+ "/ko:yaejungseong": "\u1164",
+ "/ko:yajungseong": "\u1163",
+ "/ko:yaojungseong": "\u1178",
+ "/ko:yaujungseong": "\u11A4",
+ "/ko:yayojungseong": "\u1179",
+ "/ko:ye": "\u3156",
+ "/ko:yejungseong": "\u1168",
+ "/ko:yeo": "\u3155",
+ "/ko:yeojungseong": "\u1167",
+ "/ko:yeoojungseong": "\u117D",
+ "/ko:yeorinhieuh": "\u3186",
+ "/ko:yeorinhieuhchoseong": "\u1159",
+ "/ko:yeorinhieuhjongseong": "\u11F9",
+ "/ko:yeoujungseong": "\u117E",
+ "/ko:yeoyajungseong": "\u11A5",
+ "/ko:yesieung": "\u3181",
+ "/ko:yesieungchoseong": "\u114C",
+ "/ko:yesieungjongseong": "\u11F0",
+ "/ko:yesieungpansios": "\u3183",
+ "/ko:yesieungpansiosjongseong": "\u11F2",
+ "/ko:yesieungsios": "\u3182",
+ "/ko:yesieungsiosjongseong": "\u11F1",
+ "/ko:yi": "\u3162",
+ "/ko:yijungseong": "\u1174",
+ "/ko:yiujungseong": "\u1197",
+ "/ko:yo": "\u315B",
+ "/ko:yoi": "\u3189",
+ "/ko:yoijungseong": "\u1188",
+ "/ko:yojungseong": "\u116D",
+ "/ko:yoojungseong": "\u1187",
+ "/ko:yoya": "\u3187",
+ "/ko:yoyae": "\u3188",
+ "/ko:yoyaejungseong": "\u1185",
+ "/ko:yoyajungseong": "\u1184",
+ "/ko:yoyeojungseong": "\u1186",
+ "/ko:yu": "\u3160",
+ "/ko:yuajungseong": "\u118E",
+ "/ko:yuejungseong": "\u1190",
+ "/ko:yueojungseong": "\u118F",
+ "/ko:yui": "\u318C",
+ "/ko:yuijungseong": "\u1194",
+ "/ko:yujungseong": "\u1172",
+ "/ko:yuujungseong": "\u1193",
+ "/ko:yuye": "\u318B",
+ "/ko:yuyejungseong": "\u1192",
+ "/ko:yuyeo": "\u318A",
+ "/ko:yuyeojungseong": "\u1191",
+ "/koala": "\u1F428",
+ "/kobliquestroke": "\uA7A3",
+ "/kocirclekatakana": "\u32D9",
+ "/kohiragana": "\u3053",
+ "/kohmfullwidth": "\u33C0",
+ "/kohmsquare": "\u33C0",
+ "/kokaithai": "\u0E01",
+ "/kokatakana": "\u30B3",
+ "/kokatakanahalfwidth": "\uFF7A",
+ "/kooposquare": "\u331E",
+ "/koppa": "\u03DF",
+ "/koppaarchaic": "\u03D9",
+ "/koppacyr": "\u0481",
+ "/koppacyrillic": "\u0481",
+ "/koreanstandardsymbol": "\u327F",
+ "/koroniscmb": "\u0343",
+ "/korunasquare": "\u331D",
+ "/kotoideographiccircled": "\u3247",
+ "/kpafullwidth": "\u33AA",
+ "/kparen": "\u24A6",
+ "/kparenthesized": "\u24A6",
+ "/kpasquare": "\u33AA",
+ "/kra": "\u0138",
+ "/ksicyr": "\u046F",
+ "/ksicyrillic": "\u046F",
+ "/kstroke": "\uA741",
+ "/kstrokediagonalstroke": "\uA745",
+ "/ktfullwidth": "\u33CF",
+ "/ktsquare": "\u33CF",
+ "/kturned": "\u029E",
+ "/kucirclekatakana": "\u32D7",
+ "/kuhiragana": "\u304F",
+ "/kukatakana": "\u30AF",
+ "/kukatakanahalfwidth": "\uFF78",
+ "/kuroonesquare": "\u331B",
+ "/kuruzeirosquare": "\u331A",
+ "/kvfullwidth": "\u33B8",
+ "/kvsquare": "\u33B8",
+ "/kwfullwidth": "\u33BE",
+ "/kwsquare": "\u33BE",
+ "/kyuriisquare": "\u3312",
+ "/l": "\u006C",
+ "/l.inferior": "\u2097",
+ "/label": "\u1F3F7",
+ "/labengali": "\u09B2",
+ "/laborideographiccircled": "\u3298",
+ "/laborideographicparen": "\u3238",
+ "/lacute": "\u013A",
+ "/ladeva": "\u0932",
+ "/ladyBeetle": "\u1F41E",
+ "/lagujarati": "\u0AB2",
+ "/lagurmukhi": "\u0A32",
+ "/lakkhangyaothai": "\u0E45",
+ "/lam": "\u0644",
+ "/lam.fina": "\uFEDE",
+ "/lam.init": "\uFEDF",
+ "/lam.init_alef.fina": "\uFEFB",
+ "/lam.init_alef.medi_hamzaabove.fina": "\uFEF7",
+ "/lam.init_alef.medi_hamzabelow.fina": "\uFEF9",
+ "/lam.init_alef.medi_maddaabove.fina": "\uFEF5",
+ "/lam.init_alefmaksura.fina": "\uFC43",
+ "/lam.init_hah.fina": "\uFC40",
+ "/lam.init_hah.medi": "\uFCCA",
+ "/lam.init_hah.medi_meem.medi": "\uFDB5",
+ "/lam.init_heh.medi": "\uFCCD",
+ "/lam.init_jeem.fina": "\uFC3F",
+ "/lam.init_jeem.medi": "\uFCC9",
+ "/lam.init_jeem.medi_jeem.medi": "\uFD83",
+ "/lam.init_jeem.medi_meem.medi": "\uFDBA",
+ "/lam.init_khah.fina": "\uFC41",
+ "/lam.init_khah.medi": "\uFCCB",
+ "/lam.init_khah.medi_meem.medi": "\uFD86",
+ "/lam.init_meem.fina": "\uFC42",
+ "/lam.init_meem.medi": "\uFCCC",
+ "/lam.init_meem.medi_hah.medi": "\uFD88",
+ "/lam.init_yeh.fina": "\uFC44",
+ "/lam.isol": "\uFEDD",
+ "/lam.medi": "\uFEE0",
+ "/lam.medi_alef.fina": "\uFEFC",
+ "/lam.medi_alef.medi_hamzaabove.fina": "\uFEF8",
+ "/lam.medi_alef.medi_hamzabelow.fina": "\uFEFA",
+ "/lam.medi_alef.medi_maddaabove.fina": "\uFEF6",
+ "/lam.medi_alefmaksura.fina": "\uFC86",
+ "/lam.medi_hah.medi_alefmaksura.fina": "\uFD82",
+ "/lam.medi_hah.medi_meem.fina": "\uFD80",
+ "/lam.medi_hah.medi_yeh.fina": "\uFD81",
+ "/lam.medi_jeem.medi_jeem.fina": "\uFD84",
+ "/lam.medi_jeem.medi_meem.fina": "\uFDBC",
+ "/lam.medi_jeem.medi_yeh.fina": "\uFDAC",
+ "/lam.medi_khah.medi_meem.fina": "\uFD85",
+ "/lam.medi_meem.fina": "\uFC85",
+ "/lam.medi_meem.medi": "\uFCED",
+ "/lam.medi_meem.medi_hah.fina": "\uFD87",
+ "/lam.medi_meem.medi_yeh.fina": "\uFDAD",
+ "/lam.medi_yeh.fina": "\uFC87",
+ "/lamBar": "\u076A",
+ "/lamVabove": "\u06B5",
+ "/lamalefabove": "\u06D9",
+ "/lamaleffinalarabic": "\uFEFC",
+ "/lamalefhamzaabovefinalarabic": "\uFEF8",
+ "/lamalefhamzaaboveisolatedarabic": "\uFEF7",
+ "/lamalefhamzabelowfinalarabic": "\uFEFA",
+ "/lamalefhamzabelowisolatedarabic": "\uFEF9",
+ "/lamalefisolatedarabic": "\uFEFB",
+ "/lamalefmaddaabovefinalarabic": "\uFEF6",
+ "/lamalefmaddaaboveisolatedarabic": "\uFEF5",
+ "/lamarabic": "\u0644",
+ "/lambda": "\u03BB",
+ "/lambdastroke": "\u019B",
+ "/lamdotabove": "\u06B6",
+ "/lamed": "\u05DC",
+ "/lamed:hb": "\u05DC",
+ "/lameddagesh": "\uFB3C",
+ "/lameddageshhebrew": "\uFB3C",
+ "/lamedhebrew": "\u05DC",
+ "/lamedholam": "\u05DC",
+ "/lamedholamdagesh": "\u05DC",
+ "/lamedholamdageshhebrew": "\u05DC",
+ "/lamedholamhebrew": "\u05DC",
+ "/lamedwide:hb": "\uFB25",
+ "/lamedwithdagesh:hb": "\uFB3C",
+ "/lamfinalarabic": "\uFEDE",
+ "/lamhahinitialarabic": "\uFCCA",
+ "/laminitialarabic": "\uFEDF",
+ "/lamjeeminitialarabic": "\uFCC9",
+ "/lamkhahinitialarabic": "\uFCCB",
+ "/lamlamhehisolatedarabic": "\uFDF2",
+ "/lammedialarabic": "\uFEE0",
+ "/lammeemhahinitialarabic": "\uFD88",
+ "/lammeeminitialarabic": "\uFCCC",
+ "/lammeemjeeminitialarabic": "\uFEDF",
+ "/lammeemkhahinitialarabic": "\uFEDF",
+ "/lamthreedotsabove": "\u06B7",
+ "/lamthreedotsbelow": "\u06B8",
+ "/lanemergeleftblack": "\u26D8",
+ "/lanemergeleftwhite": "\u26D9",
+ "/largeBlueCircle": "\u1F535",
+ "/largeBlueDiamond": "\u1F537",
+ "/largeOrangeDiamond": "\u1F536",
+ "/largeRedCircle": "\u1F534",
+ "/largecircle": "\u25EF",
+ "/largetackdown": "\u27D9",
+ "/largetackup": "\u27D8",
+ "/lari": "\u20BE",
+ "/lastQuarterMoon": "\u1F317",
+ "/lastQuarterMoonFace": "\u1F31C",
+ "/lastquartermoon": "\u263E",
+ "/layar": "\uA982",
+ "/lazysinverted": "\u223E",
+ "/lbar": "\u019A",
+ "/lbbar": "\u2114",
+ "/lbelt": "\u026C",
+ "/lbeltretroflex": "\uA78E",
+ "/lbopomofo": "\u310C",
+ "/lbroken": "\uA747",
+ "/lcaron": "\u013E",
+ "/lcedilla": "\u013C",
+ "/lcircle": "\u24DB",
+ "/lcircumflexbelow": "\u1E3D",
+ "/lcommaaccent": "\u013C",
+ "/lcurl": "\u0234",
+ "/ldblbar": "\u2C61",
+ "/ldot": "\u0140",
+ "/ldotaccent": "\u0140",
+ "/ldotbelow": "\u1E37",
+ "/ldotbelowmacron": "\u1E39",
+ "/leafFlutteringInWind": "\u1F343",
+ "/ledger": "\u1F4D2",
+ "/left-pointingMagnifyingGlass": "\u1F50D",
+ "/leftAngerBubble": "\u1F5EE",
+ "/leftFiveEighthsBlock": "\u258B",
+ "/leftHalfBlock": "\u258C",
+ "/leftHandTelephoneReceiver": "\u1F57B",
+ "/leftLuggage": "\u1F6C5",
+ "/leftOneEighthBlock": "\u258F",
+ "/leftOneQuarterBlock": "\u258E",
+ "/leftSevenEighthsBlock": "\u2589",
+ "/leftSpeechBubble": "\u1F5E8",
+ "/leftThoughtBubble": "\u1F5EC",
+ "/leftThreeEighthsBlock": "\u258D",
+ "/leftThreeQuartersBlock": "\u258A",
+ "/leftWritingHand": "\u1F58E",
+ "/leftangleabovecmb": "\u031A",
+ "/leftarrowoverrightarrow": "\u21C6",
+ "/leftdnheavyrightuplight": "\u2545",
+ "/leftharpoonoverrightharpoon": "\u21CB",
+ "/leftheavyrightdnlight": "\u252D",
+ "/leftheavyrightuplight": "\u2535",
+ "/leftheavyrightvertlight": "\u253D",
+ "/leftideographiccircled": "\u32A7",
+ "/leftlightrightdnheavy": "\u2532",
+ "/leftlightrightupheavy": "\u253A",
+ "/leftlightrightvertheavy": "\u254A",
+ "/lefttackbelowcmb": "\u0318",
+ "/lefttorightembed": "\u202A",
+ "/lefttorightisolate": "\u2066",
+ "/lefttorightmark": "\u200E",
+ "/lefttorightoverride": "\u202D",
+ "/leftupheavyrightdnlight": "\u2543",
+ "/lemon": "\u1F34B",
+ "/lenis": "\u1FBF",
+ "/lenisacute": "\u1FCE",
+ "/lenisgrave": "\u1FCD",
+ "/lenistilde": "\u1FCF",
+ "/leo": "\u264C",
+ "/leopard": "\u1F406",
+ "/less": "\u003C",
+ "/lessbutnotequal": "\u2268",
+ "/lessbutnotequivalent": "\u22E6",
+ "/lessdot": "\u22D6",
+ "/lessequal": "\u2264",
+ "/lessequalorgreater": "\u22DA",
+ "/lessmonospace": "\uFF1C",
+ "/lessorequivalent": "\u2272",
+ "/lessorgreater": "\u2276",
+ "/lessoverequal": "\u2266",
+ "/lesssmall": "\uFE64",
+ "/levelSlider": "\u1F39A",
+ "/lezh": "\u026E",
+ "/lfblock": "\u258C",
+ "/lhacyr": "\u0515",
+ "/lhookretroflex": "\u026D",
+ "/libra": "\u264E",
+ "/ligaturealeflamed:hb": "\uFB4F",
+ "/ligatureoemod": "\uA7F9",
+ "/lightCheckMark": "\u1F5F8",
+ "/lightRail": "\u1F688",
+ "/lightShade": "\u2591",
+ "/lightarcdnleft": "\u256E",
+ "/lightarcdnright": "\u256D",
+ "/lightarcupleft": "\u256F",
+ "/lightarcupright": "\u2570",
+ "/lightdbldashhorz": "\u254C",
+ "/lightdbldashvert": "\u254E",
+ "/lightdiagcross": "\u2573",
+ "/lightdiagupleftdnright": "\u2572",
+ "/lightdiaguprightdnleft": "\u2571",
+ "/lightdn": "\u2577",
+ "/lightdnhorz": "\u252C",
+ "/lightdnleft": "\u2510",
+ "/lightdnright": "\u250C",
+ "/lighthorz": "\u2500",
+ "/lightleft": "\u2574",
+ "/lightleftheavyright": "\u257C",
+ "/lightning": "\u2607",
+ "/lightningMood": "\u1F5F2",
+ "/lightningMoodBubble": "\u1F5F1",
+ "/lightquaddashhorz": "\u2508",
+ "/lightquaddashvert": "\u250A",
+ "/lightright": "\u2576",
+ "/lighttrpldashhorz": "\u2504",
+ "/lighttrpldashvert": "\u2506",
+ "/lightup": "\u2575",
+ "/lightupheavydn": "\u257D",
+ "/lightuphorz": "\u2534",
+ "/lightupleft": "\u2518",
+ "/lightupright": "\u2514",
+ "/lightvert": "\u2502",
+ "/lightverthorz": "\u253C",
+ "/lightvertleft": "\u2524",
+ "/lightvertright": "\u251C",
+ "/lineextensionhorizontal": "\u23AF",
+ "/lineextensionvertical": "\u23D0",
+ "/linemiddledotvertical": "\u237F",
+ "/lineseparator": "\u2028",
+ "/lingsapada": "\uA9C8",
+ "/link": "\u1F517",
+ "/linkedPaperclips": "\u1F587",
+ "/lips": "\u1F5E2",
+ "/lipstick": "\u1F484",
+ "/lira": "\u20A4",
+ "/litre": "\u2113",
+ "/livretournois": "\u20B6",
+ "/liwnarmenian": "\u056C",
+ "/lj": "\u01C9",
+ "/ljecyr": "\u0459",
+ "/ljecyrillic": "\u0459",
+ "/ljekomicyr": "\u0509",
+ "/ll": "\uF6C0",
+ "/lladeva": "\u0933",
+ "/llagujarati": "\u0AB3",
+ "/llinebelow": "\u1E3B",
+ "/llladeva": "\u0934",
+ "/llvocalicbengali": "\u09E1",
+ "/llvocalicdeva": "\u0961",
+ "/llvocalicvowelsignbengali": "\u09E3",
+ "/llvocalicvowelsigndeva": "\u0963",
+ "/llwelsh": "\u1EFB",
+ "/lmacrondot": "\u1E39",
+ "/lmfullwidth": "\u33D0",
+ "/lmiddletilde": "\u026B",
+ "/lmonospace": "\uFF4C",
+ "/lmsquare": "\u33D0",
+ "/lnfullwidth": "\u33D1",
+ "/lochulathai": "\u0E2C",
+ "/lock": "\u1F512",
+ "/lockInkPen": "\u1F50F",
+ "/logfullwidth": "\u33D2",
+ "/logicaland": "\u2227",
+ "/logicalandarray": "\u22C0",
+ "/logicalnot": "\u00AC",
+ "/logicalnotreversed": "\u2310",
+ "/logicalor": "\u2228",
+ "/logicalorarray": "\u22C1",
+ "/lolingthai": "\u0E25",
+ "/lollipop": "\u1F36D",
+ "/longdivision": "\u27CC",
+ "/longovershortmetrical": "\u23D2",
+ "/longovertwoshortsmetrical": "\u23D4",
+ "/longs": "\u017F",
+ "/longs_t": "\uFB05",
+ "/longsdot": "\u1E9B",
+ "/longswithdiagonalstroke": "\u1E9C",
+ "/longswithhighstroke": "\u1E9D",
+ "/longtackleft": "\u27DE",
+ "/longtackright": "\u27DD",
+ "/losslesssquare": "\u1F1A9",
+ "/loudlyCryingFace": "\u1F62D",
+ "/loveHotel": "\u1F3E9",
+ "/loveLetter": "\u1F48C",
+ "/lowBrightness": "\u1F505",
+ "/lowasterisk": "\u204E",
+ "/lowerFiveEighthsBlock": "\u2585",
+ "/lowerHalfBlock": "\u2584",
+ "/lowerLeftBallpointPen": "\u1F58A",
+ "/lowerLeftCrayon": "\u1F58D",
+ "/lowerLeftFountainPen": "\u1F58B",
+ "/lowerLeftPaintbrush": "\u1F58C",
+ "/lowerLeftPencil": "\u1F589",
+ "/lowerOneEighthBlock": "\u2581",
+ "/lowerOneQuarterBlock": "\u2582",
+ "/lowerRightShadowedWhiteCircle": "\u1F53E",
+ "/lowerSevenEighthsBlock": "\u2587",
+ "/lowerThreeEighthsBlock": "\u2583",
+ "/lowerThreeQuartersBlock": "\u2586",
+ "/lowercornerdotright": "\u27D3",
+ "/lowerhalfcircle": "\u25E1",
+ "/lowerhalfcircleinversewhite": "\u25DB",
+ "/lowerquadrantcirculararcleft": "\u25DF",
+ "/lowerquadrantcirculararcright": "\u25DE",
+ "/lowertriangleleft": "\u25FA",
+ "/lowertriangleleftblack": "\u25E3",
+ "/lowertriangleright": "\u25FF",
+ "/lowertrianglerightblack": "\u25E2",
+ "/lowideographiccircled": "\u32A6",
+ "/lowlinecenterline": "\uFE4E",
+ "/lowlinecmb": "\u0332",
+ "/lowlinedashed": "\uFE4D",
+ "/lownumeralsign": "\u0375",
+ "/lowquotedblprime": "\u301F",
+ "/lozenge": "\u25CA",
+ "/lozengedividedbyrulehorizontal": "\u27E0",
+ "/lozengesquare": "\u2311",
+ "/lparen": "\u24A7",
+ "/lparenthesized": "\u24A7",
+ "/lretroflex": "\u026D",
+ "/ls": "\u02AA",
+ "/lslash": "\u0142",
+ "/lsquare": "\u2113",
+ "/lstroke": "\uA749",
+ "/lsuperior": "\uF6EE",
+ "/lsupmod": "\u02E1",
+ "/lt:Alpha": "\u2C6D",
+ "/lt:Alphaturned": "\u2C70",
+ "/lt:Beta": "\uA7B4",
+ "/lt:Chi": "\uA7B3",
+ "/lt:Gamma": "\u0194",
+ "/lt:Iota": "\u0196",
+ "/lt:Omega": "\uA7B6",
+ "/lt:Upsilon": "\u01B1",
+ "/lt:beta": "\uA7B5",
+ "/lt:delta": "\u1E9F",
+ "/lt:omega": "\uA7B7",
+ "/ltshade": "\u2591",
+ "/lttr:bet": "\u2136",
+ "/lttr:dalet": "\u2138",
+ "/lttr:gimel": "\u2137",
+ "/lttr:gscript": "\u210A",
+ "/lturned": "\uA781",
+ "/ltypeopencircuit": "\u2390",
+ "/luhurpada": "\uA9C5",
+ "/lum": "\uA772",
+ "/lungsipada": "\uA9C9",
+ "/luthai": "\u0E26",
+ "/lvocalicbengali": "\u098C",
+ "/lvocalicdeva": "\u090C",
+ "/lvocalicvowelsignbengali": "\u09E2",
+ "/lvocalicvowelsigndeva": "\u0962",
+ "/lxfullwidth": "\u33D3",
+ "/lxsquare": "\u33D3",
+ "/lzed": "\u02AB",
+ "/m": "\u006D",
+ "/m.inferior": "\u2098",
+ "/m2fullwidth": "\u33A1",
+ "/m3fullwidth": "\u33A5",
+ "/mabengali": "\u09AE",
+ "/macirclekatakana": "\u32EE",
+ "/macron": "\u00AF",
+ "/macronbelowcmb": "\u0331",
+ "/macroncmb": "\u0304",
+ "/macronlowmod": "\u02CD",
+ "/macronmod": "\u02C9",
+ "/macronmonospace": "\uFFE3",
+ "/macute": "\u1E3F",
+ "/madda": "\u0653",
+ "/maddaabove": "\u06E4",
+ "/madeva": "\u092E",
+ "/madyapada": "\uA9C4",
+ "/mafullwidth": "\u3383",
+ "/magujarati": "\u0AAE",
+ "/magurmukhi": "\u0A2E",
+ "/mahapakhhebrew": "\u05A4",
+ "/mahapakhlefthebrew": "\u05A4",
+ "/mahhasquare": "\u3345",
+ "/mahiragana": "\u307E",
+ "/mahpach:hb": "\u05A4",
+ "/maichattawalowleftthai": "\uF895",
+ "/maichattawalowrightthai": "\uF894",
+ "/maichattawathai": "\u0E4B",
+ "/maichattawaupperleftthai": "\uF893",
+ "/maieklowleftthai": "\uF88C",
+ "/maieklowrightthai": "\uF88B",
+ "/maiekthai": "\u0E48",
+ "/maiekupperleftthai": "\uF88A",
+ "/maihanakatleftthai": "\uF884",
+ "/maihanakatthai": "\u0E31",
+ "/maikurosquare": "\u3343",
+ "/mairusquare": "\u3344",
+ "/maitaikhuleftthai": "\uF889",
+ "/maitaikhuthai": "\u0E47",
+ "/maitholowleftthai": "\uF88F",
+ "/maitholowrightthai": "\uF88E",
+ "/maithothai": "\u0E49",
+ "/maithoupperleftthai": "\uF88D",
+ "/maitrilowleftthai": "\uF892",
+ "/maitrilowrightthai": "\uF891",
+ "/maitrithai": "\u0E4A",
+ "/maitriupperleftthai": "\uF890",
+ "/maiyamokthai": "\u0E46",
+ "/makatakana": "\u30DE",
+ "/makatakanahalfwidth": "\uFF8F",
+ "/male": "\u2642",
+ "/malefemale": "\u26A5",
+ "/maleideographiccircled": "\u329A",
+ "/malestroke": "\u26A6",
+ "/malestrokemalefemale": "\u26A7",
+ "/man": "\u1F468",
+ "/manAndWomanHoldingHands": "\u1F46B",
+ "/manDancing": "\u1F57A",
+ "/manGuaPiMao": "\u1F472",
+ "/manInBusinessSuitLevitating": "\u1F574",
+ "/manTurban": "\u1F473",
+ "/manat": "\u20BC",
+ "/mansShoe": "\u1F45E",
+ "/mansyonsquare": "\u3347",
+ "/mantelpieceClock": "\u1F570",
+ "/mapleLeaf": "\u1F341",
+ "/maplighthouse": "\u26EF",
+ "/maqaf:hb": "\u05BE",
+ "/maqafhebrew": "\u05BE",
+ "/marchtelegraph": "\u32C2",
+ "/mark": "\u061C",
+ "/markerdottedraisedinterpolation": "\u2E07",
+ "/markerdottedtransposition": "\u2E08",
+ "/markerraisedinterpolation": "\u2E06",
+ "/marknoonghunna": "\u0658",
+ "/marksChapter": "\u1F545",
+ "/marriage": "\u26AD",
+ "/mars": "\u2642",
+ "/marukusquare": "\u3346",
+ "/masoraCircle:hb": "\u05AF",
+ "/masoracirclehebrew": "\u05AF",
+ "/masquare": "\u3383",
+ "/masumark": "\u303C",
+ "/math:bowtie": "\u22C8",
+ "/math:cuberoot": "\u221B",
+ "/math:fourthroot": "\u221C",
+ "/maximize": "\u1F5D6",
+ "/maytelegraph": "\u32C4",
+ "/mbfullwidth": "\u3386",
+ "/mbopomofo": "\u3107",
+ "/mbsmallfullwidth": "\u33D4",
+ "/mbsquare": "\u33D4",
+ "/mcircle": "\u24DC",
+ "/mcubedsquare": "\u33A5",
+ "/mdot": "\u1E41",
+ "/mdotaccent": "\u1E41",
+ "/mdotbelow": "\u1E43",
+ "/measuredangle": "\u2221",
+ "/measuredby": "\u225E",
+ "/meatOnBone": "\u1F356",
+ "/mecirclekatakana": "\u32F1",
+ "/medicineideographiccircled": "\u32A9",
+ "/mediumShade": "\u2592",
+ "/mediumcircleblack": "\u26AB",
+ "/mediumcirclewhite": "\u26AA",
+ "/mediummathematicalspace": "\u205F",
+ "/mediumsmallcirclewhite": "\u26AC",
+ "/meem": "\u0645",
+ "/meem.fina": "\uFEE2",
+ "/meem.init": "\uFEE3",
+ "/meem.init_alefmaksura.fina": "\uFC49",
+ "/meem.init_hah.fina": "\uFC46",
+ "/meem.init_hah.medi": "\uFCCF",
+ "/meem.init_hah.medi_jeem.medi": "\uFD89",
+ "/meem.init_hah.medi_meem.medi": "\uFD8A",
+ "/meem.init_jeem.fina": "\uFC45",
+ "/meem.init_jeem.medi": "\uFCCE",
+ "/meem.init_jeem.medi_hah.medi": "\uFD8C",
+ "/meem.init_jeem.medi_khah.medi": "\uFD92",
+ "/meem.init_jeem.medi_meem.medi": "\uFD8D",
+ "/meem.init_khah.fina": "\uFC47",
+ "/meem.init_khah.medi": "\uFCD0",
+ "/meem.init_khah.medi_jeem.medi": "\uFD8E",
+ "/meem.init_khah.medi_meem.medi": "\uFD8F",
+ "/meem.init_meem.fina": "\uFC48",
+ "/meem.init_meem.medi": "\uFCD1",
+ "/meem.init_yeh.fina": "\uFC4A",
+ "/meem.isol": "\uFEE1",
+ "/meem.medi": "\uFEE4",
+ "/meem.medi_alef.fina": "\uFC88",
+ "/meem.medi_hah.medi_yeh.fina": "\uFD8B",
+ "/meem.medi_jeem.medi_yeh.fina": "\uFDC0",
+ "/meem.medi_khah.medi_yeh.fina": "\uFDB9",
+ "/meem.medi_meem.fina": "\uFC89",
+ "/meem.medi_meem.medi_yeh.fina": "\uFDB1",
+ "/meemDotAbove": "\u0765",
+ "/meemDotBelow": "\u0766",
+ "/meemabove": "\u06E2",
+ "/meemabove.init": "\u06D8",
+ "/meemarabic": "\u0645",
+ "/meembelow": "\u06ED",
+ "/meemfinalarabic": "\uFEE2",
+ "/meeminitialarabic": "\uFEE3",
+ "/meemmedialarabic": "\uFEE4",
+ "/meemmeeminitialarabic": "\uFCD1",
+ "/meemmeemisolatedarabic": "\uFC48",
+ "/meetorusquare": "\u334D",
+ "/megasquare": "\u334B",
+ "/megatonsquare": "\u334C",
+ "/mehiragana": "\u3081",
+ "/meizierasquare": "\u337E",
+ "/mekatakana": "\u30E1",
+ "/mekatakanahalfwidth": "\uFF92",
+ "/melon": "\u1F348",
+ "/mem": "\u05DE",
+ "/mem:hb": "\u05DE",
+ "/memdagesh": "\uFB3E",
+ "/memdageshhebrew": "\uFB3E",
+ "/memhebrew": "\u05DE",
+ "/memo": "\u1F4DD",
+ "/memwithdagesh:hb": "\uFB3E",
+ "/menarmenian": "\u0574",
+ "/menorahNineBranches": "\u1F54E",
+ "/menpostSindhi": "\u06FE",
+ "/mens": "\u1F6B9",
+ "/mepigraphicinverted": "\uA7FD",
+ "/mercha:hb": "\u05A5",
+ "/merchaKefulah:hb": "\u05A6",
+ "/mercury": "\u263F",
+ "/merkhahebrew": "\u05A5",
+ "/merkhakefulahebrew": "\u05A6",
+ "/merkhakefulalefthebrew": "\u05A6",
+ "/merkhalefthebrew": "\u05A5",
+ "/metalideographiccircled": "\u328E",
+ "/metalideographicparen": "\u322E",
+ "/meteg:hb": "\u05BD",
+ "/metro": "\u1F687",
+ "/mgfullwidth": "\u338E",
+ "/mhook": "\u0271",
+ "/mhzfullwidth": "\u3392",
+ "/mhzsquare": "\u3392",
+ "/micirclekatakana": "\u32EF",
+ "/microphone": "\u1F3A4",
+ "/microscope": "\u1F52C",
+ "/middledotkatakanahalfwidth": "\uFF65",
+ "/middot": "\u00B7",
+ "/mieumacirclekorean": "\u3272",
+ "/mieumaparenkorean": "\u3212",
+ "/mieumcirclekorean": "\u3264",
+ "/mieumkorean": "\u3141",
+ "/mieumpansioskorean": "\u3170",
+ "/mieumparenkorean": "\u3204",
+ "/mieumpieupkorean": "\u316E",
+ "/mieumsioskorean": "\u316F",
+ "/mihiragana": "\u307F",
+ "/mikatakana": "\u30DF",
+ "/mikatakanahalfwidth": "\uFF90",
+ "/mikuronsquare": "\u3348",
+ "/milfullwidth": "\u33D5",
+ "/militaryMedal": "\u1F396",
+ "/milkyWay": "\u1F30C",
+ "/mill": "\u20A5",
+ "/millionscmbcyr": "\u0489",
+ "/millisecond": "\u2034",
+ "/millisecondreversed": "\u2037",
+ "/minibus": "\u1F690",
+ "/minidisc": "\u1F4BD",
+ "/minimize": "\u1F5D5",
+ "/minus": "\u2212",
+ "/minus.inferior": "\u208B",
+ "/minus.superior": "\u207B",
+ "/minusbelowcmb": "\u0320",
+ "/minuscircle": "\u2296",
+ "/minusmod": "\u02D7",
+ "/minusplus": "\u2213",
+ "/minussignmod": "\u02D7",
+ "/minustilde": "\u2242",
+ "/minute": "\u2032",
+ "/minutereversed": "\u2035",
+ "/miribaarusquare": "\u334A",
+ "/mirisquare": "\u3349",
+ "/misc:baby": "\u1F476",
+ "/misc:bell": "\u1F514",
+ "/misc:dash": "\u1F4A8",
+ "/misc:decimalseparator": "\u2396",
+ "/misc:diamondblack": "\u2666",
+ "/misc:diamondwhite": "\u2662",
+ "/misc:ear": "\u1F442",
+ "/misc:om": "\u1F549",
+ "/misc:ring": "\u1F48D",
+ "/misra": "\u060F",
+ "/mlfullwidth": "\u3396",
+ "/mlonglegturned": "\u0270",
+ "/mlsquare": "\u3396",
+ "/mlym:a": "\u0D05",
+ "/mlym:aa": "\u0D06",
+ "/mlym:aasign": "\u0D3E",
+ "/mlym:ai": "\u0D10",
+ "/mlym:aisign": "\u0D48",
+ "/mlym:anusvarasign": "\u0D02",
+ "/mlym:archaicii": "\u0D5F",
+ "/mlym:au": "\u0D14",
+ "/mlym:aulength": "\u0D57",
+ "/mlym:ausign": "\u0D4C",
+ "/mlym:avagrahasign": "\u0D3D",
+ "/mlym:ba": "\u0D2C",
+ "/mlym:bha": "\u0D2D",
+ "/mlym:ca": "\u0D1A",
+ "/mlym:candrabindusign": "\u0D01",
+ "/mlym:cha": "\u0D1B",
+ "/mlym:circularviramasign": "\u0D3C",
+ "/mlym:combininganusvaraabovesign": "\u0D00",
+ "/mlym:da": "\u0D26",
+ "/mlym:date": "\u0D79",
+ "/mlym:dda": "\u0D21",
+ "/mlym:ddha": "\u0D22",
+ "/mlym:dha": "\u0D27",
+ "/mlym:dotreph": "\u0D4E",
+ "/mlym:e": "\u0D0E",
+ "/mlym:ee": "\u0D0F",
+ "/mlym:eesign": "\u0D47",
+ "/mlym:eight": "\u0D6E",
+ "/mlym:esign": "\u0D46",
+ "/mlym:five": "\u0D6B",
+ "/mlym:four": "\u0D6A",
+ "/mlym:ga": "\u0D17",
+ "/mlym:gha": "\u0D18",
+ "/mlym:ha": "\u0D39",
+ "/mlym:i": "\u0D07",
+ "/mlym:ii": "\u0D08",
+ "/mlym:iisign": "\u0D40",
+ "/mlym:isign": "\u0D3F",
+ "/mlym:ja": "\u0D1C",
+ "/mlym:jha": "\u0D1D",
+ "/mlym:ka": "\u0D15",
+ "/mlym:kchillu": "\u0D7F",
+ "/mlym:kha": "\u0D16",
+ "/mlym:la": "\u0D32",
+ "/mlym:lchillu": "\u0D7D",
+ "/mlym:lla": "\u0D33",
+ "/mlym:llchillu": "\u0D7E",
+ "/mlym:llla": "\u0D34",
+ "/mlym:lllchillu": "\u0D56",
+ "/mlym:llvocal": "\u0D61",
+ "/mlym:llvocalsign": "\u0D63",
+ "/mlym:lvocal": "\u0D0C",
+ "/mlym:lvocalsign": "\u0D62",
+ "/mlym:ma": "\u0D2E",
+ "/mlym:mchillu": "\u0D54",
+ "/mlym:na": "\u0D28",
+ "/mlym:nchillu": "\u0D7B",
+ "/mlym:nga": "\u0D19",
+ "/mlym:nine": "\u0D6F",
+ "/mlym:nna": "\u0D23",
+ "/mlym:nnchillu": "\u0D7A",
+ "/mlym:nnna": "\u0D29",
+ "/mlym:nya": "\u0D1E",
+ "/mlym:o": "\u0D12",
+ "/mlym:one": "\u0D67",
+ "/mlym:oneeighth": "\u0D77",
+ "/mlym:onefifth": "\u0D5E",
+ "/mlym:onefortieth": "\u0D59",
+ "/mlym:onehalf": "\u0D74",
+ "/mlym:onehundred": "\u0D71",
+ "/mlym:oneone-hundred-and-sixtieth": "\u0D58",
+ "/mlym:onequarter": "\u0D73",
+ "/mlym:onesixteenth": "\u0D76",
+ "/mlym:onetenth": "\u0D5C",
+ "/mlym:onethousand": "\u0D72",
+ "/mlym:onetwentieth": "\u0D5B",
+ "/mlym:oo": "\u0D13",
+ "/mlym:oosign": "\u0D4B",
+ "/mlym:osign": "\u0D4A",
+ "/mlym:pa": "\u0D2A",
+ "/mlym:parasign": "\u0D4F",
+ "/mlym:pha": "\u0D2B",
+ "/mlym:ra": "\u0D30",
+ "/mlym:rra": "\u0D31",
+ "/mlym:rrchillu": "\u0D7C",
+ "/mlym:rrvocal": "\u0D60",
+ "/mlym:rrvocalsign": "\u0D44",
+ "/mlym:rvocal": "\u0D0B",
+ "/mlym:rvocalsign": "\u0D43",
+ "/mlym:sa": "\u0D38",
+ "/mlym:seven": "\u0D6D",
+ "/mlym:sha": "\u0D36",
+ "/mlym:six": "\u0D6C",
+ "/mlym:ssa": "\u0D37",
+ "/mlym:ta": "\u0D24",
+ "/mlym:ten": "\u0D70",
+ "/mlym:tha": "\u0D25",
+ "/mlym:three": "\u0D69",
+ "/mlym:threeeightieths": "\u0D5A",
+ "/mlym:threequarters": "\u0D75",
+ "/mlym:threesixteenths": "\u0D78",
+ "/mlym:threetwentieths": "\u0D5D",
+ "/mlym:tta": "\u0D1F",
+ "/mlym:ttha": "\u0D20",
+ "/mlym:ttta": "\u0D3A",
+ "/mlym:two": "\u0D68",
+ "/mlym:u": "\u0D09",
+ "/mlym:usign": "\u0D41",
+ "/mlym:uu": "\u0D0A",
+ "/mlym:uusign": "\u0D42",
+ "/mlym:va": "\u0D35",
+ "/mlym:verticalbarviramasign": "\u0D3B",
+ "/mlym:viramasign": "\u0D4D",
+ "/mlym:visargasign": "\u0D03",
+ "/mlym:ya": "\u0D2F",
+ "/mlym:ychillu": "\u0D55",
+ "/mlym:zero": "\u0D66",
+ "/mm2fullwidth": "\u339F",
+ "/mm3fullwidth": "\u33A3",
+ "/mmcubedsquare": "\u33A3",
+ "/mmfullwidth": "\u339C",
+ "/mmonospace": "\uFF4D",
+ "/mmsquaredsquare": "\u339F",
+ "/mobilePhone": "\u1F4F1",
+ "/mobilePhoneOff": "\u1F4F4",
+ "/mobilePhoneRightwardsArrowAtLeft": "\u1F4F2",
+ "/mocirclekatakana": "\u32F2",
+ "/models": "\u22A7",
+ "/mohiragana": "\u3082",
+ "/mohmfullwidth": "\u33C1",
+ "/mohmsquare": "\u33C1",
+ "/mokatakana": "\u30E2",
+ "/mokatakanahalfwidth": "\uFF93",
+ "/molfullwidth": "\u33D6",
+ "/molsquare": "\u33D6",
+ "/momathai": "\u0E21",
+ "/moneyBag": "\u1F4B0",
+ "/moneyWings": "\u1F4B8",
+ "/mong:a": "\u1820",
+ "/mong:aaligali": "\u1887",
+ "/mong:ahaligali": "\u1897",
+ "/mong:ang": "\u1829",
+ "/mong:angsibe": "\u1862",
+ "/mong:angtodo": "\u184A",
+ "/mong:anusvaraonealigali": "\u1880",
+ "/mong:ba": "\u182A",
+ "/mong:baludaaligali": "\u1885",
+ "/mong:baludaaligalithree": "\u1886",
+ "/mong:batodo": "\u184B",
+ "/mong:bhamanchualigali": "\u18A8",
+ "/mong:birga": "\u1800",
+ "/mong:caaligali": "\u188B",
+ "/mong:camanchualigali": "\u189C",
+ "/mong:cha": "\u1834",
+ "/mong:chasibe": "\u1871",
+ "/mong:chatodo": "\u1852",
+ "/mong:chi": "\u1842",
+ "/mong:colon": "\u1804",
+ "/mong:comma": "\u1802",
+ "/mong:commamanchu": "\u1808",
+ "/mong:cyamanchualigali": "\u18A3",
+ "/mong:da": "\u1833",
+ "/mong:daaligali": "\u1891",
+ "/mong:dagalgaaligali": "\u18A9",
+ "/mong:damarualigali": "\u1882",
+ "/mong:dasibe": "\u1869",
+ "/mong:datodo": "\u1851",
+ "/mong:ddaaligali": "\u188E",
+ "/mong:ddhamanchualigali": "\u189F",
+ "/mong:dhamanchualigali": "\u18A1",
+ "/mong:dzatodo": "\u185C",
+ "/mong:e": "\u1821",
+ "/mong:ee": "\u1827",
+ "/mong:eight": "\u1818",
+ "/mong:ellipsis": "\u1801",
+ "/mong:esibe": "\u185D",
+ "/mong:etodo": "\u1844",
+ "/mong:fa": "\u1839",
+ "/mong:famanchu": "\u1876",
+ "/mong:fasibe": "\u186B",
+ "/mong:five": "\u1815",
+ "/mong:four": "\u1814",
+ "/mong:fourdots": "\u1805",
+ "/mong:freevariationselectorone": "\u180B",
+ "/mong:freevariationselectorthree": "\u180D",
+ "/mong:freevariationselectortwo": "\u180C",
+ "/mong:ga": "\u182D",
+ "/mong:gaasibe": "\u186C",
+ "/mong:gaatodo": "\u1858",
+ "/mong:gasibe": "\u1864",
+ "/mong:gatodo": "\u184E",
+ "/mong:ghamanchualigali": "\u189A",
+ "/mong:haa": "\u183E",
+ "/mong:haasibe": "\u186D",
+ "/mong:haatodo": "\u1859",
+ "/mong:hasibe": "\u1865",
+ "/mong:i": "\u1822",
+ "/mong:ialigali": "\u1888",
+ "/mong:imanchu": "\u1873",
+ "/mong:isibe": "\u185E",
+ "/mong:itodo": "\u1845",
+ "/mong:iysibe": "\u185F",
+ "/mong:ja": "\u1835",
+ "/mong:jasibe": "\u186A",
+ "/mong:jatodo": "\u1853",
+ "/mong:jhamanchualigali": "\u189D",
+ "/mong:jiatodo": "\u185A",
+ "/mong:ka": "\u183A",
+ "/mong:kaaligali": "\u1889",
+ "/mong:kamanchu": "\u1874",
+ "/mong:kasibe": "\u1863",
+ "/mong:katodo": "\u1857",
+ "/mong:kha": "\u183B",
+ "/mong:la": "\u182F",
+ "/mong:lha": "\u1840",
+ "/mong:lhamanchualigali": "\u18AA",
+ "/mong:longvowelsigntodo": "\u1843",
+ "/mong:ma": "\u182E",
+ "/mong:matodo": "\u184F",
+ "/mong:na": "\u1828",
+ "/mong:ngaaligali": "\u188A",
+ "/mong:ngamanchualigali": "\u189B",
+ "/mong:niatodo": "\u185B",
+ "/mong:nine": "\u1819",
+ "/mong:nirugu": "\u180A",
+ "/mong:nnaaligali": "\u188F",
+ "/mong:o": "\u1823",
+ "/mong:oe": "\u1825",
+ "/mong:oetodo": "\u1848",
+ "/mong:one": "\u1811",
+ "/mong:otodo": "\u1846",
+ "/mong:pa": "\u182B",
+ "/mong:paaligali": "\u1892",
+ "/mong:pasibe": "\u1866",
+ "/mong:patodo": "\u184C",
+ "/mong:period": "\u1803",
+ "/mong:periodmanchu": "\u1809",
+ "/mong:phaaligali": "\u1893",
+ "/mong:qa": "\u182C",
+ "/mong:qatodo": "\u184D",
+ "/mong:ra": "\u1837",
+ "/mong:raasibe": "\u1870",
+ "/mong:ramanchu": "\u1875",
+ "/mong:sa": "\u1830",
+ "/mong:seven": "\u1817",
+ "/mong:sha": "\u1831",
+ "/mong:shasibe": "\u1867",
+ "/mong:six": "\u1816",
+ "/mong:softhyphentodo": "\u1806",
+ "/mong:ssaaligali": "\u1894",
+ "/mong:ssamanchualigali": "\u18A2",
+ "/mong:syllableboundarymarkersibe": "\u1807",
+ "/mong:ta": "\u1832",
+ "/mong:taaligali": "\u1890",
+ "/mong:tamanchualigali": "\u18A0",
+ "/mong:tasibe": "\u1868",
+ "/mong:tatodo": "\u1850",
+ "/mong:tatodoaligali": "\u1898",
+ "/mong:three": "\u1813",
+ "/mong:tsa": "\u183C",
+ "/mong:tsasibe": "\u186E",
+ "/mong:tsatodo": "\u1854",
+ "/mong:ttaaligali": "\u188C",
+ "/mong:ttamanchualigali": "\u189E",
+ "/mong:tthaaligali": "\u188D",
+ "/mong:two": "\u1812",
+ "/mong:u": "\u1824",
+ "/mong:ualigalihalf": "\u18A6",
+ "/mong:ubadamaaligali": "\u1883",
+ "/mong:ubadamaaligaliinverted": "\u1884",
+ "/mong:ue": "\u1826",
+ "/mong:uesibe": "\u1860",
+ "/mong:uetodo": "\u1849",
+ "/mong:usibe": "\u1861",
+ "/mong:utodo": "\u1847",
+ "/mong:visargaonealigali": "\u1881",
+ "/mong:vowelseparator": "\u180E",
+ "/mong:wa": "\u1838",
+ "/mong:watodo": "\u1856",
+ "/mong:ya": "\u1836",
+ "/mong:yaaligalihalf": "\u18A7",
+ "/mong:yatodo": "\u1855",
+ "/mong:za": "\u183D",
+ "/mong:zaaligali": "\u1896",
+ "/mong:zamanchualigali": "\u18A5",
+ "/mong:zasibe": "\u186F",
+ "/mong:zero": "\u1810",
+ "/mong:zhaaligali": "\u1895",
+ "/mong:zhamanchu": "\u1877",
+ "/mong:zhamanchualigali": "\u18A4",
+ "/mong:zhasibe": "\u1872",
+ "/mong:zhatodoaligali": "\u1899",
+ "/mong:zhi": "\u1841",
+ "/mong:zra": "\u183F",
+ "/monkey": "\u1F412",
+ "/monkeyFace": "\u1F435",
+ "/monogramyang": "\u268A",
+ "/monogramyin": "\u268B",
+ "/monorail": "\u1F69D",
+ "/monostable": "\u238D",
+ "/moodBubble": "\u1F5F0",
+ "/moonViewingCeremony": "\u1F391",
+ "/moonideographiccircled": "\u328A",
+ "/moonideographicparen": "\u322A",
+ "/moonlilithblack": "\u26B8",
+ "/mosque": "\u1F54C",
+ "/motorBoat": "\u1F6E5",
+ "/motorScooter": "\u1F6F5",
+ "/motorway": "\u1F6E3",
+ "/mountFuji": "\u1F5FB",
+ "/mountain": "\u26F0",
+ "/mountainBicyclist": "\u1F6B5",
+ "/mountainCableway": "\u1F6A0",
+ "/mountainRailway": "\u1F69E",
+ "/mouse": "\u1F401",
+ "/mouseFace": "\u1F42D",
+ "/mouth": "\u1F444",
+ "/movers2fullwidth": "\u33A8",
+ "/moversfullwidth": "\u33A7",
+ "/moverssquare": "\u33A7",
+ "/moverssquaredsquare": "\u33A8",
+ "/movieCamera": "\u1F3A5",
+ "/moyai": "\u1F5FF",
+ "/mpafullwidth": "\u33AB",
+ "/mparen": "\u24A8",
+ "/mparenthesized": "\u24A8",
+ "/mpasquare": "\u33AB",
+ "/msfullwidth": "\u33B3",
+ "/mssquare": "\u33B3",
+ "/msuperior": "\uF6EF",
+ "/mturned": "\u026F",
+ "/mu": "\u00B5",
+ "/mu.math": "\u00B5",
+ "/mu1": "\u00B5",
+ "/muafullwidth": "\u3382",
+ "/muasquare": "\u3382",
+ "/muchgreater": "\u226B",
+ "/muchless": "\u226A",
+ "/mucirclekatakana": "\u32F0",
+ "/muffullwidth": "\u338C",
+ "/mufsquare": "\u338C",
+ "/mugfullwidth": "\u338D",
+ "/mugreek": "\u03BC",
+ "/mugsquare": "\u338D",
+ "/muhiragana": "\u3080",
+ "/mukatakana": "\u30E0",
+ "/mukatakanahalfwidth": "\uFF91",
+ "/mulfullwidth": "\u3395",
+ "/mulsquare": "\u3395",
+ "/multimap": "\u22B8",
+ "/multimapleft": "\u27DC",
+ "/multipleMusicalNotes": "\u1F3B6",
+ "/multiply": "\u00D7",
+ "/multiset": "\u228C",
+ "/multisetmultiplication": "\u228D",
+ "/multisetunion": "\u228E",
+ "/mum": "\uA773",
+ "/mumfullwidth": "\u339B",
+ "/mumsquare": "\u339B",
+ "/munach:hb": "\u05A3",
+ "/munahhebrew": "\u05A3",
+ "/munahlefthebrew": "\u05A3",
+ "/musfullwidth": "\u33B2",
+ "/mushroom": "\u1F344",
+ "/musicalKeyboard": "\u1F3B9",
+ "/musicalKeyboardJacks": "\u1F398",
+ "/musicalNote": "\u1F3B5",
+ "/musicalScore": "\u1F3BC",
+ "/musicalnote": "\u266A",
+ "/musicalnotedbl": "\u266B",
+ "/musicflat": "\u266D",
+ "/musicflatsign": "\u266D",
+ "/musicnatural": "\u266E",
+ "/musicsharp": "\u266F",
+ "/musicsharpsign": "\u266F",
+ "/mussquare": "\u33B2",
+ "/muvfullwidth": "\u33B6",
+ "/muvsquare": "\u33B6",
+ "/muwfullwidth": "\u33BC",
+ "/muwsquare": "\u33BC",
+ "/mvfullwidth": "\u33B7",
+ "/mvmegafullwidth": "\u33B9",
+ "/mvmegasquare": "\u33B9",
+ "/mvsquare": "\u33B7",
+ "/mwfullwidth": "\u33BD",
+ "/mwmegafullwidth": "\u33BF",
+ "/mwmegasquare": "\u33BF",
+ "/mwsquare": "\u33BD",
+ "/n": "\u006E",
+ "/n.inferior": "\u2099",
+ "/n.superior": "\u207F",
+ "/nabengali": "\u09A8",
+ "/nabla": "\u2207",
+ "/nacirclekatakana": "\u32E4",
+ "/nacute": "\u0144",
+ "/nadeva": "\u0928",
+ "/nafullwidth": "\u3381",
+ "/nagujarati": "\u0AA8",
+ "/nagurmukhi": "\u0A28",
+ "/nahiragana": "\u306A",
+ "/nailPolish": "\u1F485",
+ "/naira": "\u20A6",
+ "/nakatakana": "\u30CA",
+ "/nakatakanahalfwidth": "\uFF85",
+ "/nameBadge": "\u1F4DB",
+ "/nameideographiccircled": "\u3294",
+ "/nameideographicparen": "\u3234",
+ "/namurda": "\uA99F",
+ "/nand": "\u22BC",
+ "/nanosquare": "\u3328",
+ "/napostrophe": "\u0149",
+ "/narrownobreakspace": "\u202F",
+ "/nasquare": "\u3381",
+ "/nationalPark": "\u1F3DE",
+ "/nationaldigitshapes": "\u206E",
+ "/nbopomofo": "\u310B",
+ "/nbspace": "\u00A0",
+ "/ncaron": "\u0148",
+ "/ncedilla": "\u0146",
+ "/ncircle": "\u24DD",
+ "/ncircumflexbelow": "\u1E4B",
+ "/ncommaaccent": "\u0146",
+ "/ncurl": "\u0235",
+ "/ndescender": "\uA791",
+ "/ndot": "\u1E45",
+ "/ndotaccent": "\u1E45",
+ "/ndotbelow": "\u1E47",
+ "/necirclekatakana": "\u32E7",
+ "/necktie": "\u1F454",
+ "/negatedturnstiledblverticalbarright": "\u22AF",
+ "/nehiragana": "\u306D",
+ "/neirapproximatelynoractuallyequal": "\u2247",
+ "/neirasersetnorequalup": "\u2289",
+ "/neirasubsetnorequal": "\u2288",
+ "/neirgreaternorequal": "\u2271",
+ "/neirgreaternorequivalent": "\u2275",
+ "/neirgreaternorless": "\u2279",
+ "/neirlessnorequal": "\u2270",
+ "/neirlessnorequivalent": "\u2274",
+ "/neirlessnorgreater": "\u2278",
+ "/nekatakana": "\u30CD",
+ "/nekatakanahalfwidth": "\uFF88",
+ "/neptune": "\u2646",
+ "/neuter": "\u26B2",
+ "/neutralFace": "\u1F610",
+ "/newMoon": "\u1F311",
+ "/newMoonFace": "\u1F31A",
+ "/newsheqel": "\u20AA",
+ "/newsheqelsign": "\u20AA",
+ "/newspaper": "\u1F4F0",
+ "/newsquare": "\u1F195",
+ "/nextpage": "\u2398",
+ "/nffullwidth": "\u338B",
+ "/nfsquare": "\u338B",
+ "/ng.fina": "\uFBD4",
+ "/ng.init": "\uFBD5",
+ "/ng.isol": "\uFBD3",
+ "/ng.medi": "\uFBD6",
+ "/ngabengali": "\u0999",
+ "/ngadeva": "\u0919",
+ "/ngagujarati": "\u0A99",
+ "/ngagurmukhi": "\u0A19",
+ "/ngalelet": "\uA98A",
+ "/ngaleletraswadi": "\uA98B",
+ "/ngoeh": "\u06B1",
+ "/ngoeh.fina": "\uFB9B",
+ "/ngoeh.init": "\uFB9C",
+ "/ngoeh.isol": "\uFB9A",
+ "/ngoeh.medi": "\uFB9D",
+ "/ngonguthai": "\u0E07",
+ "/ngrave": "\u01F9",
+ "/ngsquare": "\u1F196",
+ "/nhiragana": "\u3093",
+ "/nhookleft": "\u0272",
+ "/nhookretroflex": "\u0273",
+ "/nicirclekatakana": "\u32E5",
+ "/nieunacirclekorean": "\u326F",
+ "/nieunaparenkorean": "\u320F",
+ "/nieuncieuckorean": "\u3135",
+ "/nieuncirclekorean": "\u3261",
+ "/nieunhieuhkorean": "\u3136",
+ "/nieunkorean": "\u3134",
+ "/nieunpansioskorean": "\u3168",
+ "/nieunparenkorean": "\u3201",
+ "/nieunsioskorean": "\u3167",
+ "/nieuntikeutkorean": "\u3166",
+ "/nightStars": "\u1F303",
+ "/nightideographiccircled": "\u32B0",
+ "/nihiragana": "\u306B",
+ "/nikatakana": "\u30CB",
+ "/nikatakanahalfwidth": "\uFF86",
+ "/nikhahitleftthai": "\uF899",
+ "/nikhahitthai": "\u0E4D",
+ "/nine": "\u0039",
+ "/nine.inferior": "\u2089",
+ "/nine.roman": "\u2168",
+ "/nine.romansmall": "\u2178",
+ "/nine.superior": "\u2079",
+ "/ninearabic": "\u0669",
+ "/ninebengali": "\u09EF",
+ "/ninecircle": "\u2468",
+ "/ninecircledbl": "\u24FD",
+ "/ninecircleinversesansserif": "\u2792",
+ "/ninecomma": "\u1F10A",
+ "/ninedeva": "\u096F",
+ "/ninefar": "\u06F9",
+ "/ninegujarati": "\u0AEF",
+ "/ninegurmukhi": "\u0A6F",
+ "/ninehackarabic": "\u0669",
+ "/ninehangzhou": "\u3029",
+ "/nineideographiccircled": "\u3288",
+ "/nineideographicparen": "\u3228",
+ "/nineinferior": "\u2089",
+ "/ninemonospace": "\uFF19",
+ "/nineoldstyle": "\uF739",
+ "/nineparen": "\u247C",
+ "/nineparenthesized": "\u247C",
+ "/nineperiod": "\u2490",
+ "/ninepersian": "\u06F9",
+ "/nineroman": "\u2178",
+ "/ninesuperior": "\u2079",
+ "/nineteencircle": "\u2472",
+ "/nineteencircleblack": "\u24F3",
+ "/nineteenparen": "\u2486",
+ "/nineteenparenthesized": "\u2486",
+ "/nineteenperiod": "\u249A",
+ "/ninethai": "\u0E59",
+ "/nj": "\u01CC",
+ "/njecyr": "\u045A",
+ "/njecyrillic": "\u045A",
+ "/njekomicyr": "\u050B",
+ "/nkatakana": "\u30F3",
+ "/nkatakanahalfwidth": "\uFF9D",
+ "/nlegrightlong": "\u019E",
+ "/nlinebelow": "\u1E49",
+ "/nlongrightleg": "\u019E",
+ "/nmbr:oneeighth": "\u215B",
+ "/nmbr:onefifth": "\u2155",
+ "/nmbr:onetenth": "\u2152",
+ "/nmfullwidth": "\u339A",
+ "/nmonospace": "\uFF4E",
+ "/nmsquare": "\u339A",
+ "/nnabengali": "\u09A3",
+ "/nnadeva": "\u0923",
+ "/nnagujarati": "\u0AA3",
+ "/nnagurmukhi": "\u0A23",
+ "/nnnadeva": "\u0929",
+ "/noBicycles": "\u1F6B3",
+ "/noEntrySign": "\u1F6AB",
+ "/noMobilePhones": "\u1F4F5",
+ "/noOneUnderEighteen": "\u1F51E",
+ "/noPedestrians": "\u1F6B7",
+ "/noPiracy": "\u1F572",
+ "/noSmoking": "\u1F6AD",
+ "/nobliquestroke": "\uA7A5",
+ "/nocirclekatakana": "\u32E8",
+ "/nodeascending": "\u260A",
+ "/nodedescending": "\u260B",
+ "/noentry": "\u26D4",
+ "/nohiragana": "\u306E",
+ "/nokatakana": "\u30CE",
+ "/nokatakanahalfwidth": "\uFF89",
+ "/nominaldigitshapes": "\u206F",
+ "/nonPotableWater": "\u1F6B1",
+ "/nonbreakinghyphen": "\u2011",
+ "/nonbreakingspace": "\u00A0",
+ "/nonenthai": "\u0E13",
+ "/nonuthai": "\u0E19",
+ "/noon": "\u0646",
+ "/noon.fina": "\uFEE6",
+ "/noon.init": "\uFEE7",
+ "/noon.init_alefmaksura.fina": "\uFC4F",
+ "/noon.init_hah.fina": "\uFC4C",
+ "/noon.init_hah.medi": "\uFCD3",
+ "/noon.init_hah.medi_meem.medi": "\uFD95",
+ "/noon.init_heh.medi": "\uFCD6",
+ "/noon.init_jeem.fina": "\uFC4B",
+ "/noon.init_jeem.medi": "\uFCD2",
+ "/noon.init_jeem.medi_hah.medi": "\uFDB8",
+ "/noon.init_jeem.medi_meem.medi": "\uFD98",
+ "/noon.init_khah.fina": "\uFC4D",
+ "/noon.init_khah.medi": "\uFCD4",
+ "/noon.init_meem.fina": "\uFC4E",
+ "/noon.init_meem.medi": "\uFCD5",
+ "/noon.init_yeh.fina": "\uFC50",
+ "/noon.isol": "\uFEE5",
+ "/noon.medi": "\uFEE8",
+ "/noon.medi_alefmaksura.fina": "\uFC8E",
+ "/noon.medi_hah.medi_alefmaksura.fina": "\uFD96",
+ "/noon.medi_hah.medi_yeh.fina": "\uFDB3",
+ "/noon.medi_heh.medi": "\uFCEF",
+ "/noon.medi_jeem.medi_alefmaksura.fina": "\uFD99",
+ "/noon.medi_jeem.medi_hah.fina": "\uFDBD",
+ "/noon.medi_jeem.medi_meem.fina": "\uFD97",
+ "/noon.medi_jeem.medi_yeh.fina": "\uFDC7",
+ "/noon.medi_meem.fina": "\uFC8C",
+ "/noon.medi_meem.medi": "\uFCEE",
+ "/noon.medi_meem.medi_alefmaksura.fina": "\uFD9B",
+ "/noon.medi_meem.medi_yeh.fina": "\uFD9A",
+ "/noon.medi_noon.fina": "\uFC8D",
+ "/noon.medi_reh.fina": "\uFC8A",
+ "/noon.medi_yeh.fina": "\uFC8F",
+ "/noon.medi_zain.fina": "\uFC8B",
+ "/noonSmallTah": "\u0768",
+ "/noonSmallV": "\u0769",
+ "/noonTwoDotsBelow": "\u0767",
+ "/noonabove": "\u06E8",
+ "/noonarabic": "\u0646",
+ "/noondotbelow": "\u06B9",
+ "/noonfinalarabic": "\uFEE6",
+ "/noonghunna": "\u06BA",
+ "/noonghunna.fina": "\uFB9F",
+ "/noonghunna.isol": "\uFB9E",
+ "/noonghunnaarabic": "\u06BA",
+ "/noonghunnafinalarabic": "\uFB9F",
+ "/noonhehinitialarabic": "\uFEE7",
+ "/nooninitialarabic": "\uFEE7",
+ "/noonjeeminitialarabic": "\uFCD2",
+ "/noonjeemisolatedarabic": "\uFC4B",
+ "/noonmedialarabic": "\uFEE8",
+ "/noonmeeminitialarabic": "\uFCD5",
+ "/noonmeemisolatedarabic": "\uFC4E",
+ "/noonnoonfinalarabic": "\uFC8D",
+ "/noonring": "\u06BC",
+ "/noonthreedotsabove": "\u06BD",
+ "/nor": "\u22BD",
+ "/nordicmark": "\u20BB",
+ "/normalfacrsemidirectproductleft": "\u22C9",
+ "/normalfacrsemidirectproductright": "\u22CA",
+ "/normalsubgroorequalup": "\u22B4",
+ "/normalsubgroup": "\u22B2",
+ "/northeastPointingAirplane": "\u1F6EA",
+ "/nose": "\u1F443",
+ "/notalmostequal": "\u2249",
+ "/notasersetup": "\u2285",
+ "/notasympticallyequal": "\u2244",
+ "/notcheckmark": "\u237B",
+ "/notchedLeftSemicircleThreeDots": "\u1F543",
+ "/notchedRightSemicircleThreeDots": "\u1F544",
+ "/notcontains": "\u220C",
+ "/note": "\u1F5C8",
+ "/notePad": "\u1F5CA",
+ "/notePage": "\u1F5C9",
+ "/notebook": "\u1F4D3",
+ "/notebookDecorativeCover": "\u1F4D4",
+ "/notelement": "\u2209",
+ "/notelementof": "\u2209",
+ "/notequal": "\u2260",
+ "/notequivalent": "\u226D",
+ "/notexistential": "\u2204",
+ "/notgreater": "\u226F",
+ "/notgreaternorequal": "\u2271",
+ "/notgreaternorless": "\u2279",
+ "/notidentical": "\u2262",
+ "/notless": "\u226E",
+ "/notlessnorequal": "\u2270",
+ "/notnormalsubgroorequalup": "\u22EC",
+ "/notnormalsubgroup": "\u22EA",
+ "/notparallel": "\u2226",
+ "/notprecedes": "\u2280",
+ "/notsignturned": "\u2319",
+ "/notsquareimageorequal": "\u22E2",
+ "/notsquareoriginalorequal": "\u22E3",
+ "/notsubset": "\u2284",
+ "/notsucceeds": "\u2281",
+ "/notsuperset": "\u2285",
+ "/nottilde": "\u2241",
+ "/nottosquare": "\u3329",
+ "/nottrue": "\u22AD",
+ "/novembertelegraph": "\u32CA",
+ "/nowarmenian": "\u0576",
+ "/nparen": "\u24A9",
+ "/nparenthesized": "\u24A9",
+ "/nretroflex": "\u0273",
+ "/nsfullwidth": "\u33B1",
+ "/nssquare": "\u33B1",
+ "/nsuperior": "\u207F",
+ "/ntilde": "\u00F1",
+ "/nu": "\u03BD",
+ "/nucirclekatakana": "\u32E6",
+ "/nuhiragana": "\u306C",
+ "/nukatakana": "\u30CC",
+ "/nukatakanahalfwidth": "\uFF87",
+ "/nuktabengali": "\u09BC",
+ "/nuktadeva": "\u093C",
+ "/nuktagujarati": "\u0ABC",
+ "/nuktagurmukhi": "\u0A3C",
+ "/num": "\uA774",
+ "/numbermarkabove": "\u0605",
+ "/numbersign": "\u0023",
+ "/numbersignmonospace": "\uFF03",
+ "/numbersignsmall": "\uFE5F",
+ "/numeralsign": "\u0374",
+ "/numeralsigngreek": "\u0374",
+ "/numeralsignlowergreek": "\u0375",
+ "/numero": "\u2116",
+ "/nun": "\u05E0",
+ "/nun:hb": "\u05E0",
+ "/nunHafukha:hb": "\u05C6",
+ "/nundagesh": "\uFB40",
+ "/nundageshhebrew": "\uFB40",
+ "/nunhebrew": "\u05E0",
+ "/nunwithdagesh:hb": "\uFB40",
+ "/nutAndBolt": "\u1F529",
+ "/nvfullwidth": "\u33B5",
+ "/nvsquare": "\u33B5",
+ "/nwfullwidth": "\u33BB",
+ "/nwsquare": "\u33BB",
+ "/nyabengali": "\u099E",
+ "/nyadeva": "\u091E",
+ "/nyagujarati": "\u0A9E",
+ "/nyagurmukhi": "\u0A1E",
+ "/nyamurda": "\uA998",
+ "/nyeh": "\u0683",
+ "/nyeh.fina": "\uFB77",
+ "/nyeh.init": "\uFB78",
+ "/nyeh.isol": "\uFB76",
+ "/nyeh.medi": "\uFB79",
+ "/o": "\u006F",
+ "/o.inferior": "\u2092",
+ "/oacute": "\u00F3",
+ "/oangthai": "\u0E2D",
+ "/obarcyr": "\u04E9",
+ "/obardieresiscyr": "\u04EB",
+ "/obarred": "\u0275",
+ "/obarredcyrillic": "\u04E9",
+ "/obarreddieresiscyrillic": "\u04EB",
+ "/obelosdotted": "\u2E13",
+ "/obengali": "\u0993",
+ "/obopomofo": "\u311B",
+ "/obreve": "\u014F",
+ "/observereye": "\u23FF",
+ "/ocandradeva": "\u0911",
+ "/ocandragujarati": "\u0A91",
+ "/ocandravowelsigndeva": "\u0949",
+ "/ocandravowelsigngujarati": "\u0AC9",
+ "/ocaron": "\u01D2",
+ "/ocircle": "\u24DE",
+ "/ocirclekatakana": "\u32D4",
+ "/ocircumflex": "\u00F4",
+ "/ocircumflexacute": "\u1ED1",
+ "/ocircumflexdotbelow": "\u1ED9",
+ "/ocircumflexgrave": "\u1ED3",
+ "/ocircumflexhoi": "\u1ED5",
+ "/ocircumflexhookabove": "\u1ED5",
+ "/ocircumflextilde": "\u1ED7",
+ "/ocr:bowtie": "\u2445",
+ "/ocr:dash": "\u2448",
+ "/octagonalSign": "\u1F6D1",
+ "/octobertelegraph": "\u32C9",
+ "/octopus": "\u1F419",
+ "/ocyr": "\u043E",
+ "/ocyrillic": "\u043E",
+ "/odblacute": "\u0151",
+ "/odblgrave": "\u020D",
+ "/oden": "\u1F362",
+ "/odeva": "\u0913",
+ "/odieresis": "\u00F6",
+ "/odieresiscyr": "\u04E7",
+ "/odieresiscyrillic": "\u04E7",
+ "/odieresismacron": "\u022B",
+ "/odot": "\u022F",
+ "/odotbelow": "\u1ECD",
+ "/odotmacron": "\u0231",
+ "/oe": "\u0153",
+ "/oe.fina": "\uFBDA",
+ "/oe.isol": "\uFBD9",
+ "/oekirghiz": "\u06C5",
+ "/oekirghiz.fina": "\uFBE1",
+ "/oekirghiz.isol": "\uFBE0",
+ "/oekorean": "\u315A",
+ "/officeBuilding": "\u1F3E2",
+ "/ogonek": "\u02DB",
+ "/ogonekcmb": "\u0328",
+ "/ograve": "\u00F2",
+ "/ogravedbl": "\u020D",
+ "/ogujarati": "\u0A93",
+ "/oharmenian": "\u0585",
+ "/ohiragana": "\u304A",
+ "/ohm": "\u2126",
+ "/ohminverted": "\u2127",
+ "/ohoi": "\u1ECF",
+ "/ohookabove": "\u1ECF",
+ "/ohorn": "\u01A1",
+ "/ohornacute": "\u1EDB",
+ "/ohorndotbelow": "\u1EE3",
+ "/ohorngrave": "\u1EDD",
+ "/ohornhoi": "\u1EDF",
+ "/ohornhookabove": "\u1EDF",
+ "/ohorntilde": "\u1EE1",
+ "/ohungarumlaut": "\u0151",
+ "/ohuparen": "\u321E",
+ "/oi": "\u01A3",
+ "/oilDrum": "\u1F6E2",
+ "/oinvertedbreve": "\u020F",
+ "/ojeonparen": "\u321D",
+ "/okHandSign": "\u1F44C",
+ "/okatakana": "\u30AA",
+ "/okatakanahalfwidth": "\uFF75",
+ "/okorean": "\u3157",
+ "/oksquare": "\u1F197",
+ "/oldKey": "\u1F5DD",
+ "/oldPersonalComputer": "\u1F5B3",
+ "/olderMan": "\u1F474",
+ "/olderWoman": "\u1F475",
+ "/ole:hb": "\u05AB",
+ "/olehebrew": "\u05AB",
+ "/oloop": "\uA74D",
+ "/olowringinside": "\u2C7A",
+ "/omacron": "\u014D",
+ "/omacronacute": "\u1E53",
+ "/omacrongrave": "\u1E51",
+ "/omdeva": "\u0950",
+ "/omega": "\u03C9",
+ "/omega1": "\u03D6",
+ "/omegaacute": "\u1F7D",
+ "/omegaacuteiotasub": "\u1FF4",
+ "/omegaasper": "\u1F61",
+ "/omegaasperacute": "\u1F65",
+ "/omegaasperacuteiotasub": "\u1FA5",
+ "/omegaaspergrave": "\u1F63",
+ "/omegaaspergraveiotasub": "\u1FA3",
+ "/omegaasperiotasub": "\u1FA1",
+ "/omegaaspertilde": "\u1F67",
+ "/omegaaspertildeiotasub": "\u1FA7",
+ "/omegaclosed": "\u0277",
+ "/omegacyr": "\u0461",
+ "/omegacyrillic": "\u0461",
+ "/omegafunc": "\u2375",
+ "/omegagrave": "\u1F7C",
+ "/omegagraveiotasub": "\u1FF2",
+ "/omegaiotasub": "\u1FF3",
+ "/omegalatinclosed": "\u0277",
+ "/omegalenis": "\u1F60",
+ "/omegalenisacute": "\u1F64",
+ "/omegalenisacuteiotasub": "\u1FA4",
+ "/omegalenisgrave": "\u1F62",
+ "/omegalenisgraveiotasub": "\u1FA2",
+ "/omegalenisiotasub": "\u1FA0",
+ "/omegalenistilde": "\u1F66",
+ "/omegalenistildeiotasub": "\u1FA6",
+ "/omegaroundcyr": "\u047B",
+ "/omegaroundcyrillic": "\u047B",
+ "/omegatilde": "\u1FF6",
+ "/omegatildeiotasub": "\u1FF7",
+ "/omegatitlocyr": "\u047D",
+ "/omegatitlocyrillic": "\u047D",
+ "/omegatonos": "\u03CE",
+ "/omegaunderlinefunc": "\u2379",
+ "/omgujarati": "\u0AD0",
+ "/omicron": "\u03BF",
+ "/omicronacute": "\u1F79",
+ "/omicronasper": "\u1F41",
+ "/omicronasperacute": "\u1F45",
+ "/omicronaspergrave": "\u1F43",
+ "/omicrongrave": "\u1F78",
+ "/omicronlenis": "\u1F40",
+ "/omicronlenisacute": "\u1F44",
+ "/omicronlenisgrave": "\u1F42",
+ "/omicrontonos": "\u03CC",
+ "/omonospace": "\uFF4F",
+ "/onExclamationMarkLeftRightArrowAbove": "\u1F51B",
+ "/oncomingAutomobile": "\u1F698",
+ "/oncomingBus": "\u1F68D",
+ "/oncomingFireEngine": "\u1F6F1",
+ "/oncomingPoliceCar": "\u1F694",
+ "/oncomingTaxi": "\u1F696",
+ "/one": "\u0031",
+ "/one.inferior": "\u2081",
+ "/one.roman": "\u2160",
+ "/one.romansmall": "\u2170",
+ "/oneButtonMouse": "\u1F5AF",
+ "/onearabic": "\u0661",
+ "/onebengali": "\u09E7",
+ "/onecircle": "\u2460",
+ "/onecircledbl": "\u24F5",
+ "/onecircleinversesansserif": "\u278A",
+ "/onecomma": "\u1F102",
+ "/onedeva": "\u0967",
+ "/onedotenleader": "\u2024",
+ "/onedotovertwodots": "\u2E2B",
+ "/oneeighth": "\u215B",
+ "/onefar": "\u06F1",
+ "/onefitted": "\uF6DC",
+ "/onefraction": "\u215F",
+ "/onegujarati": "\u0AE7",
+ "/onegurmukhi": "\u0A67",
+ "/onehackarabic": "\u0661",
+ "/onehalf": "\u00BD",
+ "/onehangzhou": "\u3021",
+ "/onehundred.roman": "\u216D",
+ "/onehundred.romansmall": "\u217D",
+ "/onehundredthousand.roman": "\u2188",
+ "/onehundredtwentypsquare": "\u1F1A4",
+ "/oneideographiccircled": "\u3280",
+ "/oneideographicparen": "\u3220",
+ "/oneinferior": "\u2081",
+ "/onemonospace": "\uFF11",
+ "/oneninth": "\u2151",
+ "/onenumeratorbengali": "\u09F4",
+ "/oneoldstyle": "\uF731",
+ "/oneparen": "\u2474",
+ "/oneparenthesized": "\u2474",
+ "/oneperiod": "\u2488",
+ "/onepersian": "\u06F1",
+ "/onequarter": "\u00BC",
+ "/oneroman": "\u2170",
+ "/oneseventh": "\u2150",
+ "/onesixth": "\u2159",
+ "/onesuperior": "\u00B9",
+ "/onethai": "\u0E51",
+ "/onethird": "\u2153",
+ "/onethousand.roman": "\u216F",
+ "/onethousand.romansmall": "\u217F",
+ "/onethousandcd.roman": "\u2180",
+ "/onsusquare": "\u3309",
+ "/oo": "\uA74F",
+ "/oogonek": "\u01EB",
+ "/oogonekmacron": "\u01ED",
+ "/oogurmukhi": "\u0A13",
+ "/oomatragurmukhi": "\u0A4B",
+ "/oomusquare": "\u330A",
+ "/oopen": "\u0254",
+ "/oparen": "\u24AA",
+ "/oparenthesized": "\u24AA",
+ "/openBook": "\u1F4D6",
+ "/openFileFolder": "\u1F4C2",
+ "/openFolder": "\u1F5C1",
+ "/openHandsSign": "\u1F450",
+ "/openLock": "\u1F513",
+ "/openMailboxLoweredFlag": "\u1F4ED",
+ "/openMailboxRaisedFlag": "\u1F4EC",
+ "/openbullet": "\u25E6",
+ "/openheadarrowleft": "\u21FD",
+ "/openheadarrowleftright": "\u21FF",
+ "/openheadarrowright": "\u21FE",
+ "/opensubset": "\u27C3",
+ "/opensuperset": "\u27C4",
+ "/ophiuchus": "\u26CE",
+ "/opposition": "\u260D",
+ "/opticalDisc": "\u1F4BF",
+ "/opticalDiscIcon": "\u1F5B8",
+ "/option": "\u2325",
+ "/orangeBook": "\u1F4D9",
+ "/ordfeminine": "\u00AA",
+ "/ordmasculine": "\u00BA",
+ "/ordotinside": "\u27C7",
+ "/original": "\u22B6",
+ "/ornateleftparenthesis": "\uFD3E",
+ "/ornaterightparenthesis": "\uFD3F",
+ "/orthodoxcross": "\u2626",
+ "/orthogonal": "\u221F",
+ "/orya:a": "\u0B05",
+ "/orya:aa": "\u0B06",
+ "/orya:aasign": "\u0B3E",
+ "/orya:ai": "\u0B10",
+ "/orya:ailengthmark": "\u0B56",
+ "/orya:aisign": "\u0B48",
+ "/orya:anusvara": "\u0B02",
+ "/orya:au": "\u0B14",
+ "/orya:aulengthmark": "\u0B57",
+ "/orya:ausign": "\u0B4C",
+ "/orya:avagraha": "\u0B3D",
+ "/orya:ba": "\u0B2C",
+ "/orya:bha": "\u0B2D",
+ "/orya:ca": "\u0B1A",
+ "/orya:candrabindu": "\u0B01",
+ "/orya:cha": "\u0B1B",
+ "/orya:da": "\u0B26",
+ "/orya:dda": "\u0B21",
+ "/orya:ddha": "\u0B22",
+ "/orya:dha": "\u0B27",
+ "/orya:e": "\u0B0F",
+ "/orya:eight": "\u0B6E",
+ "/orya:esign": "\u0B47",
+ "/orya:five": "\u0B6B",
+ "/orya:four": "\u0B6A",
+ "/orya:fractiononeeighth": "\u0B76",
+ "/orya:fractiononehalf": "\u0B73",
+ "/orya:fractiononequarter": "\u0B72",
+ "/orya:fractiononesixteenth": "\u0B75",
+ "/orya:fractionthreequarters": "\u0B74",
+ "/orya:fractionthreesixteenths": "\u0B77",
+ "/orya:ga": "\u0B17",
+ "/orya:gha": "\u0B18",
+ "/orya:ha": "\u0B39",
+ "/orya:i": "\u0B07",
+ "/orya:ii": "\u0B08",
+ "/orya:iisign": "\u0B40",
+ "/orya:isign": "\u0B3F",
+ "/orya:isshar": "\u0B70",
+ "/orya:ja": "\u0B1C",
+ "/orya:jha": "\u0B1D",
+ "/orya:ka": "\u0B15",
+ "/orya:kha": "\u0B16",
+ "/orya:la": "\u0B32",
+ "/orya:lla": "\u0B33",
+ "/orya:llvocal": "\u0B61",
+ "/orya:llvocalsign": "\u0B63",
+ "/orya:lvocal": "\u0B0C",
+ "/orya:lvocalsign": "\u0B62",
+ "/orya:ma": "\u0B2E",
+ "/orya:na": "\u0B28",
+ "/orya:nga": "\u0B19",
+ "/orya:nine": "\u0B6F",
+ "/orya:nna": "\u0B23",
+ "/orya:nukta": "\u0B3C",
+ "/orya:nya": "\u0B1E",
+ "/orya:o": "\u0B13",
+ "/orya:one": "\u0B67",
+ "/orya:osign": "\u0B4B",
+ "/orya:pa": "\u0B2A",
+ "/orya:pha": "\u0B2B",
+ "/orya:ra": "\u0B30",
+ "/orya:rha": "\u0B5D",
+ "/orya:rra": "\u0B5C",
+ "/orya:rrvocal": "\u0B60",
+ "/orya:rrvocalsign": "\u0B44",
+ "/orya:rvocal": "\u0B0B",
+ "/orya:rvocalsign": "\u0B43",
+ "/orya:sa": "\u0B38",
+ "/orya:seven": "\u0B6D",
+ "/orya:sha": "\u0B36",
+ "/orya:six": "\u0B6C",
+ "/orya:ssa": "\u0B37",
+ "/orya:ta": "\u0B24",
+ "/orya:tha": "\u0B25",
+ "/orya:three": "\u0B69",
+ "/orya:tta": "\u0B1F",
+ "/orya:ttha": "\u0B20",
+ "/orya:two": "\u0B68",
+ "/orya:u": "\u0B09",
+ "/orya:usign": "\u0B41",
+ "/orya:uu": "\u0B0A",
+ "/orya:uusign": "\u0B42",
+ "/orya:va": "\u0B35",
+ "/orya:virama": "\u0B4D",
+ "/orya:visarga": "\u0B03",
+ "/orya:wa": "\u0B71",
+ "/orya:ya": "\u0B2F",
+ "/orya:yya": "\u0B5F",
+ "/orya:zero": "\u0B66",
+ "/oscript": "\u2134",
+ "/oshortdeva": "\u0912",
+ "/oshortvowelsigndeva": "\u094A",
+ "/oslash": "\u00F8",
+ "/oslashacute": "\u01FF",
+ "/osmallhiragana": "\u3049",
+ "/osmallkatakana": "\u30A9",
+ "/osmallkatakanahalfwidth": "\uFF6B",
+ "/ostroke": "\uA74B",
+ "/ostrokeacute": "\u01FF",
+ "/osuperior": "\uF6F0",
+ "/otcyr": "\u047F",
+ "/otcyrillic": "\u047F",
+ "/otilde": "\u00F5",
+ "/otildeacute": "\u1E4D",
+ "/otildedieresis": "\u1E4F",
+ "/otildemacron": "\u022D",
+ "/ou": "\u0223",
+ "/oubopomofo": "\u3121",
+ "/ounce": "\u2125",
+ "/outboxTray": "\u1F4E4",
+ "/outerjoinfull": "\u27D7",
+ "/outerjoinleft": "\u27D5",
+ "/outerjoinright": "\u27D6",
+ "/outputpassiveup": "\u2392",
+ "/overlap": "\u1F5D7",
+ "/overline": "\u203E",
+ "/overlinecenterline": "\uFE4A",
+ "/overlinecmb": "\u0305",
+ "/overlinedashed": "\uFE49",
+ "/overlinedblwavy": "\uFE4C",
+ "/overlinewavy": "\uFE4B",
+ "/overscore": "\u00AF",
+ "/ovfullwidth": "\u3375",
+ "/ovowelsignbengali": "\u09CB",
+ "/ovowelsigndeva": "\u094B",
+ "/ovowelsigngujarati": "\u0ACB",
+ "/ox": "\u1F402",
+ "/p": "\u0070",
+ "/p.inferior": "\u209A",
+ "/paampsfullwidth": "\u3380",
+ "/paampssquare": "\u3380",
+ "/paasentosquare": "\u332B",
+ "/paatusquare": "\u332C",
+ "/pabengali": "\u09AA",
+ "/pacerek": "\uA989",
+ "/package": "\u1F4E6",
+ "/pacute": "\u1E55",
+ "/padeva": "\u092A",
+ "/pafullwidth": "\u33A9",
+ "/page": "\u1F5CF",
+ "/pageCircledText": "\u1F5DF",
+ "/pageCurl": "\u1F4C3",
+ "/pageFacingUp": "\u1F4C4",
+ "/pagedown": "\u21DF",
+ "/pager": "\u1F4DF",
+ "/pages": "\u1F5D0",
+ "/pageup": "\u21DE",
+ "/pagoda": "\u1F6D4",
+ "/pagujarati": "\u0AAA",
+ "/pagurmukhi": "\u0A2A",
+ "/pahiragana": "\u3071",
+ "/paiyannoithai": "\u0E2F",
+ "/pakatakana": "\u30D1",
+ "/palatalizationcyrilliccmb": "\u0484",
+ "/palatcmbcyr": "\u0484",
+ "/pallas": "\u26B4",
+ "/palmTree": "\u1F334",
+ "/palmbranch": "\u2E19",
+ "/palochkacyr": "\u04CF",
+ "/palochkacyrillic": "\u04C0",
+ "/pamurda": "\uA9A6",
+ "/pandaFace": "\u1F43C",
+ "/pangkatpada": "\uA9C7",
+ "/pangkon": "\uA9C0",
+ "/pangrangkep": "\uA9CF",
+ "/pansioskorean": "\u317F",
+ "/panyangga": "\uA980",
+ "/paperclip": "\u1F4CE",
+ "/paragraph": "\u00B6",
+ "/paragraphos": "\u2E0F",
+ "/paragraphosforked": "\u2E10",
+ "/paragraphosforkedreversed": "\u2E11",
+ "/paragraphseparator": "\u2029",
+ "/parallel": "\u2225",
+ "/parallelogramblack": "\u25B0",
+ "/parallelogramwhite": "\u25B1",
+ "/parenbottom": "\u23DD",
+ "/parendblleft": "\u2E28",
+ "/parendblright": "\u2E29",
+ "/parenextensionleft": "\u239C",
+ "/parenextensionright": "\u239F",
+ "/parenflatleft": "\u27EE",
+ "/parenflatright": "\u27EF",
+ "/parenhookupleft": "\u239B",
+ "/parenhookupright": "\u239E",
+ "/parenleft": "\u0028",
+ "/parenleft.inferior": "\u208D",
+ "/parenleft.superior": "\u207D",
+ "/parenleftaltonearabic": "\uFD3E",
+ "/parenleftbt": "\uF8ED",
+ "/parenleftex": "\uF8EC",
+ "/parenleftinferior": "\u208D",
+ "/parenleftmonospace": "\uFF08",
+ "/parenleftsmall": "\uFE59",
+ "/parenleftsuperior": "\u207D",
+ "/parenlefttp": "\uF8EB",
+ "/parenleftvertical": "\uFE35",
+ "/parenlowerhookleft": "\u239D",
+ "/parenlowerhookright": "\u23A0",
+ "/parenright": "\u0029",
+ "/parenright.inferior": "\u208E",
+ "/parenright.superior": "\u207E",
+ "/parenrightaltonearabic": "\uFD3F",
+ "/parenrightbt": "\uF8F8",
+ "/parenrightex": "\uF8F7",
+ "/parenrightinferior": "\u208E",
+ "/parenrightmonospace": "\uFF09",
+ "/parenrightsmall": "\uFE5A",
+ "/parenrightsuperior": "\u207E",
+ "/parenrighttp": "\uF8F6",
+ "/parenrightvertical": "\uFE36",
+ "/parentop": "\u23DC",
+ "/partalternationmark": "\u303D",
+ "/partialdiff": "\u2202",
+ "/partnership": "\u3250",
+ "/partyPopper": "\u1F389",
+ "/paseq:hb": "\u05C0",
+ "/paseqhebrew": "\u05C0",
+ "/pashta:hb": "\u0599",
+ "/pashtahebrew": "\u0599",
+ "/pasquare": "\u33A9",
+ "/passengerShip": "\u1F6F3",
+ "/passivedown": "\u2391",
+ "/passportControl": "\u1F6C2",
+ "/patah": "\u05B7",
+ "/patah11": "\u05B7",
+ "/patah1d": "\u05B7",
+ "/patah2a": "\u05B7",
+ "/patah:hb": "\u05B7",
+ "/patahhebrew": "\u05B7",
+ "/patahnarrowhebrew": "\u05B7",
+ "/patahquarterhebrew": "\u05B7",
+ "/patahwidehebrew": "\u05B7",
+ "/pawPrints": "\u1F43E",
+ "/pawnblack": "\u265F",
+ "/pawnwhite": "\u2659",
+ "/pazer:hb": "\u05A1",
+ "/pazerhebrew": "\u05A1",
+ "/pbopomofo": "\u3106",
+ "/pcfullwidth": "\u3376",
+ "/pcircle": "\u24DF",
+ "/pdot": "\u1E57",
+ "/pdotaccent": "\u1E57",
+ "/pe": "\u05E4",
+ "/pe:hb": "\u05E4",
+ "/peace": "\u262E",
+ "/peach": "\u1F351",
+ "/pear": "\u1F350",
+ "/pecyr": "\u043F",
+ "/pecyrillic": "\u043F",
+ "/pedagesh": "\uFB44",
+ "/pedageshhebrew": "\uFB44",
+ "/pedestrian": "\u1F6B6",
+ "/peezisquare": "\u333B",
+ "/pefinaldageshhebrew": "\uFB43",
+ "/peh.fina": "\uFB57",
+ "/peh.init": "\uFB58",
+ "/peh.isol": "\uFB56",
+ "/peh.medi": "\uFB59",
+ "/peharabic": "\u067E",
+ "/peharmenian": "\u057A",
+ "/pehebrew": "\u05E4",
+ "/peheh": "\u06A6",
+ "/peheh.fina": "\uFB6F",
+ "/peheh.init": "\uFB70",
+ "/peheh.isol": "\uFB6E",
+ "/peheh.medi": "\uFB71",
+ "/pehfinalarabic": "\uFB57",
+ "/pehinitialarabic": "\uFB58",
+ "/pehiragana": "\u307A",
+ "/pehmedialarabic": "\uFB59",
+ "/pehookcyr": "\u04A7",
+ "/pekatakana": "\u30DA",
+ "/pemiddlehookcyrillic": "\u04A7",
+ "/penOverStampedEnvelope": "\u1F586",
+ "/pengkalconsonant": "\uA9BE",
+ "/penguin": "\u1F427",
+ "/penihisquare": "\u3338",
+ "/pensiveFace": "\u1F614",
+ "/pensusquare": "\u333A",
+ "/pentagram": "\u26E4",
+ "/pentasememetrical": "\u23D9",
+ "/pepetvowel": "\uA9BC",
+ "/per": "\u214C",
+ "/perafehebrew": "\uFB4E",
+ "/percent": "\u0025",
+ "/percentarabic": "\u066A",
+ "/percentmonospace": "\uFF05",
+ "/percentsmall": "\uFE6A",
+ "/percussivebidental": "\u02AD",
+ "/percussivebilabial": "\u02AC",
+ "/performingArts": "\u1F3AD",
+ "/period": "\u002E",
+ "/periodarmenian": "\u0589",
+ "/periodcentered": "\u00B7",
+ "/periodhalfwidth": "\uFF61",
+ "/periodinferior": "\uF6E7",
+ "/periodmonospace": "\uFF0E",
+ "/periodsmall": "\uFE52",
+ "/periodsuperior": "\uF6E8",
+ "/periodurdu": "\u06D4",
+ "/perispomenigreekcmb": "\u0342",
+ "/permanentpaper": "\u267E",
+ "/permille": "\u0609",
+ "/perpendicular": "\u22A5",
+ "/perseveringFace": "\u1F623",
+ "/personBlondHair": "\u1F471",
+ "/personBowingDeeply": "\u1F647",
+ "/personFrowning": "\u1F64D",
+ "/personRaisingBothHandsInCelebration": "\u1F64C",
+ "/personWithFoldedHands": "\u1F64F",
+ "/personWithPoutingFace": "\u1F64E",
+ "/personalComputer": "\u1F4BB",
+ "/personball": "\u26F9",
+ "/perspective": "\u2306",
+ "/pertenthousandsign": "\u2031",
+ "/perthousand": "\u2030",
+ "/peseta": "\u20A7",
+ "/peso": "\u20B1",
+ "/pesosquare": "\u3337",
+ "/petailcyr": "\u0525",
+ "/pewithdagesh:hb": "\uFB44",
+ "/pewithrafe:hb": "\uFB4E",
+ "/pffullwidth": "\u338A",
+ "/pflourish": "\uA753",
+ "/pfsquare": "\u338A",
+ "/phabengali": "\u09AB",
+ "/phadeva": "\u092B",
+ "/phagujarati": "\u0AAB",
+ "/phagurmukhi": "\u0A2B",
+ "/pharyngealvoicedfricative": "\u0295",
+ "/phfullwidth": "\u33D7",
+ "/phi": "\u03C6",
+ "/phi.math": "\u03D5",
+ "/phi1": "\u03D5",
+ "/phieuphacirclekorean": "\u327A",
+ "/phieuphaparenkorean": "\u321A",
+ "/phieuphcirclekorean": "\u326C",
+ "/phieuphkorean": "\u314D",
+ "/phieuphparenkorean": "\u320C",
+ "/philatin": "\u0278",
+ "/phinthuthai": "\u0E3A",
+ "/phisymbolgreek": "\u03D5",
+ "/phitailless": "\u2C77",
+ "/phon:AEsmall": "\u1D01",
+ "/phon:Aemod": "\u1D2D",
+ "/phon:Amod": "\u1D2C",
+ "/phon:Asmall": "\u1D00",
+ "/phon:Bbarmod": "\u1D2F",
+ "/phon:Bbarsmall": "\u1D03",
+ "/phon:Bmod": "\u1D2E",
+ "/phon:Csmall": "\u1D04",
+ "/phon:Dmod": "\u1D30",
+ "/phon:Dsmall": "\u1D05",
+ "/phon:ENcyrmod": "\u1D78",
+ "/phon:Elsmallcyr": "\u1D2B",
+ "/phon:Emod": "\u1D31",
+ "/phon:Ereversedmod": "\u1D32",
+ "/phon:Esmall": "\u1D07",
+ "/phon:Ethsmall": "\u1D06",
+ "/phon:Ezhsmall": "\u1D23",
+ "/phon:Gmod": "\u1D33",
+ "/phon:Hmod": "\u1D34",
+ "/phon:Imod": "\u1D35",
+ "/phon:Ismallmod": "\u1DA6",
+ "/phon:Ismallstroke": "\u1D7B",
+ "/phon:Istrokesmallmod": "\u1DA7",
+ "/phon:Jmod": "\u1D36",
+ "/phon:Jsmall": "\u1D0A",
+ "/phon:Kmod": "\u1D37",
+ "/phon:Ksmall": "\u1D0B",
+ "/phon:Lmod": "\u1D38",
+ "/phon:Lsmallmod": "\u1DAB",
+ "/phon:Lsmallstroke": "\u1D0C",
+ "/phon:Mmod": "\u1D39",
+ "/phon:Msmall": "\u1D0D",
+ "/phon:Nmod": "\u1D3A",
+ "/phon:Nreversedmod": "\u1D3B",
+ "/phon:Nsmallmod": "\u1DB0",
+ "/phon:Nsmallreversed": "\u1D0E",
+ "/phon:OUsmall": "\u1D15",
+ "/phon:Omod": "\u1D3C",
+ "/phon:Oopensmall": "\u1D10",
+ "/phon:Osmall": "\u1D0F",
+ "/phon:Oumod": "\u1D3D",
+ "/phon:Pmod": "\u1D3E",
+ "/phon:Psmall": "\u1D18",
+ "/phon:Rmod": "\u1D3F",
+ "/phon:Rsmallreversed": "\u1D19",
+ "/phon:Rsmallturned": "\u1D1A",
+ "/phon:Tmod": "\u1D40",
+ "/phon:Tsmall": "\u1D1B",
+ "/phon:Umod": "\u1D41",
+ "/phon:Usmall": "\u1D1C",
+ "/phon:Usmallmod": "\u1DB8",
+ "/phon:Usmallstroke": "\u1D7E",
+ "/phon:Vsmall": "\u1D20",
+ "/phon:Wmod": "\u1D42",
+ "/phon:Wsmall": "\u1D21",
+ "/phon:Zsmall": "\u1D22",
+ "/phon:aeturned": "\u1D02",
+ "/phon:aeturnedmod": "\u1D46",
+ "/phon:ain": "\u1D25",
+ "/phon:ainmod": "\u1D5C",
+ "/phon:alphamod": "\u1D45",
+ "/phon:alpharetroflexhook": "\u1D90",
+ "/phon:alphaturnedmod": "\u1D9B",
+ "/phon:amod": "\u1D43",
+ "/phon:aretroflexhook": "\u1D8F",
+ "/phon:aturnedmod": "\u1D44",
+ "/phon:betamod": "\u1D5D",
+ "/phon:bmiddletilde": "\u1D6C",
+ "/phon:bmod": "\u1D47",
+ "/phon:bpalatalhook": "\u1D80",
+ "/phon:ccurlmod": "\u1D9D",
+ "/phon:chimod": "\u1D61",
+ "/phon:cmod": "\u1D9C",
+ "/phon:deltamod": "\u1D5F",
+ "/phon:dhooktail": "\u1D91",
+ "/phon:dmiddletilde": "\u1D6D",
+ "/phon:dmod": "\u1D48",
+ "/phon:dotlessjstrokemod": "\u1DA1",
+ "/phon:dpalatalhook": "\u1D81",
+ "/phon:emod": "\u1D49",
+ "/phon:engmod": "\u1D51",
+ "/phon:eopenmod": "\u1D4B",
+ "/phon:eopenretroflexhook": "\u1D93",
+ "/phon:eopenreversedmod": "\u1D9F",
+ "/phon:eopenreversedretroflexhook": "\u1D94",
+ "/phon:eopenturned": "\u1D08",
+ "/phon:eopenturnedmod": "\u1D4C",
+ "/phon:eretroflexhook": "\u1D92",
+ "/phon:eshmod": "\u1DB4",
+ "/phon:eshpalatalhook": "\u1D8B",
+ "/phon:eshretroflexhook": "\u1D98",
+ "/phon:ethmod": "\u1D9E",
+ "/phon:ezhmod": "\u1DBE",
+ "/phon:ezhretroflexhook": "\u1D9A",
+ "/phon:fmiddletilde": "\u1D6E",
+ "/phon:fmod": "\u1DA0",
+ "/phon:fpalatalhook": "\u1D82",
+ "/phon:ginsular": "\u1D79",
+ "/phon:gmod": "\u1D4D",
+ "/phon:gpalatalhook": "\u1D83",
+ "/phon:gr:Gammasmall": "\u1D26",
+ "/phon:gr:Lambdasmall": "\u1D27",
+ "/phon:gr:Pismall": "\u1D28",
+ "/phon:gr:Psismall": "\u1D2A",
+ "/phon:gr:RsmallHO": "\u1D29",
+ "/phon:gr:betasubscript": "\u1D66",
+ "/phon:gr:chisubscript": "\u1D6A",
+ "/phon:gr:gammamod": "\u1D5E",
+ "/phon:gr:gammasubscript": "\u1D67",
+ "/phon:gr:phimod": "\u1D60",
+ "/phon:gr:phisubscript": "\u1D69",
+ "/phon:gr:rhosubscript": "\u1D68",
+ "/phon:gscriptmod": "\u1DA2",
+ "/phon:gturned": "\u1D77",
+ "/phon:hturnedmod": "\u1DA3",
+ "/phon:iotamod": "\u1DA5",
+ "/phon:iotastroke": "\u1D7C",
+ "/phon:iretroflexhook": "\u1D96",
+ "/phon:istrokemod": "\u1DA4",
+ "/phon:isubscript": "\u1D62",
+ "/phon:iturned": "\u1D09",
+ "/phon:iturnedmod": "\u1D4E",
+ "/phon:jcrossedtailmod": "\u1DA8",
+ "/phon:kmod": "\u1D4F",
+ "/phon:kpalatalhook": "\u1D84",
+ "/phon:lpalatalhook": "\u1D85",
+ "/phon:lpalatalhookmod": "\u1DAA",
+ "/phon:lretroflexhookmod": "\u1DA9",
+ "/phon:mhookmod": "\u1DAC",
+ "/phon:mlonglegturnedmod": "\u1DAD",
+ "/phon:mmiddletilde": "\u1D6F",
+ "/phon:mmod": "\u1D50",
+ "/phon:mpalatalhook": "\u1D86",
+ "/phon:mturnedmod": "\u1D5A",
+ "/phon:mturnedsideways": "\u1D1F",
+ "/phon:nlefthookmod": "\u1DAE",
+ "/phon:nmiddletilde": "\u1D70",
+ "/phon:npalatalhook": "\u1D87",
+ "/phon:nretroflexhookmod": "\u1DAF",
+ "/phon:obarmod": "\u1DB1",
+ "/phon:obottomhalf": "\u1D17",
+ "/phon:obottomhalfmod": "\u1D55",
+ "/phon:oeturned": "\u1D14",
+ "/phon:omod": "\u1D52",
+ "/phon:oopenmod": "\u1D53",
+ "/phon:oopenretroflexhook": "\u1D97",
+ "/phon:oopensideways": "\u1D12",
+ "/phon:osideways": "\u1D11",
+ "/phon:ostrokesideways": "\u1D13",
+ "/phon:otophalf": "\u1D16",
+ "/phon:otophalfmod": "\u1D54",
+ "/phon:phimod": "\u1DB2",
+ "/phon:pmiddletilde": "\u1D71",
+ "/phon:pmod": "\u1D56",
+ "/phon:ppalatalhook": "\u1D88",
+ "/phon:pstroke": "\u1D7D",
+ "/phon:rfishmiddletilde": "\u1D73",
+ "/phon:rmiddletilde": "\u1D72",
+ "/phon:rpalatalhook": "\u1D89",
+ "/phon:rsubscript": "\u1D63",
+ "/phon:schwamod": "\u1D4A",
+ "/phon:schwaretroflexhook": "\u1D95",
+ "/phon:shookmod": "\u1DB3",
+ "/phon:smiddletilde": "\u1D74",
+ "/phon:spalatalhook": "\u1D8A",
+ "/phon:spirantvoicedlaryngeal": "\u1D24",
+ "/phon:thetamod": "\u1DBF",
+ "/phon:thstrike": "\u1D7A",
+ "/phon:tmiddletilde": "\u1D75",
+ "/phon:tmod": "\u1D57",
+ "/phon:tpalatalhookmod": "\u1DB5",
+ "/phon:ubarmod": "\u1DB6",
+ "/phon:ue": "\u1D6B",
+ "/phon:umod": "\u1D58",
+ "/phon:upsilonmod": "\u1DB7",
+ "/phon:upsilonstroke": "\u1D7F",
+ "/phon:uretroflexhook": "\u1D99",
+ "/phon:usideways": "\u1D1D",
+ "/phon:usidewaysdieresised": "\u1D1E",
+ "/phon:usidewaysmod": "\u1D59",
+ "/phon:usubscript": "\u1D64",
+ "/phon:vhookmod": "\u1DB9",
+ "/phon:vmod": "\u1D5B",
+ "/phon:vpalatalhook": "\u1D8C",
+ "/phon:vsubscript": "\u1D65",
+ "/phon:vturnedmod": "\u1DBA",
+ "/phon:xpalatalhook": "\u1D8D",
+ "/phon:zcurlmod": "\u1DBD",
+ "/phon:zmiddletilde": "\u1D76",
+ "/phon:zmod": "\u1DBB",
+ "/phon:zpalatalhook": "\u1D8E",
+ "/phon:zretroflexhookmod": "\u1DBC",
+ "/phook": "\u01A5",
+ "/phophanthai": "\u0E1E",
+ "/phophungthai": "\u0E1C",
+ "/phosamphaothai": "\u0E20",
+ "/pi": "\u03C0",
+ "/pi.math": "\u03D6",
+ "/piasutorusquare": "\u332E",
+ "/pick": "\u26CF",
+ "/pidblstruck": "\u213C",
+ "/pieupacirclekorean": "\u3273",
+ "/pieupaparenkorean": "\u3213",
+ "/pieupcieuckorean": "\u3176",
+ "/pieupcirclekorean": "\u3265",
+ "/pieupkiyeokkorean": "\u3172",
+ "/pieupkorean": "\u3142",
+ "/pieupparenkorean": "\u3205",
+ "/pieupsioskiyeokkorean": "\u3174",
+ "/pieupsioskorean": "\u3144",
+ "/pieupsiostikeutkorean": "\u3175",
+ "/pieupthieuthkorean": "\u3177",
+ "/pieuptikeutkorean": "\u3173",
+ "/pig": "\u1F416",
+ "/pigFace": "\u1F437",
+ "/pigNose": "\u1F43D",
+ "/pihiragana": "\u3074",
+ "/pikatakana": "\u30D4",
+ "/pikosquare": "\u3330",
+ "/pikurusquare": "\u332F",
+ "/pilcrowsignreversed": "\u204B",
+ "/pileOfPoo": "\u1F4A9",
+ "/pill": "\u1F48A",
+ "/pineDecoration": "\u1F38D",
+ "/pineapple": "\u1F34D",
+ "/pisces": "\u2653",
+ "/piselehpada": "\uA9CC",
+ "/pistol": "\u1F52B",
+ "/pisymbolgreek": "\u03D6",
+ "/pitchfork": "\u22D4",
+ "/piwrarmenian": "\u0583",
+ "/placeOfWorship": "\u1F6D0",
+ "/placeofinterestsign": "\u2318",
+ "/planck": "\u210E",
+ "/plancktwopi": "\u210F",
+ "/plus": "\u002B",
+ "/plus.inferior": "\u208A",
+ "/plus.superior": "\u207A",
+ "/plusbelowcmb": "\u031F",
+ "/pluscircle": "\u2295",
+ "/plusminus": "\u00B1",
+ "/plusmod": "\u02D6",
+ "/plusmonospace": "\uFF0B",
+ "/plussignalt:hb": "\uFB29",
+ "/plussignmod": "\u02D6",
+ "/plussmall": "\uFE62",
+ "/plussuperior": "\u207A",
+ "/pluto": "\u2647",
+ "/pmfullwidth": "\u33D8",
+ "/pmonospace": "\uFF50",
+ "/pmsquare": "\u33D8",
+ "/pocketCalculator": "\u1F5A9",
+ "/poeticverse": "\u060E",
+ "/pohiragana": "\u307D",
+ "/pointerleftblack": "\u25C4",
+ "/pointerleftwhite": "\u25C5",
+ "/pointerrightblack": "\u25BA",
+ "/pointerrightwhite": "\u25BB",
+ "/pointingindexdownwhite": "\u261F",
+ "/pointingindexleftblack": "\u261A",
+ "/pointingindexleftwhite": "\u261C",
+ "/pointingindexrightblack": "\u261B",
+ "/pointingindexrightwhite": "\u261E",
+ "/pointingindexupwhite": "\u261D",
+ "/pointingtriangledownheavywhite": "\u26DB",
+ "/pointosquare": "\u333D",
+ "/pointring": "\u2E30",
+ "/pokatakana": "\u30DD",
+ "/pokrytiecmbcyr": "\u0487",
+ "/policeCar": "\u1F693",
+ "/policeCarsRevolvingLight": "\u1F6A8",
+ "/policeOfficer": "\u1F46E",
+ "/pondosquare": "\u3340",
+ "/poodle": "\u1F429",
+ "/popcorn": "\u1F37F",
+ "/popdirectionalformatting": "\u202C",
+ "/popdirectionalisolate": "\u2069",
+ "/poplathai": "\u0E1B",
+ "/portableStereo": "\u1F4FE",
+ "/positionindicator": "\u2316",
+ "/postalHorn": "\u1F4EF",
+ "/postalmark": "\u3012",
+ "/postalmarkface": "\u3020",
+ "/postbox": "\u1F4EE",
+ "/potOfFood": "\u1F372",
+ "/potableWater": "\u1F6B0",
+ "/pouch": "\u1F45D",
+ "/poultryLeg": "\u1F357",
+ "/poutingCatFace": "\u1F63E",
+ "/poutingFace": "\u1F621",
+ "/power": "\u23FB",
+ "/poweron": "\u23FD",
+ "/poweronoff": "\u23FC",
+ "/powersleep": "\u23FE",
+ "/pparen": "\u24AB",
+ "/pparenthesized": "\u24AB",
+ "/ppmfullwidth": "\u33D9",
+ "/prayerBeads": "\u1F4FF",
+ "/precedes": "\u227A",
+ "/precedesbutnotequivalent": "\u22E8",
+ "/precedesorequal": "\u227C",
+ "/precedesorequivalent": "\u227E",
+ "/precedesunderrelation": "\u22B0",
+ "/prescription": "\u211E",
+ "/preversedepigraphic": "\uA7FC",
+ "/previouspage": "\u2397",
+ "/prfullwidth": "\u33DA",
+ "/primedblmod": "\u02BA",
+ "/primemod": "\u02B9",
+ "/primereversed": "\u2035",
+ "/princess": "\u1F478",
+ "/printer": "\u1F5A8",
+ "/printerIcon": "\u1F5B6",
+ "/printideographiccircled": "\u329E",
+ "/printscreen": "\u2399",
+ "/product": "\u220F",
+ "/prohibitedSign": "\u1F6C7",
+ "/projective": "\u2305",
+ "/prolongedkana": "\u30FC",
+ "/propellor": "\u2318",
+ "/propersubset": "\u2282",
+ "/propersuperset": "\u2283",
+ "/propertyline": "\u214A",
+ "/proportion": "\u2237",
+ "/proportional": "\u221D",
+ "/psfullwidth": "\u33B0",
+ "/psi": "\u03C8",
+ "/psicyr": "\u0471",
+ "/psicyrillic": "\u0471",
+ "/psilicmbcyr": "\u0486",
+ "/psilipneumatacyrilliccmb": "\u0486",
+ "/pssquare": "\u33B0",
+ "/pstrokedescender": "\uA751",
+ "/ptail": "\uA755",
+ "/publicAddressLoudspeaker": "\u1F4E2",
+ "/puhiragana": "\u3077",
+ "/pukatakana": "\u30D7",
+ "/punctuationspace": "\u2008",
+ "/purpleHeart": "\u1F49C",
+ "/purse": "\u1F45B",
+ "/pushpin": "\u1F4CC",
+ "/putLitterInItsPlace": "\u1F6AE",
+ "/pvfullwidth": "\u33B4",
+ "/pvsquare": "\u33B4",
+ "/pwfullwidth": "\u33BA",
+ "/pwsquare": "\u33BA",
+ "/q": "\u0071",
+ "/qacyr": "\u051B",
+ "/qadeva": "\u0958",
+ "/qadma:hb": "\u05A8",
+ "/qadmahebrew": "\u05A8",
+ "/qaf": "\u0642",
+ "/qaf.fina": "\uFED6",
+ "/qaf.init": "\uFED7",
+ "/qaf.init_alefmaksura.fina": "\uFC35",
+ "/qaf.init_hah.fina": "\uFC33",
+ "/qaf.init_hah.medi": "\uFCC2",
+ "/qaf.init_meem.fina": "\uFC34",
+ "/qaf.init_meem.medi": "\uFCC3",
+ "/qaf.init_meem.medi_hah.medi": "\uFDB4",
+ "/qaf.init_yeh.fina": "\uFC36",
+ "/qaf.isol": "\uFED5",
+ "/qaf.medi": "\uFED8",
+ "/qaf.medi_alefmaksura.fina": "\uFC7E",
+ "/qaf.medi_meem.medi_hah.fina": "\uFD7E",
+ "/qaf.medi_meem.medi_meem.fina": "\uFD7F",
+ "/qaf.medi_meem.medi_yeh.fina": "\uFDB2",
+ "/qaf.medi_yeh.fina": "\uFC7F",
+ "/qaf_lam_alefmaksuraabove": "\u06D7",
+ "/qafarabic": "\u0642",
+ "/qafdotabove": "\u06A7",
+ "/qaffinalarabic": "\uFED6",
+ "/qafinitialarabic": "\uFED7",
+ "/qafmedialarabic": "\uFED8",
+ "/qafthreedotsabove": "\u06A8",
+ "/qamats": "\u05B8",
+ "/qamats10": "\u05B8",
+ "/qamats1a": "\u05B8",
+ "/qamats1c": "\u05B8",
+ "/qamats27": "\u05B8",
+ "/qamats29": "\u05B8",
+ "/qamats33": "\u05B8",
+ "/qamats:hb": "\u05B8",
+ "/qamatsQatan:hb": "\u05C7",
+ "/qamatsde": "\u05B8",
+ "/qamatshebrew": "\u05B8",
+ "/qamatsnarrowhebrew": "\u05B8",
+ "/qamatsqatanhebrew": "\u05B8",
+ "/qamatsqatannarrowhebrew": "\u05B8",
+ "/qamatsqatanquarterhebrew": "\u05B8",
+ "/qamatsqatanwidehebrew": "\u05B8",
+ "/qamatsquarterhebrew": "\u05B8",
+ "/qamatswidehebrew": "\u05B8",
+ "/qarneFarah:hb": "\u059F",
+ "/qarneyparahebrew": "\u059F",
+ "/qbopomofo": "\u3111",
+ "/qcircle": "\u24E0",
+ "/qdiagonalstroke": "\uA759",
+ "/qhook": "\u02A0",
+ "/qhooktail": "\u024B",
+ "/qmonospace": "\uFF51",
+ "/qof": "\u05E7",
+ "/qof:hb": "\u05E7",
+ "/qofdagesh": "\uFB47",
+ "/qofdageshhebrew": "\uFB47",
+ "/qofhatafpatah": "\u05E7",
+ "/qofhatafpatahhebrew": "\u05E7",
+ "/qofhatafsegol": "\u05E7",
+ "/qofhatafsegolhebrew": "\u05E7",
+ "/qofhebrew": "\u05E7",
+ "/qofhiriq": "\u05E7",
+ "/qofhiriqhebrew": "\u05E7",
+ "/qofholam": "\u05E7",
+ "/qofholamhebrew": "\u05E7",
+ "/qofpatah": "\u05E7",
+ "/qofpatahhebrew": "\u05E7",
+ "/qofqamats": "\u05E7",
+ "/qofqamatshebrew": "\u05E7",
+ "/qofqubuts": "\u05E7",
+ "/qofqubutshebrew": "\u05E7",
+ "/qofsegol": "\u05E7",
+ "/qofsegolhebrew": "\u05E7",
+ "/qofsheva": "\u05E7",
+ "/qofshevahebrew": "\u05E7",
+ "/qoftsere": "\u05E7",
+ "/qoftserehebrew": "\u05E7",
+ "/qofwithdagesh:hb": "\uFB47",
+ "/qparen": "\u24AC",
+ "/qparenthesized": "\u24AC",
+ "/qpdigraph": "\u0239",
+ "/qstrokedescender": "\uA757",
+ "/quadarrowdownfunc": "\u2357",
+ "/quadarrowleftfunc": "\u2347",
+ "/quadarrowrightfunc": "\u2348",
+ "/quadarrowupfunc": "\u2350",
+ "/quadbackslashfunc": "\u2342",
+ "/quadcaretdownfunc": "\u234C",
+ "/quadcaretupfunc": "\u2353",
+ "/quadcirclefunc": "\u233C",
+ "/quadcolonfunc": "\u2360",
+ "/quaddelfunc": "\u2354",
+ "/quaddeltafunc": "\u234D",
+ "/quaddiamondfunc": "\u233A",
+ "/quaddividefunc": "\u2339",
+ "/quadequalfunc": "\u2338",
+ "/quadfunc": "\u2395",
+ "/quadgreaterfunc": "\u2344",
+ "/quadjotfunc": "\u233B",
+ "/quadlessfunc": "\u2343",
+ "/quadnotequalfunc": "\u236F",
+ "/quadquestionfunc": "\u2370",
+ "/quadrantLowerLeft": "\u2596",
+ "/quadrantLowerRight": "\u2597",
+ "/quadrantUpperLeft": "\u2598",
+ "/quadrantUpperLeftAndLowerLeftAndLowerRight": "\u2599",
+ "/quadrantUpperLeftAndLowerRight": "\u259A",
+ "/quadrantUpperLeftAndUpperRightAndLowerLeft": "\u259B",
+ "/quadrantUpperLeftAndUpperRightAndLowerRight": "\u259C",
+ "/quadrantUpperRight": "\u259D",
+ "/quadrantUpperRightAndLowerLeft": "\u259E",
+ "/quadrantUpperRightAndLowerLeftAndLowerRight": "\u259F",
+ "/quadrupleminute": "\u2057",
+ "/quadslashfunc": "\u2341",
+ "/quarternote": "\u2669",
+ "/qubuts": "\u05BB",
+ "/qubuts18": "\u05BB",
+ "/qubuts25": "\u05BB",
+ "/qubuts31": "\u05BB",
+ "/qubuts:hb": "\u05BB",
+ "/qubutshebrew": "\u05BB",
+ "/qubutsnarrowhebrew": "\u05BB",
+ "/qubutsquarterhebrew": "\u05BB",
+ "/qubutswidehebrew": "\u05BB",
+ "/queenblack": "\u265B",
+ "/queenwhite": "\u2655",
+ "/question": "\u003F",
+ "/questionarabic": "\u061F",
+ "/questionarmenian": "\u055E",
+ "/questiondbl": "\u2047",
+ "/questiondown": "\u00BF",
+ "/questiondownsmall": "\uF7BF",
+ "/questionedequal": "\u225F",
+ "/questionexclamationmark": "\u2048",
+ "/questiongreek": "\u037E",
+ "/questionideographiccircled": "\u3244",
+ "/questionmonospace": "\uFF1F",
+ "/questionreversed": "\u2E2E",
+ "/questionsmall": "\uF73F",
+ "/quincunx": "\u26BB",
+ "/quotedbl": "\u0022",
+ "/quotedblbase": "\u201E",
+ "/quotedblleft": "\u201C",
+ "/quotedbllowreversed": "\u2E42",
+ "/quotedblmonospace": "\uFF02",
+ "/quotedblprime": "\u301E",
+ "/quotedblprimereversed": "\u301D",
+ "/quotedblreversed": "\u201F",
+ "/quotedblright": "\u201D",
+ "/quoteleft": "\u2018",
+ "/quoteleftreversed": "\u201B",
+ "/quotequadfunc": "\u235E",
+ "/quotereversed": "\u201B",
+ "/quoteright": "\u2019",
+ "/quoterightn": "\u0149",
+ "/quotesinglbase": "\u201A",
+ "/quotesingle": "\u0027",
+ "/quotesinglemonospace": "\uFF07",
+ "/quoteunderlinefunc": "\u2358",
+ "/r": "\u0072",
+ "/raagung": "\uA9AC",
+ "/raarmenian": "\u057C",
+ "/rabbit": "\u1F407",
+ "/rabbitFace": "\u1F430",
+ "/rabengali": "\u09B0",
+ "/racingCar": "\u1F3CE",
+ "/racingMotorcycle": "\u1F3CD",
+ "/racirclekatakana": "\u32F6",
+ "/racute": "\u0155",
+ "/radeva": "\u0930",
+ "/radfullwidth": "\u33AD",
+ "/radical": "\u221A",
+ "/radicalbottom": "\u23B7",
+ "/radicalex": "\uF8E5",
+ "/radio": "\u1F4FB",
+ "/radioButton": "\u1F518",
+ "/radioactive": "\u2622",
+ "/radovers2fullwidth": "\u33AF",
+ "/radoversfullwidth": "\u33AE",
+ "/radoverssquare": "\u33AE",
+ "/radoverssquaredsquare": "\u33AF",
+ "/radsquare": "\u33AD",
+ "/rafe": "\u05BF",
+ "/rafe:hb": "\u05BF",
+ "/rafehebrew": "\u05BF",
+ "/ragujarati": "\u0AB0",
+ "/ragurmukhi": "\u0A30",
+ "/rahiragana": "\u3089",
+ "/railwayCar": "\u1F683",
+ "/railwayTrack": "\u1F6E4",
+ "/rain": "\u26C6",
+ "/rainbow": "\u1F308",
+ "/raisedHandFingersSplayed": "\u1F590",
+ "/raisedHandPartBetweenMiddleAndRingFingers": "\u1F596",
+ "/raisedmcsign": "\u1F16A",
+ "/raisedmdsign": "\u1F16B",
+ "/rakatakana": "\u30E9",
+ "/rakatakanahalfwidth": "\uFF97",
+ "/ralowerdiagonalbengali": "\u09F1",
+ "/ram": "\u1F40F",
+ "/ramiddlediagonalbengali": "\u09F0",
+ "/ramshorn": "\u0264",
+ "/rat": "\u1F400",
+ "/ratio": "\u2236",
+ "/ray": "\u0608",
+ "/rbopomofo": "\u3116",
+ "/rcaron": "\u0159",
+ "/rcedilla": "\u0157",
+ "/rcircle": "\u24E1",
+ "/rcommaaccent": "\u0157",
+ "/rdblgrave": "\u0211",
+ "/rdot": "\u1E59",
+ "/rdotaccent": "\u1E59",
+ "/rdotbelow": "\u1E5B",
+ "/rdotbelowmacron": "\u1E5D",
+ "/reachideographicparen": "\u3243",
+ "/recirclekatakana": "\u32F9",
+ "/recreationalVehicle": "\u1F699",
+ "/rectangleblack": "\u25AC",
+ "/rectangleverticalblack": "\u25AE",
+ "/rectangleverticalwhite": "\u25AF",
+ "/rectanglewhite": "\u25AD",
+ "/recycledpaper": "\u267C",
+ "/recyclefiveplastics": "\u2677",
+ "/recyclefourplastics": "\u2676",
+ "/recyclegeneric": "\u267A",
+ "/recycleoneplastics": "\u2673",
+ "/recyclepartiallypaper": "\u267D",
+ "/recyclesevenplastics": "\u2679",
+ "/recyclesixplastics": "\u2678",
+ "/recyclethreeplastics": "\u2675",
+ "/recycletwoplastics": "\u2674",
+ "/recycleuniversal": "\u2672",
+ "/recycleuniversalblack": "\u267B",
+ "/redApple": "\u1F34E",
+ "/redTriangleDOwn": "\u1F53B",
+ "/redTriangleUp": "\u1F53A",
+ "/referencemark": "\u203B",
+ "/reflexsubset": "\u2286",
+ "/reflexsuperset": "\u2287",
+ "/regionalindicatorsymbollettera": "\u1F1E6",
+ "/regionalindicatorsymbolletterb": "\u1F1E7",
+ "/regionalindicatorsymbolletterc": "\u1F1E8",
+ "/regionalindicatorsymbolletterd": "\u1F1E9",
+ "/regionalindicatorsymbollettere": "\u1F1EA",
+ "/regionalindicatorsymbolletterf": "\u1F1EB",
+ "/regionalindicatorsymbolletterg": "\u1F1EC",
+ "/regionalindicatorsymbolletterh": "\u1F1ED",
+ "/regionalindicatorsymbolletteri": "\u1F1EE",
+ "/regionalindicatorsymbolletterj": "\u1F1EF",
+ "/regionalindicatorsymbolletterk": "\u1F1F0",
+ "/regionalindicatorsymbolletterl": "\u1F1F1",
+ "/regionalindicatorsymbolletterm": "\u1F1F2",
+ "/regionalindicatorsymbollettern": "\u1F1F3",
+ "/regionalindicatorsymbollettero": "\u1F1F4",
+ "/regionalindicatorsymbolletterp": "\u1F1F5",
+ "/regionalindicatorsymbolletterq": "\u1F1F6",
+ "/regionalindicatorsymbolletterr": "\u1F1F7",
+ "/regionalindicatorsymbolletters": "\u1F1F8",
+ "/regionalindicatorsymbollettert": "\u1F1F9",
+ "/regionalindicatorsymbolletteru": "\u1F1FA",
+ "/regionalindicatorsymbolletterv": "\u1F1FB",
+ "/regionalindicatorsymbolletterw": "\u1F1FC",
+ "/regionalindicatorsymbolletterx": "\u1F1FD",
+ "/regionalindicatorsymbollettery": "\u1F1FE",
+ "/regionalindicatorsymbolletterz": "\u1F1FF",
+ "/registered": "\u00AE",
+ "/registersans": "\uF8E8",
+ "/registerserif": "\uF6DA",
+ "/reh.fina": "\uFEAE",
+ "/reh.init_superscriptalef.fina": "\uFC5C",
+ "/reh.isol": "\uFEAD",
+ "/rehHamzaAbove": "\u076C",
+ "/rehSmallTahTwoDots": "\u0771",
+ "/rehStroke": "\u075B",
+ "/rehTwoDotsVerticallyAbove": "\u076B",
+ "/rehVabove": "\u0692",
+ "/rehVbelow": "\u0695",
+ "/reharabic": "\u0631",
+ "/reharmenian": "\u0580",
+ "/rehdotbelow": "\u0694",
+ "/rehdotbelowdotabove": "\u0696",
+ "/rehfinalarabic": "\uFEAE",
+ "/rehfourdotsabove": "\u0699",
+ "/rehinvertedV": "\u06EF",
+ "/rehiragana": "\u308C",
+ "/rehring": "\u0693",
+ "/rehtwodotsabove": "\u0697",
+ "/rehyehaleflamarabic": "\u0631",
+ "/rekatakana": "\u30EC",
+ "/rekatakanahalfwidth": "\uFF9A",
+ "/relievedFace": "\u1F60C",
+ "/religionideographiccircled": "\u32AA",
+ "/reminderRibbon": "\u1F397",
+ "/remusquare": "\u3355",
+ "/rentogensquare": "\u3356",
+ "/replacementchar": "\uFFFD",
+ "/replacementcharobj": "\uFFFC",
+ "/representideographicparen": "\u3239",
+ "/rerengganleft": "\uA9C1",
+ "/rerengganright": "\uA9C2",
+ "/resh": "\u05E8",
+ "/resh:hb": "\u05E8",
+ "/reshdageshhebrew": "\uFB48",
+ "/reshhatafpatah": "\u05E8",
+ "/reshhatafpatahhebrew": "\u05E8",
+ "/reshhatafsegol": "\u05E8",
+ "/reshhatafsegolhebrew": "\u05E8",
+ "/reshhebrew": "\u05E8",
+ "/reshhiriq": "\u05E8",
+ "/reshhiriqhebrew": "\u05E8",
+ "/reshholam": "\u05E8",
+ "/reshholamhebrew": "\u05E8",
+ "/reshpatah": "\u05E8",
+ "/reshpatahhebrew": "\u05E8",
+ "/reshqamats": "\u05E8",
+ "/reshqamatshebrew": "\u05E8",
+ "/reshqubuts": "\u05E8",
+ "/reshqubutshebrew": "\u05E8",
+ "/reshsegol": "\u05E8",
+ "/reshsegolhebrew": "\u05E8",
+ "/reshsheva": "\u05E8",
+ "/reshshevahebrew": "\u05E8",
+ "/reshtsere": "\u05E8",
+ "/reshtserehebrew": "\u05E8",
+ "/reshwide:hb": "\uFB27",
+ "/reshwithdagesh:hb": "\uFB48",
+ "/resourceideographiccircled": "\u32AE",
+ "/resourceideographicparen": "\u323E",
+ "/response": "\u211F",
+ "/restideographiccircled": "\u32A1",
+ "/restideographicparen": "\u3241",
+ "/restrictedentryoneleft": "\u26E0",
+ "/restrictedentrytwoleft": "\u26E1",
+ "/restroom": "\u1F6BB",
+ "/return": "\u23CE",
+ "/reversedHandMiddleFingerExtended": "\u1F595",
+ "/reversedRaisedHandFingersSplayed": "\u1F591",
+ "/reversedThumbsDownSign": "\u1F593",
+ "/reversedThumbsUpSign": "\u1F592",
+ "/reversedVictoryHand": "\u1F594",
+ "/reversedonehundred.roman": "\u2183",
+ "/reversedtilde": "\u223D",
+ "/reversedzecyr": "\u0511",
+ "/revia:hb": "\u0597",
+ "/reviahebrew": "\u0597",
+ "/reviamugrashhebrew": "\u0597",
+ "/revlogicalnot": "\u2310",
+ "/revolvingHearts": "\u1F49E",
+ "/rfishhook": "\u027E",
+ "/rfishhookreversed": "\u027F",
+ "/rgravedbl": "\u0211",
+ "/rhabengali": "\u09DD",
+ "/rhacyr": "\u0517",
+ "/rhadeva": "\u095D",
+ "/rho": "\u03C1",
+ "/rhoasper": "\u1FE5",
+ "/rhofunc": "\u2374",
+ "/rholenis": "\u1FE4",
+ "/rhook": "\u027D",
+ "/rhookturned": "\u027B",
+ "/rhookturnedsuperior": "\u02B5",
+ "/rhookturnedsupmod": "\u02B5",
+ "/rhostrokesymbol": "\u03FC",
+ "/rhosymbol": "\u03F1",
+ "/rhosymbolgreek": "\u03F1",
+ "/rhotichookmod": "\u02DE",
+ "/rial": "\uFDFC",
+ "/ribbon": "\u1F380",
+ "/riceBall": "\u1F359",
+ "/riceCracker": "\u1F358",
+ "/ricirclekatakana": "\u32F7",
+ "/rieulacirclekorean": "\u3271",
+ "/rieulaparenkorean": "\u3211",
+ "/rieulcirclekorean": "\u3263",
+ "/rieulhieuhkorean": "\u3140",
+ "/rieulkiyeokkorean": "\u313A",
+ "/rieulkiyeoksioskorean": "\u3169",
+ "/rieulkorean": "\u3139",
+ "/rieulmieumkorean": "\u313B",
+ "/rieulpansioskorean": "\u316C",
+ "/rieulparenkorean": "\u3203",
+ "/rieulphieuphkorean": "\u313F",
+ "/rieulpieupkorean": "\u313C",
+ "/rieulpieupsioskorean": "\u316B",
+ "/rieulsioskorean": "\u313D",
+ "/rieulthieuthkorean": "\u313E",
+ "/rieultikeutkorean": "\u316A",
+ "/rieulyeorinhieuhkorean": "\u316D",
+ "/right-pointingMagnifyingGlass": "\u1F50E",
+ "/rightAngerBubble": "\u1F5EF",
+ "/rightHalfBlock": "\u2590",
+ "/rightHandTelephoneReceiver": "\u1F57D",
+ "/rightOneEighthBlock": "\u2595",
+ "/rightSpeaker": "\u1F568",
+ "/rightSpeakerOneSoundWave": "\u1F569",
+ "/rightSpeakerThreeSoundWaves": "\u1F56A",
+ "/rightSpeechBubble": "\u1F5E9",
+ "/rightThoughtBubble": "\u1F5ED",
+ "/rightangle": "\u221F",
+ "/rightarrowoverleftarrow": "\u21C4",
+ "/rightdnheavyleftuplight": "\u2546",
+ "/rightharpoonoverleftharpoon": "\u21CC",
+ "/rightheavyleftdnlight": "\u252E",
+ "/rightheavyleftuplight": "\u2536",
+ "/rightheavyleftvertlight": "\u253E",
+ "/rightideographiccircled": "\u32A8",
+ "/rightlightleftdnheavy": "\u2531",
+ "/rightlightleftupheavy": "\u2539",
+ "/rightlightleftvertheavy": "\u2549",
+ "/righttackbelowcmb": "\u0319",
+ "/righttoleftembed": "\u202B",
+ "/righttoleftisolate": "\u2067",
+ "/righttoleftmark": "\u200F",
+ "/righttoleftoverride": "\u202E",
+ "/righttriangle": "\u22BF",
+ "/rightupheavyleftdnlight": "\u2544",
+ "/rihiragana": "\u308A",
+ "/rikatakana": "\u30EA",
+ "/rikatakanahalfwidth": "\uFF98",
+ "/ring": "\u02DA",
+ "/ringbelowcmb": "\u0325",
+ "/ringcmb": "\u030A",
+ "/ringequal": "\u2257",
+ "/ringhalfleft": "\u02BF",
+ "/ringhalfleftarmenian": "\u0559",
+ "/ringhalfleftbelowcmb": "\u031C",
+ "/ringhalfleftcentered": "\u02D3",
+ "/ringhalfleftcentredmod": "\u02D3",
+ "/ringhalfleftmod": "\u02BF",
+ "/ringhalfright": "\u02BE",
+ "/ringhalfrightbelowcmb": "\u0339",
+ "/ringhalfrightcentered": "\u02D2",
+ "/ringhalfrightcentredmod": "\u02D2",
+ "/ringhalfrightmod": "\u02BE",
+ "/ringinequal": "\u2256",
+ "/ringingBell": "\u1F56D",
+ "/ringlowmod": "\u02F3",
+ "/ringoperator": "\u2218",
+ "/rinsular": "\uA783",
+ "/rinvertedbreve": "\u0213",
+ "/rirasquare": "\u3352",
+ "/risingdiagonal": "\u27CB",
+ "/rittorusquare": "\u3351",
+ "/rlinebelow": "\u1E5F",
+ "/rlongleg": "\u027C",
+ "/rlonglegturned": "\u027A",
+ "/rmacrondot": "\u1E5D",
+ "/rmonospace": "\uFF52",
+ "/rnoon": "\u06BB",
+ "/rnoon.fina": "\uFBA1",
+ "/rnoon.init": "\uFBA2",
+ "/rnoon.isol": "\uFBA0",
+ "/rnoon.medi": "\uFBA3",
+ "/roastedSweetPotato": "\u1F360",
+ "/robliquestroke": "\uA7A7",
+ "/rocirclekatakana": "\u32FA",
+ "/rocket": "\u1F680",
+ "/rohiragana": "\u308D",
+ "/rokatakana": "\u30ED",
+ "/rokatakanahalfwidth": "\uFF9B",
+ "/rolled-upNewspaper": "\u1F5DE",
+ "/rollerCoaster": "\u1F3A2",
+ "/rookblack": "\u265C",
+ "/rookwhite": "\u2656",
+ "/rooster": "\u1F413",
+ "/roruathai": "\u0E23",
+ "/rose": "\u1F339",
+ "/rosette": "\u1F3F5",
+ "/roundPushpin": "\u1F4CD",
+ "/roundedzeroabove": "\u06DF",
+ "/rowboat": "\u1F6A3",
+ "/rparen": "\u24AD",
+ "/rparenthesized": "\u24AD",
+ "/rrabengali": "\u09DC",
+ "/rradeva": "\u0931",
+ "/rragurmukhi": "\u0A5C",
+ "/rreh": "\u0691",
+ "/rreh.fina": "\uFB8D",
+ "/rreh.isol": "\uFB8C",
+ "/rreharabic": "\u0691",
+ "/rrehfinalarabic": "\uFB8D",
+ "/rrotunda": "\uA75B",
+ "/rrvocalicbengali": "\u09E0",
+ "/rrvocalicdeva": "\u0960",
+ "/rrvocalicgujarati": "\u0AE0",
+ "/rrvocalicvowelsignbengali": "\u09C4",
+ "/rrvocalicvowelsigndeva": "\u0944",
+ "/rrvocalicvowelsigngujarati": "\u0AC4",
+ "/rstroke": "\u024D",
+ "/rsuperior": "\uF6F1",
+ "/rsupmod": "\u02B3",
+ "/rtailturned": "\u2C79",
+ "/rtblock": "\u2590",
+ "/rturned": "\u0279",
+ "/rturnedsuperior": "\u02B4",
+ "/rturnedsupmod": "\u02B4",
+ "/ruble": "\u20BD",
+ "/rucirclekatakana": "\u32F8",
+ "/rugbyFootball": "\u1F3C9",
+ "/ruhiragana": "\u308B",
+ "/rukatakana": "\u30EB",
+ "/rukatakanahalfwidth": "\uFF99",
+ "/rum": "\uA775",
+ "/rumrotunda": "\uA75D",
+ "/runner": "\u1F3C3",
+ "/runningShirtSash": "\u1F3BD",
+ "/rupeemarkbengali": "\u09F2",
+ "/rupeesignbengali": "\u09F3",
+ "/rupiah": "\uF6DD",
+ "/rupiisquare": "\u3353",
+ "/ruthai": "\u0E24",
+ "/ruuburusquare": "\u3354",
+ "/rvocalicbengali": "\u098B",
+ "/rvocalicdeva": "\u090B",
+ "/rvocalicgujarati": "\u0A8B",
+ "/rvocalicvowelsignbengali": "\u09C3",
+ "/rvocalicvowelsigndeva": "\u0943",
+ "/rvocalicvowelsigngujarati": "\u0AC3",
+ "/s": "\u0073",
+ "/s.inferior": "\u209B",
+ "/s_t": "\uFB06",
+ "/sabengali": "\u09B8",
+ "/sacirclekatakana": "\u32DA",
+ "/sacute": "\u015B",
+ "/sacutedotaccent": "\u1E65",
+ "/sad": "\u0635",
+ "/sad.fina": "\uFEBA",
+ "/sad.init": "\uFEBB",
+ "/sad.init_alefmaksura.fina": "\uFD05",
+ "/sad.init_hah.fina": "\uFC20",
+ "/sad.init_hah.medi": "\uFCB1",
+ "/sad.init_hah.medi_hah.medi": "\uFD65",
+ "/sad.init_khah.medi": "\uFCB2",
+ "/sad.init_meem.fina": "\uFC21",
+ "/sad.init_meem.medi": "\uFCB3",
+ "/sad.init_meem.medi_meem.medi": "\uFDC5",
+ "/sad.init_reh.fina": "\uFD0F",
+ "/sad.init_yeh.fina": "\uFD06",
+ "/sad.isol": "\uFEB9",
+ "/sad.medi": "\uFEBC",
+ "/sad.medi_alefmaksura.fina": "\uFD21",
+ "/sad.medi_hah.medi_hah.fina": "\uFD64",
+ "/sad.medi_hah.medi_yeh.fina": "\uFDA9",
+ "/sad.medi_meem.medi_meem.fina": "\uFD66",
+ "/sad.medi_reh.fina": "\uFD2B",
+ "/sad.medi_yeh.fina": "\uFD22",
+ "/sad_lam_alefmaksuraabove": "\u06D6",
+ "/sadarabic": "\u0635",
+ "/sadeva": "\u0938",
+ "/sadfinalarabic": "\uFEBA",
+ "/sadinitialarabic": "\uFEBB",
+ "/sadmedialarabic": "\uFEBC",
+ "/sadthreedotsabove": "\u069E",
+ "/sadtwodotsbelow": "\u069D",
+ "/sagittarius": "\u2650",
+ "/sagujarati": "\u0AB8",
+ "/sagurmukhi": "\u0A38",
+ "/sahiragana": "\u3055",
+ "/saikurusquare": "\u331F",
+ "/sailboat": "\u26F5",
+ "/sakatakana": "\u30B5",
+ "/sakatakanahalfwidth": "\uFF7B",
+ "/sakeBottleAndCup": "\u1F376",
+ "/sallallahoualayhewasallamarabic": "\uFDFA",
+ "/saltillo": "\uA78C",
+ "/saltire": "\u2613",
+ "/samahaprana": "\uA9B0",
+ "/samekh": "\u05E1",
+ "/samekh:hb": "\u05E1",
+ "/samekhdagesh": "\uFB41",
+ "/samekhdageshhebrew": "\uFB41",
+ "/samekhhebrew": "\u05E1",
+ "/samekhwithdagesh:hb": "\uFB41",
+ "/sampi": "\u03E1",
+ "/sampiarchaic": "\u0373",
+ "/samurda": "\uA9AF",
+ "/samvat": "\u0604",
+ "/san": "\u03FB",
+ "/santiimusquare": "\u3320",
+ "/saraaathai": "\u0E32",
+ "/saraaethai": "\u0E41",
+ "/saraaimaimalaithai": "\u0E44",
+ "/saraaimaimuanthai": "\u0E43",
+ "/saraamthai": "\u0E33",
+ "/saraathai": "\u0E30",
+ "/saraethai": "\u0E40",
+ "/saraiileftthai": "\uF886",
+ "/saraiithai": "\u0E35",
+ "/saraileftthai": "\uF885",
+ "/saraithai": "\u0E34",
+ "/saraothai": "\u0E42",
+ "/saraueeleftthai": "\uF888",
+ "/saraueethai": "\u0E37",
+ "/saraueleftthai": "\uF887",
+ "/sarauethai": "\u0E36",
+ "/sarauthai": "\u0E38",
+ "/sarauuthai": "\u0E39",
+ "/satellite": "\u1F6F0",
+ "/satelliteAntenna": "\u1F4E1",
+ "/saturn": "\u2644",
+ "/saxophone": "\u1F3B7",
+ "/sbopomofo": "\u3119",
+ "/scales": "\u2696",
+ "/scanninehorizontal": "\u23BD",
+ "/scanonehorizontal": "\u23BA",
+ "/scansevenhorizontal": "\u23BC",
+ "/scanthreehorizontal": "\u23BB",
+ "/scaron": "\u0161",
+ "/scarondot": "\u1E67",
+ "/scarondotaccent": "\u1E67",
+ "/scedilla": "\u015F",
+ "/school": "\u1F3EB",
+ "/schoolSatchel": "\u1F392",
+ "/schoolideographiccircled": "\u3246",
+ "/schwa": "\u0259",
+ "/schwa.inferior": "\u2094",
+ "/schwacyr": "\u04D9",
+ "/schwacyrillic": "\u04D9",
+ "/schwadieresiscyr": "\u04DB",
+ "/schwadieresiscyrillic": "\u04DB",
+ "/schwahook": "\u025A",
+ "/scircle": "\u24E2",
+ "/scircumflex": "\u015D",
+ "/scommaaccent": "\u0219",
+ "/scooter": "\u1F6F4",
+ "/scorpius": "\u264F",
+ "/screen": "\u1F5B5",
+ "/scroll": "\u1F4DC",
+ "/scruple": "\u2108",
+ "/sdot": "\u1E61",
+ "/sdotaccent": "\u1E61",
+ "/sdotbelow": "\u1E63",
+ "/sdotbelowdotabove": "\u1E69",
+ "/sdotbelowdotaccent": "\u1E69",
+ "/seagullbelowcmb": "\u033C",
+ "/seat": "\u1F4BA",
+ "/secirclekatakana": "\u32DD",
+ "/second": "\u2033",
+ "/secondreversed": "\u2036",
+ "/secondscreensquare": "\u1F19C",
+ "/secondtonechinese": "\u02CA",
+ "/secretideographiccircled": "\u3299",
+ "/section": "\u00A7",
+ "/sectionsignhalftop": "\u2E39",
+ "/sector": "\u2314",
+ "/seeNoEvilMonkey": "\u1F648",
+ "/seedling": "\u1F331",
+ "/seen": "\u0633",
+ "/seen.fina": "\uFEB2",
+ "/seen.init": "\uFEB3",
+ "/seen.init_alefmaksura.fina": "\uFCFB",
+ "/seen.init_hah.fina": "\uFC1D",
+ "/seen.init_hah.medi": "\uFCAE",
+ "/seen.init_hah.medi_jeem.medi": "\uFD5C",
+ "/seen.init_heh.medi": "\uFD31",
+ "/seen.init_jeem.fina": "\uFC1C",
+ "/seen.init_jeem.medi": "\uFCAD",
+ "/seen.init_jeem.medi_hah.medi": "\uFD5D",
+ "/seen.init_khah.fina": "\uFC1E",
+ "/seen.init_khah.medi": "\uFCAF",
+ "/seen.init_meem.fina": "\uFC1F",
+ "/seen.init_meem.medi": "\uFCB0",
+ "/seen.init_meem.medi_hah.medi": "\uFD60",
+ "/seen.init_meem.medi_jeem.medi": "\uFD61",
+ "/seen.init_meem.medi_meem.medi": "\uFD63",
+ "/seen.init_reh.fina": "\uFD0E",
+ "/seen.init_yeh.fina": "\uFCFC",
+ "/seen.isol": "\uFEB1",
+ "/seen.medi": "\uFEB4",
+ "/seen.medi_alefmaksura.fina": "\uFD17",
+ "/seen.medi_hah.medi": "\uFD35",
+ "/seen.medi_heh.medi": "\uFCE8",
+ "/seen.medi_jeem.medi": "\uFD34",
+ "/seen.medi_jeem.medi_alefmaksura.fina": "\uFD5E",
+ "/seen.medi_khah.medi": "\uFD36",
+ "/seen.medi_khah.medi_alefmaksura.fina": "\uFDA8",
+ "/seen.medi_khah.medi_yeh.fina": "\uFDC6",
+ "/seen.medi_meem.medi": "\uFCE7",
+ "/seen.medi_meem.medi_hah.fina": "\uFD5F",
+ "/seen.medi_meem.medi_meem.fina": "\uFD62",
+ "/seen.medi_reh.fina": "\uFD2A",
+ "/seen.medi_yeh.fina": "\uFD18",
+ "/seenDigitFourAbove": "\u077D",
+ "/seenFourDotsAbove": "\u075C",
+ "/seenInvertedV": "\u077E",
+ "/seenSmallTahTwoDots": "\u0770",
+ "/seenTwoDotsVerticallyAbove": "\u076D",
+ "/seenabove": "\u06DC",
+ "/seenarabic": "\u0633",
+ "/seendotbelowdotabove": "\u069A",
+ "/seenfinalarabic": "\uFEB2",
+ "/seeninitialarabic": "\uFEB3",
+ "/seenlow": "\u06E3",
+ "/seenmedialarabic": "\uFEB4",
+ "/seenthreedotsbelow": "\u069B",
+ "/seenthreedotsbelowthreedotsabove": "\u069C",
+ "/segment": "\u2313",
+ "/segol": "\u05B6",
+ "/segol13": "\u05B6",
+ "/segol1f": "\u05B6",
+ "/segol2c": "\u05B6",
+ "/segol:hb": "\u05B6",
+ "/segolhebrew": "\u05B6",
+ "/segolnarrowhebrew": "\u05B6",
+ "/segolquarterhebrew": "\u05B6",
+ "/segolta:hb": "\u0592",
+ "/segoltahebrew": "\u0592",
+ "/segolwidehebrew": "\u05B6",
+ "/seharmenian": "\u057D",
+ "/sehiragana": "\u305B",
+ "/sekatakana": "\u30BB",
+ "/sekatakanahalfwidth": "\uFF7E",
+ "/selfideographicparen": "\u3242",
+ "/semicolon": "\u003B",
+ "/semicolonarabic": "\u061B",
+ "/semicolonmonospace": "\uFF1B",
+ "/semicolonreversed": "\u204F",
+ "/semicolonsmall": "\uFE54",
+ "/semicolonunderlinefunc": "\u236E",
+ "/semidirectproductleft": "\u22CB",
+ "/semidirectproductright": "\u22CC",
+ "/semisextile": "\u26BA",
+ "/semisoftcyr": "\u048D",
+ "/semivoicedmarkkana": "\u309C",
+ "/semivoicedmarkkanahalfwidth": "\uFF9F",
+ "/sentisquare": "\u3322",
+ "/sentosquare": "\u3323",
+ "/septembertelegraph": "\u32C8",
+ "/sersetdblup": "\u22D1",
+ "/sersetnotequalup": "\u228B",
+ "/servicemark": "\u2120",
+ "/sesamedot": "\uFE45",
+ "/sesquiquadrate": "\u26BC",
+ "/setminus": "\u2216",
+ "/seven": "\u0037",
+ "/seven.inferior": "\u2087",
+ "/seven.roman": "\u2166",
+ "/seven.romansmall": "\u2176",
+ "/seven.superior": "\u2077",
+ "/sevenarabic": "\u0667",
+ "/sevenbengali": "\u09ED",
+ "/sevencircle": "\u2466",
+ "/sevencircledbl": "\u24FB",
+ "/sevencircleinversesansserif": "\u2790",
+ "/sevencomma": "\u1F108",
+ "/sevendeva": "\u096D",
+ "/seveneighths": "\u215E",
+ "/sevenfar": "\u06F7",
+ "/sevengujarati": "\u0AED",
+ "/sevengurmukhi": "\u0A6D",
+ "/sevenhackarabic": "\u0667",
+ "/sevenhangzhou": "\u3027",
+ "/sevenideographiccircled": "\u3286",
+ "/sevenideographicparen": "\u3226",
+ "/seveninferior": "\u2087",
+ "/sevenmonospace": "\uFF17",
+ "/sevenoldstyle": "\uF737",
+ "/sevenparen": "\u247A",
+ "/sevenparenthesized": "\u247A",
+ "/sevenperiod": "\u248E",
+ "/sevenpersian": "\u06F7",
+ "/sevenpointonesquare": "\u1F1A1",
+ "/sevenroman": "\u2176",
+ "/sevensuperior": "\u2077",
+ "/seventeencircle": "\u2470",
+ "/seventeencircleblack": "\u24F1",
+ "/seventeenparen": "\u2484",
+ "/seventeenparenthesized": "\u2484",
+ "/seventeenperiod": "\u2498",
+ "/seventhai": "\u0E57",
+ "/seventycirclesquare": "\u324E",
+ "/sextile": "\u26B9",
+ "/sfthyphen": "\u00AD",
+ "/shaarmenian": "\u0577",
+ "/shabengali": "\u09B6",
+ "/shacyr": "\u0448",
+ "/shacyrillic": "\u0448",
+ "/shaddaAlefIsol": "\uFC63",
+ "/shaddaDammaIsol": "\uFC61",
+ "/shaddaDammaMedi": "\uFCF3",
+ "/shaddaDammatanIsol": "\uFC5E",
+ "/shaddaFathaIsol": "\uFC60",
+ "/shaddaFathaMedi": "\uFCF2",
+ "/shaddaIsol": "\uFE7C",
+ "/shaddaKasraIsol": "\uFC62",
+ "/shaddaKasraMedi": "\uFCF4",
+ "/shaddaKasratanIsol": "\uFC5F",
+ "/shaddaMedi": "\uFE7D",
+ "/shaddaarabic": "\u0651",
+ "/shaddadammaarabic": "\uFC61",
+ "/shaddadammatanarabic": "\uFC5E",
+ "/shaddafathaarabic": "\uFC60",
+ "/shaddafathatanarabic": "\u0651",
+ "/shaddakasraarabic": "\uFC62",
+ "/shaddakasratanarabic": "\uFC5F",
+ "/shade": "\u2592",
+ "/shadedark": "\u2593",
+ "/shadelight": "\u2591",
+ "/shademedium": "\u2592",
+ "/shadeva": "\u0936",
+ "/shagujarati": "\u0AB6",
+ "/shagurmukhi": "\u0A36",
+ "/shalshelet:hb": "\u0593",
+ "/shalshelethebrew": "\u0593",
+ "/shamrock": "\u2618",
+ "/shavedIce": "\u1F367",
+ "/shbopomofo": "\u3115",
+ "/shchacyr": "\u0449",
+ "/shchacyrillic": "\u0449",
+ "/sheen": "\u0634",
+ "/sheen.fina": "\uFEB6",
+ "/sheen.init": "\uFEB7",
+ "/sheen.init_alefmaksura.fina": "\uFCFD",
+ "/sheen.init_hah.fina": "\uFD0A",
+ "/sheen.init_hah.medi": "\uFD2E",
+ "/sheen.init_hah.medi_meem.medi": "\uFD68",
+ "/sheen.init_heh.medi": "\uFD32",
+ "/sheen.init_jeem.fina": "\uFD09",
+ "/sheen.init_jeem.medi": "\uFD2D",
+ "/sheen.init_khah.fina": "\uFD0B",
+ "/sheen.init_khah.medi": "\uFD2F",
+ "/sheen.init_meem.fina": "\uFD0C",
+ "/sheen.init_meem.medi": "\uFD30",
+ "/sheen.init_meem.medi_khah.medi": "\uFD6B",
+ "/sheen.init_meem.medi_meem.medi": "\uFD6D",
+ "/sheen.init_reh.fina": "\uFD0D",
+ "/sheen.init_yeh.fina": "\uFCFE",
+ "/sheen.isol": "\uFEB5",
+ "/sheen.medi": "\uFEB8",
+ "/sheen.medi_alefmaksura.fina": "\uFD19",
+ "/sheen.medi_hah.fina": "\uFD26",
+ "/sheen.medi_hah.medi": "\uFD38",
+ "/sheen.medi_hah.medi_meem.fina": "\uFD67",
+ "/sheen.medi_hah.medi_yeh.fina": "\uFDAA",
+ "/sheen.medi_heh.medi": "\uFCEA",
+ "/sheen.medi_jeem.fina": "\uFD25",
+ "/sheen.medi_jeem.medi": "\uFD37",
+ "/sheen.medi_jeem.medi_yeh.fina": "\uFD69",
+ "/sheen.medi_khah.fina": "\uFD27",
+ "/sheen.medi_khah.medi": "\uFD39",
+ "/sheen.medi_meem.fina": "\uFD28",
+ "/sheen.medi_meem.medi": "\uFCE9",
+ "/sheen.medi_meem.medi_khah.fina": "\uFD6A",
+ "/sheen.medi_meem.medi_meem.fina": "\uFD6C",
+ "/sheen.medi_reh.fina": "\uFD29",
+ "/sheen.medi_yeh.fina": "\uFD1A",
+ "/sheenarabic": "\u0634",
+ "/sheendotbelow": "\u06FA",
+ "/sheenfinalarabic": "\uFEB6",
+ "/sheeninitialarabic": "\uFEB7",
+ "/sheenmedialarabic": "\uFEB8",
+ "/sheep": "\u1F411",
+ "/sheicoptic": "\u03E3",
+ "/shelfmod": "\u02FD",
+ "/shelfopenmod": "\u02FE",
+ "/sheqel": "\u20AA",
+ "/sheqelhebrew": "\u20AA",
+ "/sheva": "\u05B0",
+ "/sheva115": "\u05B0",
+ "/sheva15": "\u05B0",
+ "/sheva22": "\u05B0",
+ "/sheva2e": "\u05B0",
+ "/sheva:hb": "\u05B0",
+ "/shevahebrew": "\u05B0",
+ "/shevanarrowhebrew": "\u05B0",
+ "/shevaquarterhebrew": "\u05B0",
+ "/shevawidehebrew": "\u05B0",
+ "/shhacyr": "\u04BB",
+ "/shhacyrillic": "\u04BB",
+ "/shhatailcyr": "\u0527",
+ "/shield": "\u1F6E1",
+ "/shimacoptic": "\u03ED",
+ "/shin": "\u05E9",
+ "/shin:hb": "\u05E9",
+ "/shinDot:hb": "\u05C1",
+ "/shindagesh": "\uFB49",
+ "/shindageshhebrew": "\uFB49",
+ "/shindageshshindot": "\uFB2C",
+ "/shindageshshindothebrew": "\uFB2C",
+ "/shindageshsindot": "\uFB2D",
+ "/shindageshsindothebrew": "\uFB2D",
+ "/shindothebrew": "\u05C1",
+ "/shinhebrew": "\u05E9",
+ "/shinshindot": "\uFB2A",
+ "/shinshindothebrew": "\uFB2A",
+ "/shinsindot": "\uFB2B",
+ "/shinsindothebrew": "\uFB2B",
+ "/shintoshrine": "\u26E9",
+ "/shinwithdagesh:hb": "\uFB49",
+ "/shinwithdageshandshinDot:hb": "\uFB2C",
+ "/shinwithdageshandsinDot:hb": "\uFB2D",
+ "/shinwithshinDot:hb": "\uFB2A",
+ "/shinwithsinDot:hb": "\uFB2B",
+ "/ship": "\u1F6A2",
+ "/sho": "\u03F8",
+ "/shoejotupfunc": "\u235D",
+ "/shoestiledownfunc": "\u2366",
+ "/shoestileleftfunc": "\u2367",
+ "/shogipieceblack": "\u2617",
+ "/shogipiecewhite": "\u2616",
+ "/shook": "\u0282",
+ "/shootingStar": "\u1F320",
+ "/shoppingBags": "\u1F6CD",
+ "/shoppingTrolley": "\u1F6D2",
+ "/shortcake": "\u1F370",
+ "/shortequalsmod": "\uA78A",
+ "/shortoverlongmetrical": "\u23D3",
+ "/shoulderedopenbox": "\u237D",
+ "/shower": "\u1F6BF",
+ "/shvsquare": "\u1F1AA",
+ "/sicirclekatakana": "\u32DB",
+ "/sidewaysBlackDownPointingIndex": "\u1F5A1",
+ "/sidewaysBlackLeftPointingIndex": "\u1F59A",
+ "/sidewaysBlackRightPointingIndex": "\u1F59B",
+ "/sidewaysBlackUpPointingIndex": "\u1F5A0",
+ "/sidewaysWhiteDownPointingIndex": "\u1F59F",
+ "/sidewaysWhiteLeftPointingIndex": "\u1F598",
+ "/sidewaysWhiteRightPointingIndex": "\u1F599",
+ "/sidewaysWhiteUpPointingIndex": "\u1F59E",
+ "/sigma": "\u03C3",
+ "/sigma1": "\u03C2",
+ "/sigmafinal": "\u03C2",
+ "/sigmalunatedottedreversedsymbol": "\u037D",
+ "/sigmalunatedottedsymbol": "\u037C",
+ "/sigmalunatereversedsymbol": "\u037B",
+ "/sigmalunatesymbol": "\u03F2",
+ "/sigmalunatesymbolgreek": "\u03F2",
+ "/sihiragana": "\u3057",
+ "/sikatakana": "\u30B7",
+ "/sikatakanahalfwidth": "\uFF7C",
+ "/silhouetteOfJapan": "\u1F5FE",
+ "/siluqhebrew": "\u05BD",
+ "/siluqlefthebrew": "\u05BD",
+ "/similar": "\u223C",
+ "/sinDot:hb": "\u05C2",
+ "/sindothebrew": "\u05C2",
+ "/sinewave": "\u223F",
+ "/sinh:a": "\u0D85",
+ "/sinh:aa": "\u0D86",
+ "/sinh:aae": "\u0D88",
+ "/sinh:aaesign": "\u0DD1",
+ "/sinh:aasign": "\u0DCF",
+ "/sinh:ae": "\u0D87",
+ "/sinh:aesign": "\u0DD0",
+ "/sinh:ai": "\u0D93",
+ "/sinh:aisign": "\u0DDB",
+ "/sinh:anusvara": "\u0D82",
+ "/sinh:au": "\u0D96",
+ "/sinh:ausign": "\u0DDE",
+ "/sinh:ba": "\u0DB6",
+ "/sinh:bha": "\u0DB7",
+ "/sinh:ca": "\u0DA0",
+ "/sinh:cha": "\u0DA1",
+ "/sinh:da": "\u0DAF",
+ "/sinh:dda": "\u0DA9",
+ "/sinh:ddha": "\u0DAA",
+ "/sinh:dha": "\u0DB0",
+ "/sinh:e": "\u0D91",
+ "/sinh:ee": "\u0D92",
+ "/sinh:eesign": "\u0DDA",
+ "/sinh:esign": "\u0DD9",
+ "/sinh:fa": "\u0DC6",
+ "/sinh:ga": "\u0D9C",
+ "/sinh:gha": "\u0D9D",
+ "/sinh:ha": "\u0DC4",
+ "/sinh:i": "\u0D89",
+ "/sinh:ii": "\u0D8A",
+ "/sinh:iisign": "\u0DD3",
+ "/sinh:isign": "\u0DD2",
+ "/sinh:ja": "\u0DA2",
+ "/sinh:jha": "\u0DA3",
+ "/sinh:jnya": "\u0DA5",
+ "/sinh:ka": "\u0D9A",
+ "/sinh:kha": "\u0D9B",
+ "/sinh:kunddaliya": "\u0DF4",
+ "/sinh:la": "\u0DBD",
+ "/sinh:litheight": "\u0DEE",
+ "/sinh:lithfive": "\u0DEB",
+ "/sinh:lithfour": "\u0DEA",
+ "/sinh:lithnine": "\u0DEF",
+ "/sinh:lithone": "\u0DE7",
+ "/sinh:lithseven": "\u0DED",
+ "/sinh:lithsix": "\u0DEC",
+ "/sinh:liththree": "\u0DE9",
+ "/sinh:lithtwo": "\u0DE8",
+ "/sinh:lithzero": "\u0DE6",
+ "/sinh:lla": "\u0DC5",
+ "/sinh:llvocal": "\u0D90",
+ "/sinh:llvocalsign": "\u0DF3",
+ "/sinh:lvocal": "\u0D8F",
+ "/sinh:lvocalsign": "\u0DDF",
+ "/sinh:ma": "\u0DB8",
+ "/sinh:mba": "\u0DB9",
+ "/sinh:na": "\u0DB1",
+ "/sinh:nda": "\u0DB3",
+ "/sinh:nga": "\u0D9E",
+ "/sinh:nna": "\u0DAB",
+ "/sinh:nndda": "\u0DAC",
+ "/sinh:nnga": "\u0D9F",
+ "/sinh:nya": "\u0DA4",
+ "/sinh:nyja": "\u0DA6",
+ "/sinh:o": "\u0D94",
+ "/sinh:oo": "\u0D95",
+ "/sinh:oosign": "\u0DDD",
+ "/sinh:osign": "\u0DDC",
+ "/sinh:pa": "\u0DB4",
+ "/sinh:pha": "\u0DB5",
+ "/sinh:ra": "\u0DBB",
+ "/sinh:rrvocal": "\u0D8E",
+ "/sinh:rrvocalsign": "\u0DF2",
+ "/sinh:rvocal": "\u0D8D",
+ "/sinh:rvocalsign": "\u0DD8",
+ "/sinh:sa": "\u0DC3",
+ "/sinh:sha": "\u0DC1",
+ "/sinh:ssa": "\u0DC2",
+ "/sinh:ta": "\u0DAD",
+ "/sinh:tha": "\u0DAE",
+ "/sinh:tta": "\u0DA7",
+ "/sinh:ttha": "\u0DA8",
+ "/sinh:u": "\u0D8B",
+ "/sinh:usign": "\u0DD4",
+ "/sinh:uu": "\u0D8C",
+ "/sinh:uusign": "\u0DD6",
+ "/sinh:va": "\u0DC0",
+ "/sinh:virama": "\u0DCA",
+ "/sinh:visarga": "\u0D83",
+ "/sinh:ya": "\u0DBA",
+ "/sinologicaldot": "\uA78F",
+ "/sinsular": "\uA785",
+ "/siosacirclekorean": "\u3274",
+ "/siosaparenkorean": "\u3214",
+ "/sioscieuckorean": "\u317E",
+ "/sioscirclekorean": "\u3266",
+ "/sioskiyeokkorean": "\u317A",
+ "/sioskorean": "\u3145",
+ "/siosnieunkorean": "\u317B",
+ "/siosparenkorean": "\u3206",
+ "/siospieupkorean": "\u317D",
+ "/siostikeutkorean": "\u317C",
+ "/siringusquare": "\u3321",
+ "/six": "\u0036",
+ "/six.inferior": "\u2086",
+ "/six.roman": "\u2165",
+ "/six.romansmall": "\u2175",
+ "/six.superior": "\u2076",
+ "/sixPointedStarMiddleDot": "\u1F52F",
+ "/sixarabic": "\u0666",
+ "/sixbengali": "\u09EC",
+ "/sixcircle": "\u2465",
+ "/sixcircledbl": "\u24FA",
+ "/sixcircleinversesansserif": "\u278F",
+ "/sixcomma": "\u1F107",
+ "/sixdeva": "\u096C",
+ "/sixdotsvertical": "\u2E3D",
+ "/sixfar": "\u06F6",
+ "/sixgujarati": "\u0AEC",
+ "/sixgurmukhi": "\u0A6C",
+ "/sixhackarabic": "\u0666",
+ "/sixhangzhou": "\u3026",
+ "/sixideographiccircled": "\u3285",
+ "/sixideographicparen": "\u3225",
+ "/sixinferior": "\u2086",
+ "/sixlateform.roman": "\u2185",
+ "/sixmonospace": "\uFF16",
+ "/sixoldstyle": "\uF736",
+ "/sixparen": "\u2479",
+ "/sixparenthesized": "\u2479",
+ "/sixperemspace": "\u2006",
+ "/sixperiod": "\u248D",
+ "/sixpersian": "\u06F6",
+ "/sixroman": "\u2175",
+ "/sixsuperior": "\u2076",
+ "/sixteencircle": "\u246F",
+ "/sixteencircleblack": "\u24F0",
+ "/sixteencurrencydenominatorbengali": "\u09F9",
+ "/sixteenparen": "\u2483",
+ "/sixteenparenthesized": "\u2483",
+ "/sixteenperiod": "\u2497",
+ "/sixthai": "\u0E56",
+ "/sixtycirclesquare": "\u324D",
+ "/sixtypsquare": "\u1F1A3",
+ "/sjekomicyr": "\u050D",
+ "/skiAndSkiBoot": "\u1F3BF",
+ "/skier": "\u26F7",
+ "/skull": "\u1F480",
+ "/skullcrossbones": "\u2620",
+ "/slash": "\u002F",
+ "/slashbarfunc": "\u233F",
+ "/slashmonospace": "\uFF0F",
+ "/sled": "\u1F6F7",
+ "/sleeping": "\u1F4A4",
+ "/sleepingAccommodation": "\u1F6CC",
+ "/sleepingFace": "\u1F634",
+ "/sleepyFace": "\u1F62A",
+ "/sleuthOrSpy": "\u1F575",
+ "/sliceOfPizza": "\u1F355",
+ "/slightlyFrowningFace": "\u1F641",
+ "/slightlySmilingFace": "\u1F642",
+ "/slong": "\u017F",
+ "/slongdotaccent": "\u1E9B",
+ "/slope": "\u2333",
+ "/slotMachine": "\u1F3B0",
+ "/smallAirplane": "\u1F6E9",
+ "/smallBlueDiamond": "\u1F539",
+ "/smallOrangeDiamond": "\u1F538",
+ "/smallRedTriangleDOwn": "\u1F53D",
+ "/smallRedTriangleUp": "\u1F53C",
+ "/smile": "\u2323",
+ "/smileface": "\u263A",
+ "/smilingCatFaceWithHeartShapedEyes": "\u1F63B",
+ "/smilingCatFaceWithOpenMouth": "\u1F63A",
+ "/smilingFaceWithHalo": "\u1F607",
+ "/smilingFaceWithHeartShapedEyes": "\u1F60D",
+ "/smilingFaceWithHorns": "\u1F608",
+ "/smilingFaceWithOpenMouth": "\u1F603",
+ "/smilingFaceWithOpenMouthAndColdSweat": "\u1F605",
+ "/smilingFaceWithOpenMouthAndSmilingEyes": "\u1F604",
+ "/smilingFaceWithOpenMouthAndTightlyClosedEyes": "\u1F606",
+ "/smilingFaceWithSmilingEyes": "\u1F60A",
+ "/smilingFaceWithSunglasses": "\u1F60E",
+ "/smilingfaceblack": "\u263B",
+ "/smilingfacewhite": "\u263A",
+ "/smirkingFace": "\u1F60F",
+ "/smll:ampersand": "\uFE60",
+ "/smll:asterisk": "\uFE61",
+ "/smll:backslash": "\uFE68",
+ "/smll:braceleft": "\uFE5B",
+ "/smll:braceright": "\uFE5C",
+ "/smll:colon": "\uFE55",
+ "/smll:comma": "\uFE50",
+ "/smll:dollar": "\uFE69",
+ "/smll:emdash": "\uFE58",
+ "/smll:equal": "\uFE66",
+ "/smll:exclam": "\uFE57",
+ "/smll:greater": "\uFE65",
+ "/smll:hyphen": "\uFE63",
+ "/smll:ideographiccomma": "\uFE51",
+ "/smll:less": "\uFE64",
+ "/smll:numbersign": "\uFE5F",
+ "/smll:parenthesisleft": "\uFE59",
+ "/smll:parenthesisright": "\uFE5A",
+ "/smll:percent": "\uFE6A",
+ "/smll:period": "\uFE52",
+ "/smll:plus": "\uFE62",
+ "/smll:question": "\uFE56",
+ "/smll:semicolon": "\uFE54",
+ "/smll:tortoiseshellbracketleft": "\uFE5D",
+ "/smll:tortoiseshellbracketright": "\uFE5E",
+ "/smoking": "\u1F6AC",
+ "/smonospace": "\uFF53",
+ "/snail": "\u1F40C",
+ "/snake": "\u1F40D",
+ "/snowboarder": "\u1F3C2",
+ "/snowcappedMountain": "\u1F3D4",
+ "/snowman": "\u2603",
+ "/snowmanblack": "\u26C7",
+ "/snowmanoutsnow": "\u26C4",
+ "/sobliquestroke": "\uA7A9",
+ "/soccerball": "\u26BD",
+ "/societyideographiccircled": "\u3293",
+ "/societyideographicparen": "\u3233",
+ "/socirclekatakana": "\u32DE",
+ "/sofPasuq:hb": "\u05C3",
+ "/sofpasuqhebrew": "\u05C3",
+ "/softIceCream": "\u1F366",
+ "/softShellFloppyDisk": "\u1F5AC",
+ "/softcyr": "\u044C",
+ "/softhyphen": "\u00AD",
+ "/softsigncyrillic": "\u044C",
+ "/softwarefunction": "\u2394",
+ "/sohiragana": "\u305D",
+ "/sokatakana": "\u30BD",
+ "/sokatakanahalfwidth": "\uFF7F",
+ "/soliduslongoverlaycmb": "\u0338",
+ "/solidusshortoverlaycmb": "\u0337",
+ "/solidussubsetreversepreceding": "\u27C8",
+ "/solidussupersetpreceding": "\u27C9",
+ "/soonRightwardsArrowAbove": "\u1F51C",
+ "/sorusithai": "\u0E29",
+ "/sosalathai": "\u0E28",
+ "/sosothai": "\u0E0B",
+ "/sossquare": "\u1F198",
+ "/sosuathai": "\u0E2A",
+ "/soundcopyright": "\u2117",
+ "/space": "\u0020",
+ "/spacehackarabic": "\u0020",
+ "/spade": "\u2660",
+ "/spadeblack": "\u2660",
+ "/spadesuitblack": "\u2660",
+ "/spadesuitwhite": "\u2664",
+ "/spadewhite": "\u2664",
+ "/spaghetti": "\u1F35D",
+ "/sparen": "\u24AE",
+ "/sparenthesized": "\u24AE",
+ "/sparklingHeart": "\u1F496",
+ "/speakNoEvilMonkey": "\u1F64A",
+ "/speaker": "\u1F508",
+ "/speakerCancellationStroke": "\u1F507",
+ "/speakerOneSoundWave": "\u1F509",
+ "/speakerThreeSoundWaves": "\u1F50A",
+ "/speakingHeadInSilhouette": "\u1F5E3",
+ "/specialideographiccircled": "\u3295",
+ "/specialideographicparen": "\u3235",
+ "/speechBalloon": "\u1F4AC",
+ "/speedboat": "\u1F6A4",
+ "/spesmilo": "\u20B7",
+ "/sphericalangle": "\u2222",
+ "/spider": "\u1F577",
+ "/spiderWeb": "\u1F578",
+ "/spiralCalendarPad": "\u1F5D3",
+ "/spiralNotePad": "\u1F5D2",
+ "/spiralShell": "\u1F41A",
+ "/splashingSweat": "\u1F4A6",
+ "/sportsMedal": "\u1F3C5",
+ "/spoutingWhale": "\u1F433",
+ "/sppl:tildevertical": "\u2E2F",
+ "/squarebelowcmb": "\u033B",
+ "/squareblack": "\u25A0",
+ "/squarebracketleftvertical": "\uFE47",
+ "/squarebracketrightvertical": "\uFE48",
+ "/squarecap": "\u2293",
+ "/squarecc": "\u33C4",
+ "/squarecm": "\u339D",
+ "/squarecup": "\u2294",
+ "/squareddotoperator": "\u22A1",
+ "/squarediagonalcrosshatchfill": "\u25A9",
+ "/squaredj": "\u1F190",
+ "/squaredkey": "\u26BF",
+ "/squaredminus": "\u229F",
+ "/squaredplus": "\u229E",
+ "/squaredsaltire": "\u26DD",
+ "/squaredtimes": "\u22A0",
+ "/squarefourcorners": "\u26F6",
+ "/squarehalfleftblack": "\u25E7",
+ "/squarehalfrightblack": "\u25E8",
+ "/squarehorizontalfill": "\u25A4",
+ "/squareimage": "\u228F",
+ "/squareimageorequal": "\u2291",
+ "/squareimageornotequal": "\u22E4",
+ "/squarekg": "\u338F",
+ "/squarekm": "\u339E",
+ "/squarekmcapital": "\u33CE",
+ "/squareln": "\u33D1",
+ "/squarelog": "\u33D2",
+ "/squarelowerdiagonalhalfrightblack": "\u25EA",
+ "/squaremediumblack": "\u25FC",
+ "/squaremediumwhite": "\u25FB",
+ "/squaremg": "\u338E",
+ "/squaremil": "\u33D5",
+ "/squaremm": "\u339C",
+ "/squaremsquared": "\u33A1",
+ "/squareoriginal": "\u2290",
+ "/squareoriginalorequal": "\u2292",
+ "/squareoriginalornotequal": "\u22E5",
+ "/squareorthogonalcrosshatchfill": "\u25A6",
+ "/squareraised": "\u2E0B",
+ "/squaresmallblack": "\u25AA",
+ "/squaresmallmediumblack": "\u25FE",
+ "/squaresmallmediumwhite": "\u25FD",
+ "/squaresmallwhite": "\u25AB",
+ "/squareupperdiagonalhalfleftblack": "\u25E9",
+ "/squareupperlefttolowerrightfill": "\u25A7",
+ "/squareupperrighttolowerleftfill": "\u25A8",
+ "/squareverticalfill": "\u25A5",
+ "/squarewhite": "\u25A1",
+ "/squarewhitebisectinglinevertical": "\u25EB",
+ "/squarewhitelowerquadrantleft": "\u25F1",
+ "/squarewhitelowerquadrantright": "\u25F2",
+ "/squarewhiteround": "\u25A2",
+ "/squarewhiteupperquadrantleft": "\u25F0",
+ "/squarewhiteupperquadrantright": "\u25F3",
+ "/squarewhitewithsmallblack": "\u25A3",
+ "/squarewhitewithsquaresmallblack": "\u25A3",
+ "/squishquadfunc": "\u2337",
+ "/srfullwidth": "\u33DB",
+ "/srsquare": "\u33DB",
+ "/ssabengali": "\u09B7",
+ "/ssadeva": "\u0937",
+ "/ssagujarati": "\u0AB7",
+ "/ssangcieuckorean": "\u3149",
+ "/ssanghieuhkorean": "\u3185",
+ "/ssangieungkorean": "\u3180",
+ "/ssangkiyeokkorean": "\u3132",
+ "/ssangnieunkorean": "\u3165",
+ "/ssangpieupkorean": "\u3143",
+ "/ssangsioskorean": "\u3146",
+ "/ssangtikeutkorean": "\u3138",
+ "/ssuperior": "\uF6F2",
+ "/ssupmod": "\u02E2",
+ "/sswashtail": "\u023F",
+ "/stackedcommadbl": "\u2E49",
+ "/stadium": "\u1F3DF",
+ "/staffofaesculapius": "\u2695",
+ "/staffofhermes": "\u269A",
+ "/stampedEnvelope": "\u1F583",
+ "/star": "\u22C6",
+ "/starblack": "\u2605",
+ "/starcrescent": "\u262A",
+ "/stardiaeresisfunc": "\u2363",
+ "/starequals": "\u225B",
+ "/staroperator": "\u22C6",
+ "/staroutlinedwhite": "\u269D",
+ "/starwhite": "\u2606",
+ "/station": "\u1F689",
+ "/statueOfLiberty": "\u1F5FD",
+ "/steamLocomotive": "\u1F682",
+ "/steamingBowl": "\u1F35C",
+ "/stenographicfullstop": "\u2E3C",
+ "/sterling": "\u00A3",
+ "/sterlingmonospace": "\uFFE1",
+ "/stigma": "\u03DB",
+ "/stiletildefunc": "\u236D",
+ "/stockChart": "\u1F5E0",
+ "/stockideographiccircled": "\u3291",
+ "/stockideographicparen": "\u3231",
+ "/stopabove": "\u06EB",
+ "/stopbelow": "\u06EA",
+ "/straightRuler": "\u1F4CF",
+ "/straightness": "\u23E4",
+ "/strawberry": "\u1F353",
+ "/stresslowtonemod": "\uA721",
+ "/stresstonemod": "\uA720",
+ "/strictlyequivalent": "\u2263",
+ "/strokelongoverlaycmb": "\u0336",
+ "/strokeshortoverlaycmb": "\u0335",
+ "/studioMicrophone": "\u1F399",
+ "/studyideographiccircled": "\u32AB",
+ "/studyideographicparen": "\u323B",
+ "/stupa": "\u1F6D3",
+ "/subscriptalef": "\u0656",
+ "/subset": "\u2282",
+ "/subsetdbl": "\u22D0",
+ "/subsetnotequal": "\u228A",
+ "/subsetorequal": "\u2286",
+ "/succeeds": "\u227B",
+ "/succeedsbutnotequivalent": "\u22E9",
+ "/succeedsorequal": "\u227D",
+ "/succeedsorequivalent": "\u227F",
+ "/succeedsunderrelation": "\u22B1",
+ "/suchthat": "\u220B",
+ "/sucirclekatakana": "\u32DC",
+ "/suhiragana": "\u3059",
+ "/suitableideographiccircled": "\u329C",
+ "/sukatakana": "\u30B9",
+ "/sukatakanahalfwidth": "\uFF7D",
+ "/sukumendutvowel": "\uA9B9",
+ "/sukunIsol": "\uFE7E",
+ "/sukunMedi": "\uFE7F",
+ "/sukunarabic": "\u0652",
+ "/sukuvowel": "\uA9B8",
+ "/summation": "\u2211",
+ "/summationbottom": "\u23B3",
+ "/summationdblstruck": "\u2140",
+ "/summationtop": "\u23B2",
+ "/sun": "\u263C",
+ "/sunFace": "\u1F31E",
+ "/sunbehindcloud": "\u26C5",
+ "/sunflower": "\u1F33B",
+ "/sunideographiccircled": "\u3290",
+ "/sunideographicparen": "\u3230",
+ "/sunraysblack": "\u2600",
+ "/sunrayswhite": "\u263C",
+ "/sunrise": "\u1F305",
+ "/sunriseOverMountains": "\u1F304",
+ "/sunsetOverBuildings": "\u1F307",
+ "/superset": "\u2283",
+ "/supersetnotequal": "\u228B",
+ "/supersetorequal": "\u2287",
+ "/superviseideographiccircled": "\u32AC",
+ "/superviseideographicparen": "\u323C",
+ "/surfer": "\u1F3C4",
+ "/sushi": "\u1F363",
+ "/suspensionRailway": "\u1F69F",
+ "/suspensiondbl": "\u2E44",
+ "/svfullwidth": "\u33DC",
+ "/svsquare": "\u33DC",
+ "/swatchtop": "\u23F1",
+ "/swimmer": "\u1F3CA",
+ "/swungdash": "\u2053",
+ "/symbolabovethreedotsabove": "\uFBB6",
+ "/symbolbelowthreedotsabove": "\uFBB7",
+ "/symboldotabove": "\uFBB2",
+ "/symboldotbelow": "\uFBB3",
+ "/symboldoubleverticalbarbelow": "\uFBBC",
+ "/symbolfourdotsabove": "\uFBBA",
+ "/symbolfourdotsbelow": "\uFBBB",
+ "/symbolpointingabovedownthreedotsabove": "\uFBB8",
+ "/symbolpointingbelowdownthreedotsabove": "\uFBB9",
+ "/symbolring": "\uFBBF",
+ "/symboltahabovesmall": "\uFBC0",
+ "/symboltahbelowsmall": "\uFBC1",
+ "/symboltwodotsabove": "\uFBB4",
+ "/symboltwodotsbelow": "\uFBB5",
+ "/symboltwodotsverticallyabove": "\uFBBD",
+ "/symboltwodotsverticallybelow": "\uFBBE",
+ "/symmetry": "\u232F",
+ "/synagogue": "\u1F54D",
+ "/syouwaerasquare": "\u337C",
+ "/syringe": "\u1F489",
+ "/t": "\u0074",
+ "/t-shirt": "\u1F455",
+ "/t.inferior": "\u209C",
+ "/tabengali": "\u09A4",
+ "/tableTennisPaddleAndBall": "\u1F3D3",
+ "/tacirclekatakana": "\u32DF",
+ "/tackcircleaboveup": "\u27DF",
+ "/tackdiaeresisupfunc": "\u2361",
+ "/tackdown": "\u22A4",
+ "/tackdownmod": "\u02D5",
+ "/tackjotdownfunc": "\u234E",
+ "/tackjotupfunc": "\u2355",
+ "/tackleft": "\u22A3",
+ "/tackleftright": "\u27DB",
+ "/tackoverbarupfunc": "\u2351",
+ "/tackright": "\u22A2",
+ "/tackunderlinedownfunc": "\u234A",
+ "/tackup": "\u22A5",
+ "/tackupmod": "\u02D4",
+ "/taco": "\u1F32E",
+ "/tadeva": "\u0924",
+ "/tagujarati": "\u0AA4",
+ "/tagurmukhi": "\u0A24",
+ "/tah": "\u0637",
+ "/tah.fina": "\uFEC2",
+ "/tah.init": "\uFEC3",
+ "/tah.init_alefmaksura.fina": "\uFCF5",
+ "/tah.init_hah.fina": "\uFC26",
+ "/tah.init_hah.medi": "\uFCB8",
+ "/tah.init_meem.fina": "\uFC27",
+ "/tah.init_meem.medi": "\uFD33",
+ "/tah.init_meem.medi_hah.medi": "\uFD72",
+ "/tah.init_meem.medi_meem.medi": "\uFD73",
+ "/tah.init_yeh.fina": "\uFCF6",
+ "/tah.isol": "\uFEC1",
+ "/tah.medi": "\uFEC4",
+ "/tah.medi_alefmaksura.fina": "\uFD11",
+ "/tah.medi_meem.medi": "\uFD3A",
+ "/tah.medi_meem.medi_hah.fina": "\uFD71",
+ "/tah.medi_meem.medi_yeh.fina": "\uFD74",
+ "/tah.medi_yeh.fina": "\uFD12",
+ "/tahabove": "\u0615",
+ "/taharabic": "\u0637",
+ "/tahfinalarabic": "\uFEC2",
+ "/tahinitialarabic": "\uFEC3",
+ "/tahiragana": "\u305F",
+ "/tahmedialarabic": "\uFEC4",
+ "/tahthreedotsabove": "\u069F",
+ "/taisyouerasquare": "\u337D",
+ "/takatakana": "\u30BF",
+ "/takatakanahalfwidth": "\uFF80",
+ "/takhallus": "\u0614",
+ "/talingvowel": "\uA9BA",
+ "/taml:a": "\u0B85",
+ "/taml:aa": "\u0B86",
+ "/taml:aasign": "\u0BBE",
+ "/taml:ai": "\u0B90",
+ "/taml:aisign": "\u0BC8",
+ "/taml:anusvarasign": "\u0B82",
+ "/taml:asabovesign": "\u0BF8",
+ "/taml:au": "\u0B94",
+ "/taml:aulengthmark": "\u0BD7",
+ "/taml:ausign": "\u0BCC",
+ "/taml:ca": "\u0B9A",
+ "/taml:creditsign": "\u0BF7",
+ "/taml:daysign": "\u0BF3",
+ "/taml:debitsign": "\u0BF6",
+ "/taml:e": "\u0B8E",
+ "/taml:ee": "\u0B8F",
+ "/taml:eesign": "\u0BC7",
+ "/taml:eight": "\u0BEE",
+ "/taml:esign": "\u0BC6",
+ "/taml:five": "\u0BEB",
+ "/taml:four": "\u0BEA",
+ "/taml:ha": "\u0BB9",
+ "/taml:i": "\u0B87",
+ "/taml:ii": "\u0B88",
+ "/taml:iisign": "\u0BC0",
+ "/taml:isign": "\u0BBF",
+ "/taml:ja": "\u0B9C",
+ "/taml:ka": "\u0B95",
+ "/taml:la": "\u0BB2",
+ "/taml:lla": "\u0BB3",
+ "/taml:llla": "\u0BB4",
+ "/taml:ma": "\u0BAE",
+ "/taml:monthsign": "\u0BF4",
+ "/taml:na": "\u0BA8",
+ "/taml:nga": "\u0B99",
+ "/taml:nine": "\u0BEF",
+ "/taml:nna": "\u0BA3",
+ "/taml:nnna": "\u0BA9",
+ "/taml:nya": "\u0B9E",
+ "/taml:o": "\u0B92",
+ "/taml:om": "\u0BD0",
+ "/taml:one": "\u0BE7",
+ "/taml:onehundred": "\u0BF1",
+ "/taml:onethousand": "\u0BF2",
+ "/taml:oo": "\u0B93",
+ "/taml:oosign": "\u0BCB",
+ "/taml:osign": "\u0BCA",
+ "/taml:pa": "\u0BAA",
+ "/taml:ra": "\u0BB0",
+ "/taml:rra": "\u0BB1",
+ "/taml:rupeesign": "\u0BF9",
+ "/taml:sa": "\u0BB8",
+ "/taml:seven": "\u0BED",
+ "/taml:sha": "\u0BB6",
+ "/taml:sign": "\u0BFA",
+ "/taml:six": "\u0BEC",
+ "/taml:ssa": "\u0BB7",
+ "/taml:ta": "\u0BA4",
+ "/taml:ten": "\u0BF0",
+ "/taml:three": "\u0BE9",
+ "/taml:tta": "\u0B9F",
+ "/taml:two": "\u0BE8",
+ "/taml:u": "\u0B89",
+ "/taml:usign": "\u0BC1",
+ "/taml:uu": "\u0B8A",
+ "/taml:uusign": "\u0BC2",
+ "/taml:va": "\u0BB5",
+ "/taml:viramasign": "\u0BCD",
+ "/taml:visargasign": "\u0B83",
+ "/taml:ya": "\u0BAF",
+ "/taml:yearsign": "\u0BF5",
+ "/taml:zero": "\u0BE6",
+ "/tamurda": "\uA9A1",
+ "/tanabataTree": "\u1F38B",
+ "/tangerine": "\u1F34A",
+ "/tapeCartridge": "\u1F5AD",
+ "/tarungvowel": "\uA9B4",
+ "/tatweelFathatanAbove": "\uFE71",
+ "/tatweelarabic": "\u0640",
+ "/tau": "\u03C4",
+ "/taurus": "\u2649",
+ "/tav": "\u05EA",
+ "/tav:hb": "\u05EA",
+ "/tavdages": "\uFB4A",
+ "/tavdagesh": "\uFB4A",
+ "/tavdageshhebrew": "\uFB4A",
+ "/tavhebrew": "\u05EA",
+ "/tavwide:hb": "\uFB28",
+ "/tavwithdagesh:hb": "\uFB4A",
+ "/taxi": "\u1F695",
+ "/tbar": "\u0167",
+ "/tbopomofo": "\u310A",
+ "/tcaron": "\u0165",
+ "/tccurl": "\u02A8",
+ "/tcedilla": "\u0163",
+ "/tcheh": "\u0686",
+ "/tcheh.fina": "\uFB7B",
+ "/tcheh.init": "\uFB7C",
+ "/tcheh.isol": "\uFB7A",
+ "/tcheh.medi": "\uFB7D",
+ "/tcheharabic": "\u0686",
+ "/tchehdotabove": "\u06BF",
+ "/tcheheh": "\u0687",
+ "/tcheheh.fina": "\uFB7F",
+ "/tcheheh.init": "\uFB80",
+ "/tcheheh.isol": "\uFB7E",
+ "/tcheheh.medi": "\uFB81",
+ "/tchehfinalarabic": "\uFB7B",
+ "/tchehinitialarabic": "\uFB7C",
+ "/tchehmedialarabic": "\uFB7D",
+ "/tchehmeeminitialarabic": "\uFB7C",
+ "/tcircle": "\u24E3",
+ "/tcircumflexbelow": "\u1E71",
+ "/tcommaaccent": "\u0163",
+ "/tcurl": "\u0236",
+ "/tdieresis": "\u1E97",
+ "/tdot": "\u1E6B",
+ "/tdotaccent": "\u1E6B",
+ "/tdotbelow": "\u1E6D",
+ "/teacupOutHandle": "\u1F375",
+ "/tear-offCalendar": "\u1F4C6",
+ "/tecirclekatakana": "\u32E2",
+ "/tecyr": "\u0442",
+ "/tecyrillic": "\u0442",
+ "/tedescendercyrillic": "\u04AD",
+ "/teh": "\u062A",
+ "/teh.fina": "\uFE96",
+ "/teh.init": "\uFE97",
+ "/teh.init_alefmaksura.fina": "\uFC0F",
+ "/teh.init_hah.fina": "\uFC0C",
+ "/teh.init_hah.medi": "\uFCA2",
+ "/teh.init_hah.medi_jeem.medi": "\uFD52",
+ "/teh.init_hah.medi_meem.medi": "\uFD53",
+ "/teh.init_heh.medi": "\uFCA5",
+ "/teh.init_jeem.fina": "\uFC0B",
+ "/teh.init_jeem.medi": "\uFCA1",
+ "/teh.init_jeem.medi_meem.medi": "\uFD50",
+ "/teh.init_khah.fina": "\uFC0D",
+ "/teh.init_khah.medi": "\uFCA3",
+ "/teh.init_khah.medi_meem.medi": "\uFD54",
+ "/teh.init_meem.fina": "\uFC0E",
+ "/teh.init_meem.medi": "\uFCA4",
+ "/teh.init_meem.medi_hah.medi": "\uFD56",
+ "/teh.init_meem.medi_jeem.medi": "\uFD55",
+ "/teh.init_meem.medi_khah.medi": "\uFD57",
+ "/teh.init_yeh.fina": "\uFC10",
+ "/teh.isol": "\uFE95",
+ "/teh.medi": "\uFE98",
+ "/teh.medi_alefmaksura.fina": "\uFC74",
+ "/teh.medi_hah.medi_jeem.fina": "\uFD51",
+ "/teh.medi_heh.medi": "\uFCE4",
+ "/teh.medi_jeem.medi_alefmaksura.fina": "\uFDA0",
+ "/teh.medi_jeem.medi_yeh.fina": "\uFD9F",
+ "/teh.medi_khah.medi_alefmaksura.fina": "\uFDA2",
+ "/teh.medi_khah.medi_yeh.fina": "\uFDA1",
+ "/teh.medi_meem.fina": "\uFC72",
+ "/teh.medi_meem.medi": "\uFCE3",
+ "/teh.medi_meem.medi_alefmaksura.fina": "\uFDA4",
+ "/teh.medi_meem.medi_yeh.fina": "\uFDA3",
+ "/teh.medi_noon.fina": "\uFC73",
+ "/teh.medi_reh.fina": "\uFC70",
+ "/teh.medi_yeh.fina": "\uFC75",
+ "/teh.medi_zain.fina": "\uFC71",
+ "/teharabic": "\u062A",
+ "/tehdownthreedotsabove": "\u067D",
+ "/teheh": "\u067F",
+ "/teheh.fina": "\uFB63",
+ "/teheh.init": "\uFB64",
+ "/teheh.isol": "\uFB62",
+ "/teheh.medi": "\uFB65",
+ "/tehfinalarabic": "\uFE96",
+ "/tehhahinitialarabic": "\uFCA2",
+ "/tehhahisolatedarabic": "\uFC0C",
+ "/tehinitialarabic": "\uFE97",
+ "/tehiragana": "\u3066",
+ "/tehjeeminitialarabic": "\uFCA1",
+ "/tehjeemisolatedarabic": "\uFC0B",
+ "/tehmarbuta": "\u0629",
+ "/tehmarbuta.fina": "\uFE94",
+ "/tehmarbuta.isol": "\uFE93",
+ "/tehmarbutaarabic": "\u0629",
+ "/tehmarbutafinalarabic": "\uFE94",
+ "/tehmarbutagoal": "\u06C3",
+ "/tehmedialarabic": "\uFE98",
+ "/tehmeeminitialarabic": "\uFCA4",
+ "/tehmeemisolatedarabic": "\uFC0E",
+ "/tehnoonfinalarabic": "\uFC73",
+ "/tehring": "\u067C",
+ "/tekatakana": "\u30C6",
+ "/tekatakanahalfwidth": "\uFF83",
+ "/telephone": "\u2121",
+ "/telephoneOnTopOfModem": "\u1F580",
+ "/telephoneReceiver": "\u1F4DE",
+ "/telephoneReceiverPage": "\u1F57C",
+ "/telephoneblack": "\u260E",
+ "/telephonerecorder": "\u2315",
+ "/telephonewhite": "\u260F",
+ "/telescope": "\u1F52D",
+ "/television": "\u1F4FA",
+ "/telishaGedolah:hb": "\u05A0",
+ "/telishaQetannah:hb": "\u05A9",
+ "/telishagedolahebrew": "\u05A0",
+ "/telishaqetanahebrew": "\u05A9",
+ "/telu:a": "\u0C05",
+ "/telu:aa": "\u0C06",
+ "/telu:aasign": "\u0C3E",
+ "/telu:ai": "\u0C10",
+ "/telu:ailengthmark": "\u0C56",
+ "/telu:aisign": "\u0C48",
+ "/telu:anusvarasign": "\u0C02",
+ "/telu:au": "\u0C14",
+ "/telu:ausign": "\u0C4C",
+ "/telu:avagrahasign": "\u0C3D",
+ "/telu:ba": "\u0C2C",
+ "/telu:bha": "\u0C2D",
+ "/telu:bindusigncandra": "\u0C01",
+ "/telu:ca": "\u0C1A",
+ "/telu:cha": "\u0C1B",
+ "/telu:combiningbinduabovesigncandra": "\u0C00",
+ "/telu:da": "\u0C26",
+ "/telu:dda": "\u0C21",
+ "/telu:ddha": "\u0C22",
+ "/telu:dha": "\u0C27",
+ "/telu:dza": "\u0C59",
+ "/telu:e": "\u0C0E",
+ "/telu:ee": "\u0C0F",
+ "/telu:eesign": "\u0C47",
+ "/telu:eight": "\u0C6E",
+ "/telu:esign": "\u0C46",
+ "/telu:five": "\u0C6B",
+ "/telu:four": "\u0C6A",
+ "/telu:fractiononeforevenpowersoffour": "\u0C7C",
+ "/telu:fractiononeforoddpowersoffour": "\u0C79",
+ "/telu:fractionthreeforevenpowersoffour": "\u0C7E",
+ "/telu:fractionthreeforoddpowersoffour": "\u0C7B",
+ "/telu:fractiontwoforevenpowersoffour": "\u0C7D",
+ "/telu:fractiontwoforoddpowersoffour": "\u0C7A",
+ "/telu:fractionzeroforoddpowersoffour": "\u0C78",
+ "/telu:ga": "\u0C17",
+ "/telu:gha": "\u0C18",
+ "/telu:ha": "\u0C39",
+ "/telu:i": "\u0C07",
+ "/telu:ii": "\u0C08",
+ "/telu:iisign": "\u0C40",
+ "/telu:isign": "\u0C3F",
+ "/telu:ja": "\u0C1C",
+ "/telu:jha": "\u0C1D",
+ "/telu:ka": "\u0C15",
+ "/telu:kha": "\u0C16",
+ "/telu:la": "\u0C32",
+ "/telu:lengthmark": "\u0C55",
+ "/telu:lla": "\u0C33",
+ "/telu:llla": "\u0C34",
+ "/telu:llsignvocal": "\u0C63",
+ "/telu:llvocal": "\u0C61",
+ "/telu:lsignvocal": "\u0C62",
+ "/telu:lvocal": "\u0C0C",
+ "/telu:ma": "\u0C2E",
+ "/telu:na": "\u0C28",
+ "/telu:nga": "\u0C19",
+ "/telu:nine": "\u0C6F",
+ "/telu:nna": "\u0C23",
+ "/telu:nya": "\u0C1E",
+ "/telu:o": "\u0C12",
+ "/telu:one": "\u0C67",
+ "/telu:oo": "\u0C13",
+ "/telu:oosign": "\u0C4B",
+ "/telu:osign": "\u0C4A",
+ "/telu:pa": "\u0C2A",
+ "/telu:pha": "\u0C2B",
+ "/telu:ra": "\u0C30",
+ "/telu:rra": "\u0C31",
+ "/telu:rrra": "\u0C5A",
+ "/telu:rrsignvocal": "\u0C44",
+ "/telu:rrvocal": "\u0C60",
+ "/telu:rsignvocal": "\u0C43",
+ "/telu:rvocal": "\u0C0B",
+ "/telu:sa": "\u0C38",
+ "/telu:seven": "\u0C6D",
+ "/telu:sha": "\u0C36",
+ "/telu:six": "\u0C6C",
+ "/telu:ssa": "\u0C37",
+ "/telu:ta": "\u0C24",
+ "/telu:tha": "\u0C25",
+ "/telu:three": "\u0C69",
+ "/telu:tsa": "\u0C58",
+ "/telu:tta": "\u0C1F",
+ "/telu:ttha": "\u0C20",
+ "/telu:tuumusign": "\u0C7F",
+ "/telu:two": "\u0C68",
+ "/telu:u": "\u0C09",
+ "/telu:usign": "\u0C41",
+ "/telu:uu": "\u0C0A",
+ "/telu:uusign": "\u0C42",
+ "/telu:va": "\u0C35",
+ "/telu:viramasign": "\u0C4D",
+ "/telu:visargasign": "\u0C03",
+ "/telu:ya": "\u0C2F",
+ "/telu:zero": "\u0C66",
+ "/ten.roman": "\u2169",
+ "/ten.romansmall": "\u2179",
+ "/tencircle": "\u2469",
+ "/tencircledbl": "\u24FE",
+ "/tencirclesquare": "\u3248",
+ "/tenge": "\u20B8",
+ "/tenhangzhou": "\u3038",
+ "/tenideographiccircled": "\u3289",
+ "/tenideographicparen": "\u3229",
+ "/tennisRacquetAndBall": "\u1F3BE",
+ "/tenparen": "\u247D",
+ "/tenparenthesized": "\u247D",
+ "/tenperiod": "\u2491",
+ "/tenroman": "\u2179",
+ "/tent": "\u26FA",
+ "/tenthousand.roman": "\u2182",
+ "/tesh": "\u02A7",
+ "/tet": "\u05D8",
+ "/tet:hb": "\u05D8",
+ "/tetailcyr": "\u04AD",
+ "/tetdagesh": "\uFB38",
+ "/tetdageshhebrew": "\uFB38",
+ "/tethebrew": "\u05D8",
+ "/tetrasememetrical": "\u23D8",
+ "/tetsecyr": "\u04B5",
+ "/tetsecyrillic": "\u04B5",
+ "/tetwithdagesh:hb": "\uFB38",
+ "/tevir:hb": "\u059B",
+ "/tevirhebrew": "\u059B",
+ "/tevirlefthebrew": "\u059B",
+ "/thabengali": "\u09A5",
+ "/thadeva": "\u0925",
+ "/thagujarati": "\u0AA5",
+ "/thagurmukhi": "\u0A25",
+ "/thai:angkhankhu": "\u0E5A",
+ "/thai:baht": "\u0E3F",
+ "/thai:bobaimai": "\u0E1A",
+ "/thai:chochan": "\u0E08",
+ "/thai:chochang": "\u0E0A",
+ "/thai:choching": "\u0E09",
+ "/thai:chochoe": "\u0E0C",
+ "/thai:dochada": "\u0E0E",
+ "/thai:dodek": "\u0E14",
+ "/thai:eight": "\u0E58",
+ "/thai:five": "\u0E55",
+ "/thai:fofa": "\u0E1D",
+ "/thai:fofan": "\u0E1F",
+ "/thai:fongman": "\u0E4F",
+ "/thai:four": "\u0E54",
+ "/thai:hohip": "\u0E2B",
+ "/thai:honokhuk": "\u0E2E",
+ "/thai:khokhai": "\u0E02",
+ "/thai:khokhon": "\u0E05",
+ "/thai:khokhuat": "\u0E03",
+ "/thai:khokhwai": "\u0E04",
+ "/thai:khomut": "\u0E5B",
+ "/thai:khorakhang": "\u0E06",
+ "/thai:kokai": "\u0E01",
+ "/thai:lakkhangyao": "\u0E45",
+ "/thai:lochula": "\u0E2C",
+ "/thai:loling": "\u0E25",
+ "/thai:lu": "\u0E26",
+ "/thai:maichattawa": "\u0E4B",
+ "/thai:maiek": "\u0E48",
+ "/thai:maihan-akat": "\u0E31",
+ "/thai:maitaikhu": "\u0E47",
+ "/thai:maitho": "\u0E49",
+ "/thai:maitri": "\u0E4A",
+ "/thai:maiyamok": "\u0E46",
+ "/thai:moma": "\u0E21",
+ "/thai:ngongu": "\u0E07",
+ "/thai:nikhahit": "\u0E4D",
+ "/thai:nine": "\u0E59",
+ "/thai:nonen": "\u0E13",
+ "/thai:nonu": "\u0E19",
+ "/thai:oang": "\u0E2D",
+ "/thai:one": "\u0E51",
+ "/thai:paiyannoi": "\u0E2F",
+ "/thai:phinthu": "\u0E3A",
+ "/thai:phophan": "\u0E1E",
+ "/thai:phophung": "\u0E1C",
+ "/thai:phosamphao": "\u0E20",
+ "/thai:popla": "\u0E1B",
+ "/thai:rorua": "\u0E23",
+ "/thai:ru": "\u0E24",
+ "/thai:saraa": "\u0E30",
+ "/thai:saraaa": "\u0E32",
+ "/thai:saraae": "\u0E41",
+ "/thai:saraaimaimalai": "\u0E44",
+ "/thai:saraaimaimuan": "\u0E43",
+ "/thai:saraam": "\u0E33",
+ "/thai:sarae": "\u0E40",
+ "/thai:sarai": "\u0E34",
+ "/thai:saraii": "\u0E35",
+ "/thai:sarao": "\u0E42",
+ "/thai:sarau": "\u0E38",
+ "/thai:saraue": "\u0E36",
+ "/thai:sarauee": "\u0E37",
+ "/thai:sarauu": "\u0E39",
+ "/thai:seven": "\u0E57",
+ "/thai:six": "\u0E56",
+ "/thai:sorusi": "\u0E29",
+ "/thai:sosala": "\u0E28",
+ "/thai:soso": "\u0E0B",
+ "/thai:sosua": "\u0E2A",
+ "/thai:thanthakhat": "\u0E4C",
+ "/thai:thonangmontho": "\u0E11",
+ "/thai:thophuthao": "\u0E12",
+ "/thai:thothahan": "\u0E17",
+ "/thai:thothan": "\u0E10",
+ "/thai:thothong": "\u0E18",
+ "/thai:thothung": "\u0E16",
+ "/thai:three": "\u0E53",
+ "/thai:topatak": "\u0E0F",
+ "/thai:totao": "\u0E15",
+ "/thai:two": "\u0E52",
+ "/thai:wowaen": "\u0E27",
+ "/thai:yamakkan": "\u0E4E",
+ "/thai:yoyak": "\u0E22",
+ "/thai:yoying": "\u0E0D",
+ "/thai:zero": "\u0E50",
+ "/thal": "\u0630",
+ "/thal.fina": "\uFEAC",
+ "/thal.init_superscriptalef.fina": "\uFC5B",
+ "/thal.isol": "\uFEAB",
+ "/thalarabic": "\u0630",
+ "/thalfinalarabic": "\uFEAC",
+ "/thanthakhatlowleftthai": "\uF898",
+ "/thanthakhatlowrightthai": "\uF897",
+ "/thanthakhatthai": "\u0E4C",
+ "/thanthakhatupperleftthai": "\uF896",
+ "/theh": "\u062B",
+ "/theh.fina": "\uFE9A",
+ "/theh.init": "\uFE9B",
+ "/theh.init_alefmaksura.fina": "\uFC13",
+ "/theh.init_jeem.fina": "\uFC11",
+ "/theh.init_meem.fina": "\uFC12",
+ "/theh.init_meem.medi": "\uFCA6",
+ "/theh.init_yeh.fina": "\uFC14",
+ "/theh.isol": "\uFE99",
+ "/theh.medi": "\uFE9C",
+ "/theh.medi_alefmaksura.fina": "\uFC7A",
+ "/theh.medi_heh.medi": "\uFCE6",
+ "/theh.medi_meem.fina": "\uFC78",
+ "/theh.medi_meem.medi": "\uFCE5",
+ "/theh.medi_noon.fina": "\uFC79",
+ "/theh.medi_reh.fina": "\uFC76",
+ "/theh.medi_yeh.fina": "\uFC7B",
+ "/theh.medi_zain.fina": "\uFC77",
+ "/theharabic": "\u062B",
+ "/thehfinalarabic": "\uFE9A",
+ "/thehinitialarabic": "\uFE9B",
+ "/thehmedialarabic": "\uFE9C",
+ "/thereexists": "\u2203",
+ "/therefore": "\u2234",
+ "/thermometer": "\u1F321",
+ "/theta": "\u03B8",
+ "/theta.math": "\u03D1",
+ "/theta1": "\u03D1",
+ "/thetasymbolgreek": "\u03D1",
+ "/thieuthacirclekorean": "\u3279",
+ "/thieuthaparenkorean": "\u3219",
+ "/thieuthcirclekorean": "\u326B",
+ "/thieuthkorean": "\u314C",
+ "/thieuthparenkorean": "\u320B",
+ "/thinspace": "\u2009",
+ "/thirteencircle": "\u246C",
+ "/thirteencircleblack": "\u24ED",
+ "/thirteenparen": "\u2480",
+ "/thirteenparenthesized": "\u2480",
+ "/thirteenperiod": "\u2494",
+ "/thirtycircle": "\u325A",
+ "/thirtycirclesquare": "\u324A",
+ "/thirtyeightcircle": "\u32B3",
+ "/thirtyfivecircle": "\u325F",
+ "/thirtyfourcircle": "\u325E",
+ "/thirtyhangzhou": "\u303A",
+ "/thirtyninecircle": "\u32B4",
+ "/thirtyonecircle": "\u325B",
+ "/thirtysevencircle": "\u32B2",
+ "/thirtysixcircle": "\u32B1",
+ "/thirtythreecircle": "\u325D",
+ "/thirtytwocircle": "\u325C",
+ "/thonangmonthothai": "\u0E11",
+ "/thook": "\u01AD",
+ "/thophuthaothai": "\u0E12",
+ "/thorn": "\u00FE",
+ "/thornstroke": "\uA765",
+ "/thornstrokedescender": "\uA767",
+ "/thothahanthai": "\u0E17",
+ "/thothanthai": "\u0E10",
+ "/thothongthai": "\u0E18",
+ "/thothungthai": "\u0E16",
+ "/thoughtBalloon": "\u1F4AD",
+ "/thousandcyrillic": "\u0482",
+ "/thousandscyr": "\u0482",
+ "/thousandsseparator": "\u066C",
+ "/thousandsseparatorarabic": "\u066C",
+ "/thousandsseparatorpersian": "\u066C",
+ "/three": "\u0033",
+ "/three.inferior": "\u2083",
+ "/three.roman": "\u2162",
+ "/three.romansmall": "\u2172",
+ "/threeButtonMouse": "\u1F5B1",
+ "/threeNetworkedComputers": "\u1F5A7",
+ "/threeRaysAbove": "\u1F5E4",
+ "/threeRaysBelow": "\u1F5E5",
+ "/threeRaysLeft": "\u1F5E6",
+ "/threeRaysRight": "\u1F5E7",
+ "/threeSpeechBubbles": "\u1F5EB",
+ "/threearabic": "\u0663",
+ "/threebengali": "\u09E9",
+ "/threecircle": "\u2462",
+ "/threecircledbl": "\u24F7",
+ "/threecircleinversesansserif": "\u278C",
+ "/threecomma": "\u1F104",
+ "/threedeva": "\u0969",
+ "/threedimensionalangle": "\u27C0",
+ "/threedotpunctuation": "\u2056",
+ "/threedotsaboveabove": "\u06DB",
+ "/threedsquare": "\u1F19B",
+ "/threeeighths": "\u215C",
+ "/threefar": "\u06F3",
+ "/threefifths": "\u2157",
+ "/threegujarati": "\u0AE9",
+ "/threegurmukhi": "\u0A69",
+ "/threehackarabic": "\u0663",
+ "/threehangzhou": "\u3023",
+ "/threeideographiccircled": "\u3282",
+ "/threeideographicparen": "\u3222",
+ "/threeinferior": "\u2083",
+ "/threelinesconvergingleft": "\u269F",
+ "/threelinesconvergingright": "\u269E",
+ "/threemonospace": "\uFF13",
+ "/threenumeratorbengali": "\u09F6",
+ "/threeoldstyle": "\uF733",
+ "/threeparen": "\u2476",
+ "/threeparenthesized": "\u2476",
+ "/threeperemspace": "\u2004",
+ "/threeperiod": "\u248A",
+ "/threepersian": "\u06F3",
+ "/threequarters": "\u00BE",
+ "/threequartersemdash": "\uF6DE",
+ "/threerightarrows": "\u21F6",
+ "/threeroman": "\u2172",
+ "/threesuperior": "\u00B3",
+ "/threethai": "\u0E53",
+ "/thumbsDownSign": "\u1F44E",
+ "/thumbsUpSign": "\u1F44D",
+ "/thundercloudrain": "\u26C8",
+ "/thunderstorm": "\u2608",
+ "/thzfullwidth": "\u3394",
+ "/thzsquare": "\u3394",
+ "/tibt:AA": "\u0F60",
+ "/tibt:a": "\u0F68",
+ "/tibt:aavowelsign": "\u0F71",
+ "/tibt:angkhanggyasmark": "\u0F3D",
+ "/tibt:angkhanggyonmark": "\u0F3C",
+ "/tibt:astrologicalkhyudpasign": "\u0F18",
+ "/tibt:astrologicalsdongtshugssign": "\u0F19",
+ "/tibt:astrologicalsgragcancharrtagssign": "\u0F17",
+ "/tibt:asubjoined": "\u0FB8",
+ "/tibt:ba": "\u0F56",
+ "/tibt:basubjoined": "\u0FA6",
+ "/tibt:bha": "\u0F57",
+ "/tibt:bhasubjoined": "\u0FA7",
+ "/tibt:bkashogyigmgomark": "\u0F0A",
+ "/tibt:brdarnyingyigmgomdunmainitialmark": "\u0FD3",
+ "/tibt:brdarnyingyigmgosgabmaclosingmark": "\u0FD4",
+ "/tibt:bsdusrtagsmark": "\u0F34",
+ "/tibt:bskashoggimgorgyanmark": "\u0FD0",
+ "/tibt:bskuryigmgomark": "\u0F09",
+ "/tibt:ca": "\u0F45",
+ "/tibt:cangteucantillationsign": "\u0FC2",
+ "/tibt:caretdzudrtagsbzhimigcanmark": "\u0F36",
+ "/tibt:caretdzudrtagsmelongcanmark": "\u0F13",
+ "/tibt:caretyigmgophurshadmamark": "\u0F06",
+ "/tibt:casubjoined": "\u0F95",
+ "/tibt:cha": "\u0F46",
+ "/tibt:chadrtagslogotypesign": "\u0F15",
+ "/tibt:chasubjoined": "\u0F96",
+ "/tibt:chemgomark": "\u0F38",
+ "/tibt:da": "\u0F51",
+ "/tibt:dasubjoined": "\u0FA1",
+ "/tibt:dda": "\u0F4C",
+ "/tibt:ddasubjoined": "\u0F9C",
+ "/tibt:ddha": "\u0F4D",
+ "/tibt:ddhasubjoined": "\u0F9D",
+ "/tibt:delimitertshegbstarmark": "\u0F0C",
+ "/tibt:dha": "\u0F52",
+ "/tibt:dhasubjoined": "\u0FA2",
+ "/tibt:drilbusymbol": "\u0FC4",
+ "/tibt:dza": "\u0F5B",
+ "/tibt:dzasubjoined": "\u0FAB",
+ "/tibt:dzha": "\u0F5C",
+ "/tibt:dzhasubjoined": "\u0FAC",
+ "/tibt:eevowelsign": "\u0F7B",
+ "/tibt:eight": "\u0F28",
+ "/tibt:evowelsign": "\u0F7A",
+ "/tibt:five": "\u0F25",
+ "/tibt:four": "\u0F24",
+ "/tibt:ga": "\u0F42",
+ "/tibt:gasubjoined": "\u0F92",
+ "/tibt:gha": "\u0F43",
+ "/tibt:ghasubjoined": "\u0F93",
+ "/tibt:grucanrgyingssign": "\u0F8A",
+ "/tibt:grumedrgyingssign": "\u0F8B",
+ "/tibt:gtertshegmark": "\u0F14",
+ "/tibt:gteryigmgotruncatedamark": "\u0F01",
+ "/tibt:gteryigmgoumgtertshegmamark": "\u0F03",
+ "/tibt:gteryigmgoumrnambcadmamark": "\u0F02",
+ "/tibt:gugrtagsgyasmark": "\u0F3B",
+ "/tibt:gugrtagsgyonmark": "\u0F3A",
+ "/tibt:ha": "\u0F67",
+ "/tibt:halantamark": "\u0F84",
+ "/tibt:halfeight": "\u0F31",
+ "/tibt:halffive": "\u0F2E",
+ "/tibt:halffour": "\u0F2D",
+ "/tibt:halfnine": "\u0F32",
+ "/tibt:halfone": "\u0F2A",
+ "/tibt:halfseven": "\u0F30",
+ "/tibt:halfsix": "\u0F2F",
+ "/tibt:halfthree": "\u0F2C",
+ "/tibt:halftwo": "\u0F2B",
+ "/tibt:halfzero": "\u0F33",
+ "/tibt:hasubjoined": "\u0FB7",
+ "/tibt:heavybeatcantillationsign": "\u0FC0",
+ "/tibt:iivowelsign": "\u0F73",
+ "/tibt:intersyllabictshegmark": "\u0F0B",
+ "/tibt:invertedmchucansign": "\u0F8C",
+ "/tibt:invertedmchucansubjoinedsign": "\u0F8F",
+ "/tibt:ivowelsign": "\u0F72",
+ "/tibt:ja": "\u0F47",
+ "/tibt:jasubjoined": "\u0F97",
+ "/tibt:ka": "\u0F40",
+ "/tibt:kasubjoined": "\u0F90",
+ "/tibt:kha": "\u0F41",
+ "/tibt:khasubjoined": "\u0F91",
+ "/tibt:kka": "\u0F6B",
+ "/tibt:kssa": "\u0F69",
+ "/tibt:kssasubjoined": "\u0FB9",
+ "/tibt:kurukha": "\u0FBE",
+ "/tibt:kurukhabzhimigcan": "\u0FBF",
+ "/tibt:la": "\u0F63",
+ "/tibt:lasubjoined": "\u0FB3",
+ "/tibt:lcetsacansign": "\u0F88",
+ "/tibt:lcetsacansubjoinedsign": "\u0F8D",
+ "/tibt:lcirtagssign": "\u0F86",
+ "/tibt:leadingmchanrtagsmark": "\u0FD9",
+ "/tibt:lhagrtagslogotypesign": "\u0F16",
+ "/tibt:lightbeatcantillationsign": "\u0FC1",
+ "/tibt:llvocalicvowelsign": "\u0F79",
+ "/tibt:lvocalicvowelsign": "\u0F78",
+ "/tibt:ma": "\u0F58",
+ "/tibt:martshessign": "\u0F3F",
+ "/tibt:masubjoined": "\u0FA8",
+ "/tibt:mchucansign": "\u0F89",
+ "/tibt:mchucansubjoinedsign": "\u0F8E",
+ "/tibt:mnyamyiggimgorgyanmark": "\u0FD1",
+ "/tibt:na": "\u0F53",
+ "/tibt:nasubjoined": "\u0FA3",
+ "/tibt:nga": "\u0F44",
+ "/tibt:ngasbzungnyizlamark": "\u0F35",
+ "/tibt:ngasbzungsgorrtagsmark": "\u0F37",
+ "/tibt:ngasubjoined": "\u0F94",
+ "/tibt:nine": "\u0F29",
+ "/tibt:nna": "\u0F4E",
+ "/tibt:nnasubjoined": "\u0F9E",
+ "/tibt:norbubzhikhyilsymbol": "\u0FCC",
+ "/tibt:norbugsumkhyilsymbol": "\u0FCB",
+ "/tibt:norbunyiskhyilsymbol": "\u0FCA",
+ "/tibt:norbusymbol": "\u0FC9",
+ "/tibt:nya": "\u0F49",
+ "/tibt:nyasubjoined": "\u0F99",
+ "/tibt:nyisshadmark": "\u0F0E",
+ "/tibt:nyistshegmark": "\u0FD2",
+ "/tibt:nyistshegshadmark": "\u0F10",
+ "/tibt:nyizlanaadasign": "\u0F82",
+ "/tibt:omsyllable": "\u0F00",
+ "/tibt:one": "\u0F21",
+ "/tibt:oovowelsign": "\u0F7D",
+ "/tibt:ovowelsign": "\u0F7C",
+ "/tibt:pa": "\u0F54",
+ "/tibt:padmagdansymbol": "\u0FC6",
+ "/tibt:palutamark": "\u0F85",
+ "/tibt:pasubjoined": "\u0FA4",
+ "/tibt:pha": "\u0F55",
+ "/tibt:phasubjoined": "\u0FA5",
+ "/tibt:phurpasymbol": "\u0FC8",
+ "/tibt:ra": "\u0F62",
+ "/tibt:rafixed": "\u0F6A",
+ "/tibt:rasubjoined": "\u0FB2",
+ "/tibt:rasubjoinedfixed": "\u0FBC",
+ "/tibt:rdeldkargcigsign": "\u0F1A",
+ "/tibt:rdeldkargnyissign": "\u0F1B",
+ "/tibt:rdeldkargsumsign": "\u0F1C",
+ "/tibt:rdeldkarrdelnagsign": "\u0F1F",
+ "/tibt:rdelnaggcigsign": "\u0F1D",
+ "/tibt:rdelnaggnyissign": "\u0F1E",
+ "/tibt:rdelnaggsumsign": "\u0FCF",
+ "/tibt:rdelnagrdeldkarsign": "\u0FCE",
+ "/tibt:rdorjergyagramsymbol": "\u0FC7",
+ "/tibt:rdorjesymbol": "\u0FC5",
+ "/tibt:reversediivowelsign": "\u0F81",
+ "/tibt:reversedivowelsign": "\u0F80",
+ "/tibt:rgyagramshadmark": "\u0F12",
+ "/tibt:rinchenspungsshadmark": "\u0F11",
+ "/tibt:rjessungarosign": "\u0F7E",
+ "/tibt:rnambcadsign": "\u0F7F",
+ "/tibt:rra": "\u0F6C",
+ "/tibt:rrvocalicvowelsign": "\u0F77",
+ "/tibt:rvocalicvowelsign": "\u0F76",
+ "/tibt:sa": "\u0F66",
+ "/tibt:sasubjoined": "\u0FB6",
+ "/tibt:sbrulshadmark": "\u0F08",
+ "/tibt:sbubchalcantillationsign": "\u0FC3",
+ "/tibt:seven": "\u0F27",
+ "/tibt:sha": "\u0F64",
+ "/tibt:shadmark": "\u0F0D",
+ "/tibt:shasubjoined": "\u0FB4",
+ "/tibt:six": "\u0F26",
+ "/tibt:snaldansign": "\u0F83",
+ "/tibt:ssa": "\u0F65",
+ "/tibt:ssasubjoined": "\u0FB5",
+ "/tibt:subjoinedAA": "\u0FB0",
+ "/tibt:svastileft": "\u0FD6",
+ "/tibt:svastileftdot": "\u0FD8",
+ "/tibt:svastiright": "\u0FD5",
+ "/tibt:svastirightdot": "\u0FD7",
+ "/tibt:ta": "\u0F4F",
+ "/tibt:tasubjoined": "\u0F9F",
+ "/tibt:tha": "\u0F50",
+ "/tibt:thasubjoined": "\u0FA0",
+ "/tibt:three": "\u0F23",
+ "/tibt:trailingmchanrtagsmark": "\u0FDA",
+ "/tibt:tsa": "\u0F59",
+ "/tibt:tsaphrumark": "\u0F39",
+ "/tibt:tsasubjoined": "\u0FA9",
+ "/tibt:tsha": "\u0F5A",
+ "/tibt:tshasubjoined": "\u0FAA",
+ "/tibt:tshegshadmark": "\u0F0F",
+ "/tibt:tta": "\u0F4A",
+ "/tibt:ttasubjoined": "\u0F9A",
+ "/tibt:ttha": "\u0F4B",
+ "/tibt:tthasubjoined": "\u0F9B",
+ "/tibt:two": "\u0F22",
+ "/tibt:uuvowelsign": "\u0F75",
+ "/tibt:uvowelsign": "\u0F74",
+ "/tibt:wa": "\u0F5D",
+ "/tibt:wasubjoined": "\u0FAD",
+ "/tibt:wasubjoinedfixed": "\u0FBA",
+ "/tibt:ya": "\u0F61",
+ "/tibt:yangrtagssign": "\u0F87",
+ "/tibt:yartshessign": "\u0F3E",
+ "/tibt:yasubjoined": "\u0FB1",
+ "/tibt:yasubjoinedfixed": "\u0FBB",
+ "/tibt:yigmgomdunmainitialmark": "\u0F04",
+ "/tibt:yigmgosgabmaclosingmark": "\u0F05",
+ "/tibt:yigmgotshegshadmamark": "\u0F07",
+ "/tibt:za": "\u0F5F",
+ "/tibt:zasubjoined": "\u0FAF",
+ "/tibt:zero": "\u0F20",
+ "/tibt:zha": "\u0F5E",
+ "/tibt:zhasubjoined": "\u0FAE",
+ "/ticirclekatakana": "\u32E0",
+ "/tickconvavediamondleftwhite": "\u27E2",
+ "/tickconvavediamondrightwhite": "\u27E3",
+ "/ticket": "\u1F3AB",
+ "/tickleftwhitesquare": "\u27E4",
+ "/tickrightwhitesquare": "\u27E5",
+ "/tifcha:hb": "\u0596",
+ "/tiger": "\u1F405",
+ "/tigerFace": "\u1F42F",
+ "/tihiragana": "\u3061",
+ "/tikatakana": "\u30C1",
+ "/tikatakanahalfwidth": "\uFF81",
+ "/tikeutacirclekorean": "\u3270",
+ "/tikeutaparenkorean": "\u3210",
+ "/tikeutcirclekorean": "\u3262",
+ "/tikeutkorean": "\u3137",
+ "/tikeutparenkorean": "\u3202",
+ "/tilde": "\u02DC",
+ "/tildebelowcmb": "\u0330",
+ "/tildecmb": "\u0303",
+ "/tildecomb": "\u0303",
+ "/tildediaeresisfunc": "\u2368",
+ "/tildedotaccent": "\u2E1E",
+ "/tildedotbelow": "\u2E1F",
+ "/tildedoublecmb": "\u0360",
+ "/tildeequalsreversed": "\u22CD",
+ "/tildelowmod": "\u02F7",
+ "/tildeoperator": "\u223C",
+ "/tildeoverlaycmb": "\u0334",
+ "/tildereversed": "\u223D",
+ "/tildering": "\u2E1B",
+ "/tildetpl": "\u224B",
+ "/tildeverticalcmb": "\u033E",
+ "/timerclock": "\u23F2",
+ "/timescircle": "\u2297",
+ "/tinsular": "\uA787",
+ "/tipehahebrew": "\u0596",
+ "/tipehalefthebrew": "\u0596",
+ "/tippigurmukhi": "\u0A70",
+ "/tiredFace": "\u1F62B",
+ "/tironiansignet": "\u204A",
+ "/tirtatumetespada": "\uA9DE",
+ "/titlocmbcyr": "\u0483",
+ "/titlocyrilliccmb": "\u0483",
+ "/tiwnarmenian": "\u057F",
+ "/tjekomicyr": "\u050F",
+ "/tlinebelow": "\u1E6F",
+ "/tmonospace": "\uFF54",
+ "/toarmenian": "\u0569",
+ "/tocirclekatakana": "\u32E3",
+ "/tocornerarrowNW": "\u21F1",
+ "/tocornerarrowSE": "\u21F2",
+ "/tohiragana": "\u3068",
+ "/toilet": "\u1F6BD",
+ "/tokatakana": "\u30C8",
+ "/tokatakanahalfwidth": "\uFF84",
+ "/tokyoTower": "\u1F5FC",
+ "/tolongvowel": "\uA9B5",
+ "/tomato": "\u1F345",
+ "/tonebarextrahighmod": "\u02E5",
+ "/tonebarextralowmod": "\u02E9",
+ "/tonebarhighmod": "\u02E6",
+ "/tonebarlowmod": "\u02E8",
+ "/tonebarmidmod": "\u02E7",
+ "/tonefive": "\u01BD",
+ "/tonehighbeginmod": "\u02F9",
+ "/tonehighendmod": "\u02FA",
+ "/tonelowbeginmod": "\u02FB",
+ "/tonelowendmod": "\u02FC",
+ "/tonesix": "\u0185",
+ "/tonetwo": "\u01A8",
+ "/tongue": "\u1F445",
+ "/tonos": "\u0384",
+ "/tonsquare": "\u3327",
+ "/topHat": "\u1F3A9",
+ "/topUpwardsArrowAbove": "\u1F51D",
+ "/topatakthai": "\u0E0F",
+ "/tortoiseshellbracketleft": "\u3014",
+ "/tortoiseshellbracketleftsmall": "\uFE5D",
+ "/tortoiseshellbracketleftvertical": "\uFE39",
+ "/tortoiseshellbracketright": "\u3015",
+ "/tortoiseshellbracketrightsmall": "\uFE5E",
+ "/tortoiseshellbracketrightvertical": "\uFE3A",
+ "/totalrunout": "\u2330",
+ "/totaothai": "\u0E15",
+ "/tpalatalhook": "\u01AB",
+ "/tparen": "\u24AF",
+ "/tparenthesized": "\u24AF",
+ "/trackball": "\u1F5B2",
+ "/tractor": "\u1F69C",
+ "/trademark": "\u2122",
+ "/trademarksans": "\uF8EA",
+ "/trademarkserif": "\uF6DB",
+ "/train": "\u1F686",
+ "/tram": "\u1F68A",
+ "/tramCar": "\u1F68B",
+ "/trapeziumwhite": "\u23E2",
+ "/tresillo": "\uA72B",
+ "/tretroflex": "\u0288",
+ "/tretroflexhook": "\u0288",
+ "/triagdn": "\u25BC",
+ "/triaglf": "\u25C4",
+ "/triagrt": "\u25BA",
+ "/triagup": "\u25B2",
+ "/triangleWithRoundedCorners": "\u1F6C6",
+ "/triangledotupwhite": "\u25EC",
+ "/triangledownblack": "\u25BC",
+ "/triangledownsmallblack": "\u25BE",
+ "/triangledownsmallwhite": "\u25BF",
+ "/triangledownwhite": "\u25BD",
+ "/trianglehalfupleftblack": "\u25ED",
+ "/trianglehalfuprightblack": "\u25EE",
+ "/triangleleftblack": "\u25C0",
+ "/triangleleftsmallblack": "\u25C2",
+ "/triangleleftsmallwhite": "\u25C3",
+ "/triangleleftwhite": "\u25C1",
+ "/triangleright": "\u22BF",
+ "/trianglerightblack": "\u25B6",
+ "/trianglerightsmallblack": "\u25B8",
+ "/trianglerightsmallwhite": "\u25B9",
+ "/trianglerightwhite": "\u25B7",
+ "/triangleupblack": "\u25B2",
+ "/triangleupsmallblack": "\u25B4",
+ "/triangleupsmallwhite": "\u25B5",
+ "/triangleupwhite": "\u25B3",
+ "/triangularFlagOnPost": "\u1F6A9",
+ "/triangularRuler": "\u1F4D0",
+ "/triangularbullet": "\u2023",
+ "/tricolon": "\u205D",
+ "/tricontainingtriwhiteanglesmall": "\u27C1",
+ "/tridentEmblem": "\u1F531",
+ "/trigramearth": "\u2637",
+ "/trigramfire": "\u2632",
+ "/trigramheaven": "\u2630",
+ "/trigramlake": "\u2631",
+ "/trigrammountain": "\u2636",
+ "/trigramthunder": "\u2633",
+ "/trigramwater": "\u2635",
+ "/trigramwind": "\u2634",
+ "/triplearrowleft": "\u21DA",
+ "/triplearrowright": "\u21DB",
+ "/tripledot": "\u061E",
+ "/trisememetrical": "\u23D7",
+ "/trns:baby": "\u1F6BC",
+ "/trolleybus": "\u1F68E",
+ "/trophy": "\u1F3C6",
+ "/tropicalDrink": "\u1F379",
+ "/tropicalFish": "\u1F420",
+ "/truckblack": "\u26DF",
+ "/true": "\u22A8",
+ "/trumpet": "\u1F3BA",
+ "/ts": "\u02A6",
+ "/tsadi": "\u05E6",
+ "/tsadi:hb": "\u05E6",
+ "/tsadidagesh": "\uFB46",
+ "/tsadidageshhebrew": "\uFB46",
+ "/tsadihebrew": "\u05E6",
+ "/tsadiwithdagesh:hb": "\uFB46",
+ "/tsecyr": "\u0446",
+ "/tsecyrillic": "\u0446",
+ "/tsere": "\u05B5",
+ "/tsere12": "\u05B5",
+ "/tsere1e": "\u05B5",
+ "/tsere2b": "\u05B5",
+ "/tsere:hb": "\u05B5",
+ "/tserehebrew": "\u05B5",
+ "/tserenarrowhebrew": "\u05B5",
+ "/tserequarterhebrew": "\u05B5",
+ "/tserewidehebrew": "\u05B5",
+ "/tshecyr": "\u045B",
+ "/tshecyrillic": "\u045B",
+ "/tsinnorit:hb": "\u05AE",
+ "/tstroke": "\u2C66",
+ "/tsuperior": "\uF6F3",
+ "/ttabengali": "\u099F",
+ "/ttadeva": "\u091F",
+ "/ttagujarati": "\u0A9F",
+ "/ttagurmukhi": "\u0A1F",
+ "/ttamahaprana": "\uA99C",
+ "/tteh": "\u0679",
+ "/tteh.fina": "\uFB67",
+ "/tteh.init": "\uFB68",
+ "/tteh.isol": "\uFB66",
+ "/tteh.medi": "\uFB69",
+ "/tteharabic": "\u0679",
+ "/tteheh": "\u067A",
+ "/tteheh.fina": "\uFB5F",
+ "/tteheh.init": "\uFB60",
+ "/tteheh.isol": "\uFB5E",
+ "/tteheh.medi": "\uFB61",
+ "/ttehfinalarabic": "\uFB67",
+ "/ttehinitialarabic": "\uFB68",
+ "/ttehmedialarabic": "\uFB69",
+ "/tthabengali": "\u09A0",
+ "/tthadeva": "\u0920",
+ "/tthagujarati": "\u0AA0",
+ "/tthagurmukhi": "\u0A20",
+ "/tturned": "\u0287",
+ "/tucirclekatakana": "\u32E1",
+ "/tugrik": "\u20AE",
+ "/tuhiragana": "\u3064",
+ "/tukatakana": "\u30C4",
+ "/tukatakanahalfwidth": "\uFF82",
+ "/tulip": "\u1F337",
+ "/tum": "\uA777",
+ "/turkishlira": "\u20BA",
+ "/turnedOkHandSign": "\u1F58F",
+ "/turnedcomma": "\u2E32",
+ "/turneddagger": "\u2E38",
+ "/turneddigitthree": "\u218B",
+ "/turneddigittwo": "\u218A",
+ "/turnedpiselehpada": "\uA9CD",
+ "/turnedsemicolon": "\u2E35",
+ "/turnedshogipieceblack": "\u26CA",
+ "/turnedshogipiecewhite": "\u26C9",
+ "/turnstiledblverticalbarright": "\u22AB",
+ "/turnstileleftrightdbl": "\u27DA",
+ "/turnstiletplverticalbarright": "\u22AA",
+ "/turtle": "\u1F422",
+ "/tusmallhiragana": "\u3063",
+ "/tusmallkatakana": "\u30C3",
+ "/tusmallkatakanahalfwidth": "\uFF6F",
+ "/twelve.roman": "\u216B",
+ "/twelve.romansmall": "\u217B",
+ "/twelvecircle": "\u246B",
+ "/twelvecircleblack": "\u24EC",
+ "/twelveparen": "\u247F",
+ "/twelveparenthesized": "\u247F",
+ "/twelveperiod": "\u2493",
+ "/twelveroman": "\u217B",
+ "/twenty-twopointtwosquare": "\u1F1A2",
+ "/twentycircle": "\u2473",
+ "/twentycircleblack": "\u24F4",
+ "/twentycirclesquare": "\u3249",
+ "/twentyeightcircle": "\u3258",
+ "/twentyfivecircle": "\u3255",
+ "/twentyfourcircle": "\u3254",
+ "/twentyhangzhou": "\u5344",
+ "/twentyninecircle": "\u3259",
+ "/twentyonecircle": "\u3251",
+ "/twentyparen": "\u2487",
+ "/twentyparenthesized": "\u2487",
+ "/twentyperiod": "\u249B",
+ "/twentysevencircle": "\u3257",
+ "/twentysixcircle": "\u3256",
+ "/twentythreecircle": "\u3253",
+ "/twentytwocircle": "\u3252",
+ "/twistedRightwardsArrows": "\u1F500",
+ "/two": "\u0032",
+ "/two.inferior": "\u2082",
+ "/two.roman": "\u2161",
+ "/two.romansmall": "\u2171",
+ "/twoButtonMouse": "\u1F5B0",
+ "/twoHearts": "\u1F495",
+ "/twoMenHoldingHands": "\u1F46C",
+ "/twoSpeechBubbles": "\u1F5EA",
+ "/twoWomenHoldingHands": "\u1F46D",
+ "/twoarabic": "\u0662",
+ "/twoasterisksalignedvertically": "\u2051",
+ "/twobengali": "\u09E8",
+ "/twocircle": "\u2461",
+ "/twocircledbl": "\u24F6",
+ "/twocircleinversesansserif": "\u278B",
+ "/twocomma": "\u1F103",
+ "/twodeva": "\u0968",
+ "/twodotenleader": "\u2025",
+ "/twodotleader": "\u2025",
+ "/twodotleadervertical": "\uFE30",
+ "/twodotpunctuation": "\u205A",
+ "/twodotsoveronedot": "\u2E2A",
+ "/twofar": "\u06F2",
+ "/twofifths": "\u2156",
+ "/twogujarati": "\u0AE8",
+ "/twogurmukhi": "\u0A68",
+ "/twohackarabic": "\u0662",
+ "/twohangzhou": "\u3022",
+ "/twoideographiccircled": "\u3281",
+ "/twoideographicparen": "\u3221",
+ "/twoinferior": "\u2082",
+ "/twoksquare": "\u1F19D",
+ "/twomonospace": "\uFF12",
+ "/twonumeratorbengali": "\u09F5",
+ "/twooldstyle": "\uF732",
+ "/twoparen": "\u2475",
+ "/twoparenthesized": "\u2475",
+ "/twoperiod": "\u2489",
+ "/twopersian": "\u06F2",
+ "/tworoman": "\u2171",
+ "/twoshortsjoinedmetrical": "\u23D6",
+ "/twoshortsoverlongmetrical": "\u23D5",
+ "/twostroke": "\u01BB",
+ "/twosuperior": "\u00B2",
+ "/twothai": "\u0E52",
+ "/twothirds": "\u2154",
+ "/twowayleftwaytrafficblack": "\u26D6",
+ "/twowayleftwaytrafficwhite": "\u26D7",
+ "/tz": "\uA729",
+ "/u": "\u0075",
+ "/u.fina": "\uFBD8",
+ "/u.isol": "\uFBD7",
+ "/uacute": "\u00FA",
+ "/uacutedblcyr": "\u04F3",
+ "/ubar": "\u0289",
+ "/ubengali": "\u0989",
+ "/ubopomofo": "\u3128",
+ "/ubracketleft": "\u2E26",
+ "/ubracketright": "\u2E27",
+ "/ubreve": "\u016D",
+ "/ucaron": "\u01D4",
+ "/ucircle": "\u24E4",
+ "/ucirclekatakana": "\u32D2",
+ "/ucircumflex": "\u00FB",
+ "/ucircumflexbelow": "\u1E77",
+ "/ucyr": "\u0443",
+ "/ucyrillic": "\u0443",
+ "/udattadeva": "\u0951",
+ "/udblacute": "\u0171",
+ "/udblgrave": "\u0215",
+ "/udeva": "\u0909",
+ "/udieresis": "\u00FC",
+ "/udieresisacute": "\u01D8",
+ "/udieresisbelow": "\u1E73",
+ "/udieresiscaron": "\u01DA",
+ "/udieresiscyr": "\u04F1",
+ "/udieresiscyrillic": "\u04F1",
+ "/udieresisgrave": "\u01DC",
+ "/udieresismacron": "\u01D6",
+ "/udotbelow": "\u1EE5",
+ "/ugrave": "\u00F9",
+ "/ugravedbl": "\u0215",
+ "/ugujarati": "\u0A89",
+ "/ugurmukhi": "\u0A09",
+ "/uhamza": "\u0677",
+ "/uhamza.isol": "\uFBDD",
+ "/uhdsquare": "\u1F1AB",
+ "/uhiragana": "\u3046",
+ "/uhoi": "\u1EE7",
+ "/uhookabove": "\u1EE7",
+ "/uhorn": "\u01B0",
+ "/uhornacute": "\u1EE9",
+ "/uhorndotbelow": "\u1EF1",
+ "/uhorngrave": "\u1EEB",
+ "/uhornhoi": "\u1EED",
+ "/uhornhookabove": "\u1EED",
+ "/uhorntilde": "\u1EEF",
+ "/uhungarumlaut": "\u0171",
+ "/uhungarumlautcyrillic": "\u04F3",
+ "/uighurkazakhkirghizalefmaksura.init": "\uFBE8",
+ "/uighurkazakhkirghizalefmaksura.medi": "\uFBE9",
+ "/uighurkirghizyeh.init_hamzaabove.medi_alefmaksura.fina": "\uFBF9",
+ "/uighurkirghizyeh.init_hamzaabove.medi_alefmaksura.medi": "\uFBFB",
+ "/uighurkirghizyeh.medi_hamzaabove.medi_alefmaksura.fina": "\uFBFA",
+ "/uinvertedbreve": "\u0217",
+ "/ukatakana": "\u30A6",
+ "/ukatakanahalfwidth": "\uFF73",
+ "/ukcyr": "\u0479",
+ "/ukcyrillic": "\u0479",
+ "/ukorean": "\u315C",
+ "/um": "\uA778",
+ "/umacron": "\u016B",
+ "/umacroncyr": "\u04EF",
+ "/umacroncyrillic": "\u04EF",
+ "/umacrondieresis": "\u1E7B",
+ "/umatragurmukhi": "\u0A41",
+ "/umbrella": "\u2602",
+ "/umbrellaonground": "\u26F1",
+ "/umbrellaraindrops": "\u2614",
+ "/umonospace": "\uFF55",
+ "/unamusedFace": "\u1F612",
+ "/unaspiratedmod": "\u02ED",
+ "/underscore": "\u005F",
+ "/underscorecenterline": "\uFE4E",
+ "/underscoredashed": "\uFE4D",
+ "/underscoredbl": "\u2017",
+ "/underscoremonospace": "\uFF3F",
+ "/underscorevertical": "\uFE33",
+ "/underscorewavy": "\uFE4F",
+ "/underscorewavyvertical": "\uFE34",
+ "/undertie": "\u203F",
+ "/undo": "\u238C",
+ "/union": "\u222A",
+ "/unionarray": "\u22C3",
+ "/uniondbl": "\u22D3",
+ "/universal": "\u2200",
+ "/unmarriedpartnership": "\u26AF",
+ "/uogonek": "\u0173",
+ "/uonsquare": "\u3306",
+ "/upPointingAirplane": "\u1F6E7",
+ "/upPointingMilitaryAirplane": "\u1F6E6",
+ "/upPointingSmallAirplane": "\u1F6E8",
+ "/uparen": "\u24B0",
+ "/uparenthesized": "\u24B0",
+ "/uparrowleftofdownarrow": "\u21C5",
+ "/upblock": "\u2580",
+ "/updblhorzsng": "\u2568",
+ "/updblleftsng": "\u255C",
+ "/updblrightsng": "\u2559",
+ "/upheavydnhorzlight": "\u2540",
+ "/upheavyhorzlight": "\u2538",
+ "/upheavyleftdnlight": "\u2526",
+ "/upheavyleftlight": "\u251A",
+ "/upheavyrightdnlight": "\u251E",
+ "/upheavyrightlight": "\u2516",
+ "/uplightdnhorzheavy": "\u2548",
+ "/uplighthorzheavy": "\u2537",
+ "/uplightleftdnheavy": "\u252A",
+ "/uplightleftheavy": "\u2519",
+ "/uplightrightdnheavy": "\u2522",
+ "/uplightrightheavy": "\u2515",
+ "/upperHalfBlock": "\u2580",
+ "/upperOneEighthBlock": "\u2594",
+ "/upperRightShadowedWhiteCircle": "\u1F53F",
+ "/upperdothebrew": "\u05C4",
+ "/upperhalfcircle": "\u25E0",
+ "/upperhalfcircleinversewhite": "\u25DA",
+ "/upperquadrantcirculararcleft": "\u25DC",
+ "/upperquadrantcirculararcright": "\u25DD",
+ "/uppertriangleleft": "\u25F8",
+ "/uppertriangleleftblack": "\u25E4",
+ "/uppertriangleright": "\u25F9",
+ "/uppertrianglerightblack": "\u25E5",
+ "/upsideDownFace": "\u1F643",
+ "/upsilon": "\u03C5",
+ "/upsilonacute": "\u1F7B",
+ "/upsilonasper": "\u1F51",
+ "/upsilonasperacute": "\u1F55",
+ "/upsilonaspergrave": "\u1F53",
+ "/upsilonaspertilde": "\u1F57",
+ "/upsilonbreve": "\u1FE0",
+ "/upsilondieresis": "\u03CB",
+ "/upsilondieresisacute": "\u1FE3",
+ "/upsilondieresisgrave": "\u1FE2",
+ "/upsilondieresistilde": "\u1FE7",
+ "/upsilondieresistonos": "\u03B0",
+ "/upsilongrave": "\u1F7A",
+ "/upsilonlatin": "\u028A",
+ "/upsilonlenis": "\u1F50",
+ "/upsilonlenisacute": "\u1F54",
+ "/upsilonlenisgrave": "\u1F52",
+ "/upsilonlenistilde": "\u1F56",
+ "/upsilontilde": "\u1FE6",
+ "/upsilontonos": "\u03CD",
+ "/upsilonwithmacron": "\u1FE1",
+ "/upsnghorzdbl": "\u2567",
+ "/upsngleftdbl": "\u255B",
+ "/upsngrightdbl": "\u2558",
+ "/uptackbelowcmb": "\u031D",
+ "/uptackmod": "\u02D4",
+ "/upwithexclamationmarksquare": "\u1F199",
+ "/uragurmukhi": "\u0A73",
+ "/uranus": "\u2645",
+ "/uring": "\u016F",
+ "/ushortcyr": "\u045E",
+ "/ushortcyrillic": "\u045E",
+ "/usmallhiragana": "\u3045",
+ "/usmallkatakana": "\u30A5",
+ "/usmallkatakanahalfwidth": "\uFF69",
+ "/usmod": "\uA770",
+ "/ustraightcyr": "\u04AF",
+ "/ustraightcyrillic": "\u04AF",
+ "/ustraightstrokecyr": "\u04B1",
+ "/ustraightstrokecyrillic": "\u04B1",
+ "/utilde": "\u0169",
+ "/utildeacute": "\u1E79",
+ "/utildebelow": "\u1E75",
+ "/uubengali": "\u098A",
+ "/uudeva": "\u090A",
+ "/uugujarati": "\u0A8A",
+ "/uugurmukhi": "\u0A0A",
+ "/uumatragurmukhi": "\u0A42",
+ "/uuvowelsignbengali": "\u09C2",
+ "/uuvowelsigndeva": "\u0942",
+ "/uuvowelsigngujarati": "\u0AC2",
+ "/uvowelsignbengali": "\u09C1",
+ "/uvowelsigndeva": "\u0941",
+ "/uvowelsigngujarati": "\u0AC1",
+ "/v": "\u0076",
+ "/vadeva": "\u0935",
+ "/vagujarati": "\u0AB5",
+ "/vagurmukhi": "\u0A35",
+ "/vakatakana": "\u30F7",
+ "/vanedownfunc": "\u2356",
+ "/vaneleftfunc": "\u2345",
+ "/vanerightfunc": "\u2346",
+ "/vaneupfunc": "\u234F",
+ "/varikajudeospanish:hb": "\uFB1E",
+ "/vav": "\u05D5",
+ "/vav:hb": "\u05D5",
+ "/vav_vav:hb": "\u05F0",
+ "/vav_yod:hb": "\u05F1",
+ "/vavdagesh": "\uFB35",
+ "/vavdagesh65": "\uFB35",
+ "/vavdageshhebrew": "\uFB35",
+ "/vavhebrew": "\u05D5",
+ "/vavholam": "\uFB4B",
+ "/vavholamhebrew": "\uFB4B",
+ "/vavvavhebrew": "\u05F0",
+ "/vavwithdagesh:hb": "\uFB35",
+ "/vavwithholam:hb": "\uFB4B",
+ "/vavyodhebrew": "\u05F1",
+ "/vcircle": "\u24E5",
+ "/vcurl": "\u2C74",
+ "/vdiagonalstroke": "\uA75F",
+ "/vdotbelow": "\u1E7F",
+ "/ve.fina": "\uFBDF",
+ "/ve.isol": "\uFBDE",
+ "/ve:abovetonecandra": "\u1CF4",
+ "/ve:anusvaraantargomukhasign": "\u1CE9",
+ "/ve:anusvarabahirgomukhasign": "\u1CEA",
+ "/ve:anusvarasignlong": "\u1CEF",
+ "/ve:anusvaraubhayatomukhasign": "\u1CF1",
+ "/ve:anusvaravamagomukhasign": "\u1CEB",
+ "/ve:anusvaravamagomukhawithtailsign": "\u1CEC",
+ "/ve:ardhavisargasign": "\u1CF2",
+ "/ve:atharvaindependentsvaritatone": "\u1CE1",
+ "/ve:atikramasign": "\u1CF7",
+ "/ve:belowtonecandra": "\u1CD8",
+ "/ve:dotbelowtone": "\u1CDD",
+ "/ve:hexiformanusvarasignlong": "\u1CEE",
+ "/ve:jihvamuliyasign": "\u1CF5",
+ "/ve:karshanatone": "\u1CD0",
+ "/ve:kathakaanudattatone": "\u1CDC",
+ "/ve:nihshvasasign": "\u1CD3",
+ "/ve:prenkhatone": "\u1CD2",
+ "/ve:rigkashmiriindependentsvaritatone": "\u1CE0",
+ "/ve:ringabovetone": "\u1CF8",
+ "/ve:ringabovetonedbl": "\u1CF9",
+ "/ve:rotatedardhavisargasign": "\u1CF3",
+ "/ve:rthanganusvarasignlong": "\u1CF0",
+ "/ve:sharatone": "\u1CD1",
+ "/ve:svaritatonedbl": "\u1CDA",
+ "/ve:svaritatonetpl": "\u1CDB",
+ "/ve:threedotsbelowtone": "\u1CDF",
+ "/ve:tiryaksign": "\u1CED",
+ "/ve:twodotsbelowtone": "\u1CDE",
+ "/ve:upadhmaniyasign": "\u1CF6",
+ "/ve:visargaanudattasign": "\u1CE5",
+ "/ve:visargaanudattasignreversed": "\u1CE6",
+ "/ve:visargaanudattawithtailsign": "\u1CE8",
+ "/ve:visargasvaritasign": "\u1CE2",
+ "/ve:visargaudattasign": "\u1CE3",
+ "/ve:visargaudattasignreversed": "\u1CE4",
+ "/ve:visargaudattawithtailsign": "\u1CE7",
+ "/ve:yajuraggravatedindependentsvaritatone": "\u1CD5",
+ "/ve:yajurindependentsvaritatone": "\u1CD6",
+ "/ve:yajurkathakaindependentsvaritaschroedertone": "\u1CD9",
+ "/ve:yajurkathakaindependentsvaritatone": "\u1CD7",
+ "/ve:yajurmidlinesvaritasign": "\u1CD4",
+ "/vecyr": "\u0432",
+ "/vecyrillic": "\u0432",
+ "/veh": "\u06A4",
+ "/veh.fina": "\uFB6B",
+ "/veh.init": "\uFB6C",
+ "/veh.isol": "\uFB6A",
+ "/veh.medi": "\uFB6D",
+ "/veharabic": "\u06A4",
+ "/vehfinalarabic": "\uFB6B",
+ "/vehinitialarabic": "\uFB6C",
+ "/vehmedialarabic": "\uFB6D",
+ "/vekatakana": "\u30F9",
+ "/vend": "\uA769",
+ "/venus": "\u2640",
+ "/versicle": "\u2123",
+ "/vert:bracketwhiteleft": "\uFE17",
+ "/vert:brakcetwhiteright": "\uFE18",
+ "/vert:colon": "\uFE13",
+ "/vert:comma": "\uFE10",
+ "/vert:ellipsishor": "\uFE19",
+ "/vert:exclam": "\uFE15",
+ "/vert:ideographiccomma": "\uFE11",
+ "/vert:ideographicfullstop": "\uFE12",
+ "/vert:question": "\uFE16",
+ "/vert:semicolon": "\uFE14",
+ "/vertdblhorzsng": "\u256B",
+ "/vertdblleftsng": "\u2562",
+ "/vertdblrightsng": "\u255F",
+ "/vertheavyhorzlight": "\u2542",
+ "/vertheavyleftlight": "\u2528",
+ "/vertheavyrightlight": "\u2520",
+ "/verticalTrafficLight": "\u1F6A6",
+ "/verticalbar": "\u007C",
+ "/verticalbardbl": "\u2016",
+ "/verticalbarhorizontalstroke": "\u27CA",
+ "/verticalbarwhitearrowonpedestalup": "\u21ED",
+ "/verticalfourdots": "\u205E",
+ "/verticalideographiciterationmark": "\u303B",
+ "/verticalkanarepeatmark": "\u3031",
+ "/verticalkanarepeatmarklowerhalf": "\u3035",
+ "/verticalkanarepeatmarkupperhalf": "\u3033",
+ "/verticalkanarepeatwithvoicedsoundmark": "\u3032",
+ "/verticalkanarepeatwithvoicedsoundmarkupperhalf": "\u3034",
+ "/verticallineabovecmb": "\u030D",
+ "/verticallinebelowcmb": "\u0329",
+ "/verticallinelowmod": "\u02CC",
+ "/verticallinemod": "\u02C8",
+ "/verticalmalestroke": "\u26A8",
+ "/verticalsdbltrokearrowleft": "\u21FA",
+ "/verticalsdbltrokearrowleftright": "\u21FC",
+ "/verticalsdbltrokearrowright": "\u21FB",
+ "/verticalstrokearrowleft": "\u21F7",
+ "/verticalstrokearrowleftright": "\u21F9",
+ "/verticalstrokearrowright": "\u21F8",
+ "/vertlighthorzheavy": "\u253F",
+ "/vertlightleftheavy": "\u2525",
+ "/vertlightrightheavy": "\u251D",
+ "/vertsnghorzdbl": "\u256A",
+ "/vertsngleftdbl": "\u2561",
+ "/vertsngrightdbl": "\u255E",
+ "/verymuchgreater": "\u22D9",
+ "/verymuchless": "\u22D8",
+ "/vesta": "\u26B6",
+ "/vewarmenian": "\u057E",
+ "/vhook": "\u028B",
+ "/vibrationMode": "\u1F4F3",
+ "/videoCamera": "\u1F4F9",
+ "/videoGame": "\u1F3AE",
+ "/videocassette": "\u1F4FC",
+ "/viewdatasquare": "\u2317",
+ "/vikatakana": "\u30F8",
+ "/violin": "\u1F3BB",
+ "/viramabengali": "\u09CD",
+ "/viramadeva": "\u094D",
+ "/viramagujarati": "\u0ACD",
+ "/virgo": "\u264D",
+ "/visargabengali": "\u0983",
+ "/visargadeva": "\u0903",
+ "/visargagujarati": "\u0A83",
+ "/visigothicz": "\uA763",
+ "/vmonospace": "\uFF56",
+ "/voarmenian": "\u0578",
+ "/vodsquare": "\u1F1AC",
+ "/voicediterationhiragana": "\u309E",
+ "/voicediterationkatakana": "\u30FE",
+ "/voicedmarkkana": "\u309B",
+ "/voicedmarkkanahalfwidth": "\uFF9E",
+ "/voicingmod": "\u02EC",
+ "/vokatakana": "\u30FA",
+ "/volapukae": "\uA79B",
+ "/volapukoe": "\uA79D",
+ "/volapukue": "\uA79F",
+ "/volcano": "\u1F30B",
+ "/volleyball": "\u1F3D0",
+ "/vovermfullwidth": "\u33DE",
+ "/vowelVabove": "\u065A",
+ "/voweldotbelow": "\u065C",
+ "/vowelinvertedVabove": "\u065B",
+ "/vparen": "\u24B1",
+ "/vparenthesized": "\u24B1",
+ "/vrighthook": "\u2C71",
+ "/vssquare": "\u1F19A",
+ "/vtilde": "\u1E7D",
+ "/vturned": "\u028C",
+ "/vuhiragana": "\u3094",
+ "/vukatakana": "\u30F4",
+ "/vwelsh": "\u1EFD",
+ "/vy": "\uA761",
+ "/w": "\u0077",
+ "/wacirclekatakana": "\u32FB",
+ "/wacute": "\u1E83",
+ "/waekorean": "\u3159",
+ "/wahiragana": "\u308F",
+ "/wakatakana": "\u30EF",
+ "/wakatakanahalfwidth": "\uFF9C",
+ "/wakorean": "\u3158",
+ "/waningCrescentMoon": "\u1F318",
+ "/waningGibbousMoon": "\u1F316",
+ "/warning": "\u26A0",
+ "/wasmallhiragana": "\u308E",
+ "/wasmallkatakana": "\u30EE",
+ "/wastebasket": "\u1F5D1",
+ "/watch": "\u231A",
+ "/waterBuffalo": "\u1F403",
+ "/waterCloset": "\u1F6BE",
+ "/waterWave": "\u1F30A",
+ "/waterideographiccircled": "\u328C",
+ "/waterideographicparen": "\u322C",
+ "/watermelon": "\u1F349",
+ "/wattosquare": "\u3357",
+ "/wavedash": "\u301C",
+ "/wavingBlackFlag": "\u1F3F4",
+ "/wavingHandSign": "\u1F44B",
+ "/wavingWhiteFlag": "\u1F3F3",
+ "/wavydash": "\u3030",
+ "/wavyhamzabelow": "\u065F",
+ "/wavyline": "\u2307",
+ "/wavyunderscorevertical": "\uFE34",
+ "/waw": "\u0648",
+ "/waw.fina": "\uFEEE",
+ "/waw.isol": "\uFEED",
+ "/wawDigitThreeAbove": "\u0779",
+ "/wawDigitTwoAbove": "\u0778",
+ "/wawarabic": "\u0648",
+ "/wawdotabove": "\u06CF",
+ "/wawfinalarabic": "\uFEEE",
+ "/wawhamza": "\u0624",
+ "/wawhamza.fina": "\uFE86",
+ "/wawhamza.isol": "\uFE85",
+ "/wawhamzaabovearabic": "\u0624",
+ "/wawhamzaabovefinalarabic": "\uFE86",
+ "/wawhighhamza": "\u0676",
+ "/wawring": "\u06C4",
+ "/wawsmall": "\u06E5",
+ "/wawtwodotsabove": "\u06CA",
+ "/waxingCrescentMoon": "\u1F312",
+ "/waxingGibbousMoon": "\u1F314",
+ "/wbfullwidth": "\u33DD",
+ "/wbsquare": "\u33DD",
+ "/wcircle": "\u24E6",
+ "/wcircumflex": "\u0175",
+ "/wcsquare": "\u1F14F",
+ "/wcsquareblack": "\u1F18F",
+ "/wdieresis": "\u1E85",
+ "/wdot": "\u1E87",
+ "/wdotaccent": "\u1E87",
+ "/wdotbelow": "\u1E89",
+ "/wearyCatFace": "\u1F640",
+ "/wearyFace": "\u1F629",
+ "/wecirclekatakana": "\u32FD",
+ "/wecyr": "\u051D",
+ "/wedding": "\u1F492",
+ "/wehiragana": "\u3091",
+ "/weierstrass": "\u2118",
+ "/weightLifter": "\u1F3CB",
+ "/wekatakana": "\u30F1",
+ "/wekorean": "\u315E",
+ "/weokorean": "\u315D",
+ "/westsyriaccross": "\u2670",
+ "/wgrave": "\u1E81",
+ "/whale": "\u1F40B",
+ "/wheelchair": "\u267F",
+ "/wheelofdharma": "\u2638",
+ "/whiteDownPointingBackhandIndex": "\u1F447",
+ "/whiteDownPointingLeftHandIndex": "\u1F597",
+ "/whiteFlower": "\u1F4AE",
+ "/whiteHardShellFloppyDisk": "\u1F5AB",
+ "/whiteLatinCross": "\u1F546",
+ "/whiteLeftPointingBackhandIndex": "\u1F448",
+ "/whitePennant": "\u1F3F1",
+ "/whiteRightPointingBackhandIndex": "\u1F449",
+ "/whiteSquareButton": "\u1F533",
+ "/whiteSun": "\u1F323",
+ "/whiteSunBehindCloud": "\u1F325",
+ "/whiteSunBehindCloudRain": "\u1F326",
+ "/whiteSunSmallCloud": "\u1F324",
+ "/whiteTouchtoneTelephone": "\u1F57E",
+ "/whiteUpPointingBackhandIndex": "\u1F446",
+ "/whitearrowdown": "\u21E9",
+ "/whitearrowfromwallright": "\u21F0",
+ "/whitearrowleft": "\u21E6",
+ "/whitearrowonpedestalup": "\u21EB",
+ "/whitearrowright": "\u21E8",
+ "/whitearrowup": "\u21E7",
+ "/whitearrowupdown": "\u21F3",
+ "/whitearrowupfrombar": "\u21EA",
+ "/whitebullet": "\u25E6",
+ "/whitecircle": "\u25CB",
+ "/whitecircleinverse": "\u25D9",
+ "/whitecornerbracketleft": "\u300E",
+ "/whitecornerbracketleftvertical": "\uFE43",
+ "/whitecornerbracketright": "\u300F",
+ "/whitecornerbracketrightvertical": "\uFE44",
+ "/whitedblarrowonpedestalup": "\u21EF",
+ "/whitedblarrowup": "\u21EE",
+ "/whitediamond": "\u25C7",
+ "/whitediamondcontainingblacksmalldiamond": "\u25C8",
+ "/whitedownpointingsmalltriangle": "\u25BF",
+ "/whitedownpointingtriangle": "\u25BD",
+ "/whiteleftpointingsmalltriangle": "\u25C3",
+ "/whiteleftpointingtriangle": "\u25C1",
+ "/whitelenticularbracketleft": "\u3016",
+ "/whitelenticularbracketright": "\u3017",
+ "/whiterightpointingsmalltriangle": "\u25B9",
+ "/whiterightpointingtriangle": "\u25B7",
+ "/whitesesamedot": "\uFE46",
+ "/whitesmallsquare": "\u25AB",
+ "/whitesmilingface": "\u263A",
+ "/whitesquare": "\u25A1",
+ "/whitesquarebracketleft": "\u301A",
+ "/whitesquarebracketright": "\u301B",
+ "/whitestar": "\u2606",
+ "/whitetelephone": "\u260F",
+ "/whitetortoiseshellbracketleft": "\u3018",
+ "/whitetortoiseshellbracketright": "\u3019",
+ "/whiteuppointingsmalltriangle": "\u25B5",
+ "/whiteuppointingtriangle": "\u25B3",
+ "/whook": "\u2C73",
+ "/wicirclekatakana": "\u32FC",
+ "/wigglylinevertical": "\u2E3E",
+ "/wignyan": "\uA983",
+ "/wihiragana": "\u3090",
+ "/wikatakana": "\u30F0",
+ "/wikorean": "\u315F",
+ "/windBlowingFace": "\u1F32C",
+ "/windChime": "\u1F390",
+ "/windupada": "\uA9C6",
+ "/wineGlass": "\u1F377",
+ "/winkingFace": "\u1F609",
+ "/wiredKeyboard": "\u1F5AE",
+ "/wmonospace": "\uFF57",
+ "/wocirclekatakana": "\u32FE",
+ "/wohiragana": "\u3092",
+ "/wokatakana": "\u30F2",
+ "/wokatakanahalfwidth": "\uFF66",
+ "/wolfFace": "\u1F43A",
+ "/woman": "\u1F469",
+ "/womanBunnyEars": "\u1F46F",
+ "/womansBoots": "\u1F462",
+ "/womansClothes": "\u1F45A",
+ "/womansHat": "\u1F452",
+ "/womansSandal": "\u1F461",
+ "/womens": "\u1F6BA",
+ "/won": "\u20A9",
+ "/wonmonospace": "\uFFE6",
+ "/woodideographiccircled": "\u328D",
+ "/woodideographicparen": "\u322D",
+ "/wordjoiner": "\u2060",
+ "/wordseparatormiddledot": "\u2E31",
+ "/worldMap": "\u1F5FA",
+ "/worriedFace": "\u1F61F",
+ "/wowaenthai": "\u0E27",
+ "/wparen": "\u24B2",
+ "/wparenthesized": "\u24B2",
+ "/wrappedPresent": "\u1F381",
+ "/wreathproduct": "\u2240",
+ "/wrench": "\u1F527",
+ "/wring": "\u1E98",
+ "/wsuperior": "\u02B7",
+ "/wsupmod": "\u02B7",
+ "/wturned": "\u028D",
+ "/wulumelikvowel": "\uA9B7",
+ "/wuluvowel": "\uA9B6",
+ "/wynn": "\u01BF",
+ "/x": "\u0078",
+ "/x.inferior": "\u2093",
+ "/xabovecmb": "\u033D",
+ "/xatailcyr": "\u04B3",
+ "/xbopomofo": "\u3112",
+ "/xcircle": "\u24E7",
+ "/xdieresis": "\u1E8D",
+ "/xdot": "\u1E8B",
+ "/xdotaccent": "\u1E8B",
+ "/xeharmenian": "\u056D",
+ "/xi": "\u03BE",
+ "/xmonospace": "\uFF58",
+ "/xor": "\u22BB",
+ "/xparen": "\u24B3",
+ "/xparenthesized": "\u24B3",
+ "/xsuperior": "\u02E3",
+ "/xsupmod": "\u02E3",
+ "/y": "\u0079",
+ "/yaadosquare": "\u334E",
+ "/yaarusquare": "\u334F",
+ "/yabengali": "\u09AF",
+ "/yacirclekatakana": "\u32F3",
+ "/yacute": "\u00FD",
+ "/yacyr": "\u044F",
+ "/yadeva": "\u092F",
+ "/yaecyr": "\u0519",
+ "/yaekorean": "\u3152",
+ "/yagujarati": "\u0AAF",
+ "/yagurmukhi": "\u0A2F",
+ "/yahiragana": "\u3084",
+ "/yakatakana": "\u30E4",
+ "/yakatakanahalfwidth": "\uFF94",
+ "/yakorean": "\u3151",
+ "/yamakkanthai": "\u0E4E",
+ "/yangtonemod": "\u02EB",
+ "/yasmallhiragana": "\u3083",
+ "/yasmallkatakana": "\u30E3",
+ "/yasmallkatakanahalfwidth": "\uFF6C",
+ "/yatcyr": "\u0463",
+ "/yatcyrillic": "\u0463",
+ "/ycircle": "\u24E8",
+ "/ycircumflex": "\u0177",
+ "/ydieresis": "\u00FF",
+ "/ydot": "\u1E8F",
+ "/ydotaccent": "\u1E8F",
+ "/ydotbelow": "\u1EF5",
+ "/yeh": "\u064A",
+ "/yeh.fina": "\uFEF2",
+ "/yeh.init": "\uFEF3",
+ "/yeh.init_alefmaksura.fina": "\uFC59",
+ "/yeh.init_hah.fina": "\uFC56",
+ "/yeh.init_hah.medi": "\uFCDB",
+ "/yeh.init_hamzaabove.medi_ae.fina": "\uFBEC",
+ "/yeh.init_hamzaabove.medi_alef.fina": "\uFBEA",
+ "/yeh.init_hamzaabove.medi_alefmaksura.fina": "\uFC03",
+ "/yeh.init_hamzaabove.medi_e.fina": "\uFBF6",
+ "/yeh.init_hamzaabove.medi_e.medi": "\uFBF8",
+ "/yeh.init_hamzaabove.medi_hah.fina": "\uFC01",
+ "/yeh.init_hamzaabove.medi_hah.medi": "\uFC98",
+ "/yeh.init_hamzaabove.medi_heh.medi": "\uFC9B",
+ "/yeh.init_hamzaabove.medi_jeem.fina": "\uFC00",
+ "/yeh.init_hamzaabove.medi_jeem.medi": "\uFC97",
+ "/yeh.init_hamzaabove.medi_khah.medi": "\uFC99",
+ "/yeh.init_hamzaabove.medi_meem.fina": "\uFC02",
+ "/yeh.init_hamzaabove.medi_meem.medi": "\uFC9A",
+ "/yeh.init_hamzaabove.medi_oe.fina": "\uFBF2",
+ "/yeh.init_hamzaabove.medi_u.fina": "\uFBF0",
+ "/yeh.init_hamzaabove.medi_waw.fina": "\uFBEE",
+ "/yeh.init_hamzaabove.medi_yeh.fina": "\uFC04",
+ "/yeh.init_hamzaabove.medi_yu.fina": "\uFBF4",
+ "/yeh.init_heh.medi": "\uFCDE",
+ "/yeh.init_jeem.fina": "\uFC55",
+ "/yeh.init_jeem.medi": "\uFCDA",
+ "/yeh.init_khah.fina": "\uFC57",
+ "/yeh.init_khah.medi": "\uFCDC",
+ "/yeh.init_meem.fina": "\uFC58",
+ "/yeh.init_meem.medi": "\uFCDD",
+ "/yeh.init_meem.medi_meem.medi": "\uFD9D",
+ "/yeh.init_yeh.fina": "\uFC5A",
+ "/yeh.isol": "\uFEF1",
+ "/yeh.medi": "\uFEF4",
+ "/yeh.medi_alefmaksura.fina": "\uFC95",
+ "/yeh.medi_hah.medi_yeh.fina": "\uFDAE",
+ "/yeh.medi_hamzaabove.medi_ae.fina": "\uFBED",
+ "/yeh.medi_hamzaabove.medi_alef.fina": "\uFBEB",
+ "/yeh.medi_hamzaabove.medi_alefmaksura.fina": "\uFC68",
+ "/yeh.medi_hamzaabove.medi_e.fina": "\uFBF7",
+ "/yeh.medi_hamzaabove.medi_heh.medi": "\uFCE0",
+ "/yeh.medi_hamzaabove.medi_meem.fina": "\uFC66",
+ "/yeh.medi_hamzaabove.medi_meem.medi": "\uFCDF",
+ "/yeh.medi_hamzaabove.medi_noon.fina": "\uFC67",
+ "/yeh.medi_hamzaabove.medi_oe.fina": "\uFBF3",
+ "/yeh.medi_hamzaabove.medi_reh.fina": "\uFC64",
+ "/yeh.medi_hamzaabove.medi_u.fina": "\uFBF1",
+ "/yeh.medi_hamzaabove.medi_waw.fina": "\uFBEF",
+ "/yeh.medi_hamzaabove.medi_yeh.fina": "\uFC69",
+ "/yeh.medi_hamzaabove.medi_yu.fina": "\uFBF5",
+ "/yeh.medi_hamzaabove.medi_zain.fina": "\uFC65",
+ "/yeh.medi_heh.medi": "\uFCF1",
+ "/yeh.medi_jeem.medi_yeh.fina": "\uFDAF",
+ "/yeh.medi_meem.fina": "\uFC93",
+ "/yeh.medi_meem.medi": "\uFCF0",
+ "/yeh.medi_meem.medi_meem.fina": "\uFD9C",
+ "/yeh.medi_meem.medi_yeh.fina": "\uFDB0",
+ "/yeh.medi_noon.fina": "\uFC94",
+ "/yeh.medi_reh.fina": "\uFC91",
+ "/yeh.medi_yeh.fina": "\uFC96",
+ "/yeh.medi_zain.fina": "\uFC92",
+ "/yehBarreeDigitThreeAbove": "\u077B",
+ "/yehBarreeDigitTwoAbove": "\u077A",
+ "/yehVabove": "\u06CE",
+ "/yehabove": "\u06E7",
+ "/yeharabic": "\u064A",
+ "/yehbarree": "\u06D2",
+ "/yehbarree.fina": "\uFBAF",
+ "/yehbarree.isol": "\uFBAE",
+ "/yehbarreearabic": "\u06D2",
+ "/yehbarreefinalarabic": "\uFBAF",
+ "/yehbarreehamza": "\u06D3",
+ "/yehbarreehamza.fina": "\uFBB1",
+ "/yehbarreehamza.isol": "\uFBB0",
+ "/yehfarsi": "\u06CC",
+ "/yehfarsi.fina": "\uFBFD",
+ "/yehfarsi.init": "\uFBFE",
+ "/yehfarsi.isol": "\uFBFC",
+ "/yehfarsi.medi": "\uFBFF",
+ "/yehfarsiinvertedV": "\u063D",
+ "/yehfarsithreedotsabove": "\u063F",
+ "/yehfarsitwodotsabove": "\u063E",
+ "/yehfinalarabic": "\uFEF2",
+ "/yehhamza": "\u0626",
+ "/yehhamza.fina": "\uFE8A",
+ "/yehhamza.init": "\uFE8B",
+ "/yehhamza.isol": "\uFE89",
+ "/yehhamza.medi": "\uFE8C",
+ "/yehhamzaabovearabic": "\u0626",
+ "/yehhamzaabovefinalarabic": "\uFE8A",
+ "/yehhamzaaboveinitialarabic": "\uFE8B",
+ "/yehhamzaabovemedialarabic": "\uFE8C",
+ "/yehhighhamza": "\u0678",
+ "/yehinitialarabic": "\uFEF3",
+ "/yehmedialarabic": "\uFEF4",
+ "/yehmeeminitialarabic": "\uFCDD",
+ "/yehmeemisolatedarabic": "\uFC58",
+ "/yehnoonfinalarabic": "\uFC94",
+ "/yehsmall": "\u06E6",
+ "/yehtail": "\u06CD",
+ "/yehthreedotsbelow": "\u06D1",
+ "/yehthreedotsbelowarabic": "\u06D1",
+ "/yekorean": "\u3156",
+ "/yellowHeart": "\u1F49B",
+ "/yen": "\u00A5",
+ "/yenmonospace": "\uFFE5",
+ "/yeokorean": "\u3155",
+ "/yeorinhieuhkorean": "\u3186",
+ "/yerachBenYomo:hb": "\u05AA",
+ "/yerahbenyomohebrew": "\u05AA",
+ "/yerahbenyomolefthebrew": "\u05AA",
+ "/yericyrillic": "\u044B",
+ "/yerudieresiscyrillic": "\u04F9",
+ "/yesieungkorean": "\u3181",
+ "/yesieungpansioskorean": "\u3183",
+ "/yesieungsioskorean": "\u3182",
+ "/yetiv:hb": "\u059A",
+ "/yetivhebrew": "\u059A",
+ "/ygrave": "\u1EF3",
+ "/yhoi": "\u1EF7",
+ "/yhook": "\u01B4",
+ "/yhookabove": "\u1EF7",
+ "/yiarmenian": "\u0575",
+ "/yicyrillic": "\u0457",
+ "/yikorean": "\u3162",
+ "/yintonemod": "\u02EA",
+ "/yinyang": "\u262F",
+ "/yiwnarmenian": "\u0582",
+ "/ylongcyr": "\u044B",
+ "/ylongdieresiscyr": "\u04F9",
+ "/yloop": "\u1EFF",
+ "/ymacron": "\u0233",
+ "/ymonospace": "\uFF59",
+ "/yocirclekatakana": "\u32F5",
+ "/yod": "\u05D9",
+ "/yod:hb": "\u05D9",
+ "/yod_yod:hb": "\u05F2",
+ "/yod_yod_patah:hb": "\uFB1F",
+ "/yoddagesh": "\uFB39",
+ "/yoddageshhebrew": "\uFB39",
+ "/yodhebrew": "\u05D9",
+ "/yodwithdagesh:hb": "\uFB39",
+ "/yodwithhiriq:hb": "\uFB1D",
+ "/yodyodhebrew": "\u05F2",
+ "/yodyodpatahhebrew": "\uFB1F",
+ "/yogh": "\u021D",
+ "/yohiragana": "\u3088",
+ "/yoikorean": "\u3189",
+ "/yokatakana": "\u30E8",
+ "/yokatakanahalfwidth": "\uFF96",
+ "/yokorean": "\u315B",
+ "/yosmallhiragana": "\u3087",
+ "/yosmallkatakana": "\u30E7",
+ "/yosmallkatakanahalfwidth": "\uFF6E",
+ "/yot": "\u03F3",
+ "/yotgreek": "\u03F3",
+ "/yoyaekorean": "\u3188",
+ "/yoyakorean": "\u3187",
+ "/yoyakthai": "\u0E22",
+ "/yoyingthai": "\u0E0D",
+ "/yparen": "\u24B4",
+ "/yparenthesized": "\u24B4",
+ "/ypogegrammeni": "\u037A",
+ "/ypogegrammenigreekcmb": "\u0345",
+ "/yr": "\u01A6",
+ "/yring": "\u1E99",
+ "/ystroke": "\u024F",
+ "/ysuperior": "\u02B8",
+ "/ysupmod": "\u02B8",
+ "/ytilde": "\u1EF9",
+ "/yturned": "\u028E",
+ "/yu.fina": "\uFBDC",
+ "/yu.isol": "\uFBDB",
+ "/yuansquare": "\u3350",
+ "/yucirclekatakana": "\u32F4",
+ "/yucyr": "\u044E",
+ "/yuhiragana": "\u3086",
+ "/yuikorean": "\u318C",
+ "/yukatakana": "\u30E6",
+ "/yukatakanahalfwidth": "\uFF95",
+ "/yukirghiz": "\u06C9",
+ "/yukirghiz.fina": "\uFBE3",
+ "/yukirghiz.isol": "\uFBE2",
+ "/yukorean": "\u3160",
+ "/yukrcyr": "\u0457",
+ "/yusbigcyr": "\u046B",
+ "/yusbigcyrillic": "\u046B",
+ "/yusbigiotifiedcyr": "\u046D",
+ "/yusbigiotifiedcyrillic": "\u046D",
+ "/yuslittlecyr": "\u0467",
+ "/yuslittlecyrillic": "\u0467",
+ "/yuslittleiotifiedcyr": "\u0469",
+ "/yuslittleiotifiedcyrillic": "\u0469",
+ "/yusmallhiragana": "\u3085",
+ "/yusmallkatakana": "\u30E5",
+ "/yusmallkatakanahalfwidth": "\uFF6D",
+ "/yuyekorean": "\u318B",
+ "/yuyeokorean": "\u318A",
+ "/yyabengali": "\u09DF",
+ "/yyadeva": "\u095F",
+ "/z": "\u007A",
+ "/zaarmenian": "\u0566",
+ "/zacute": "\u017A",
+ "/zadeva": "\u095B",
+ "/zagurmukhi": "\u0A5B",
+ "/zah": "\u0638",
+ "/zah.fina": "\uFEC6",
+ "/zah.init": "\uFEC7",
+ "/zah.init_meem.fina": "\uFC28",
+ "/zah.init_meem.medi": "\uFCB9",
+ "/zah.isol": "\uFEC5",
+ "/zah.medi": "\uFEC8",
+ "/zah.medi_meem.medi": "\uFD3B",
+ "/zaharabic": "\u0638",
+ "/zahfinalarabic": "\uFEC6",
+ "/zahinitialarabic": "\uFEC7",
+ "/zahiragana": "\u3056",
+ "/zahmedialarabic": "\uFEC8",
+ "/zain": "\u0632",
+ "/zain.fina": "\uFEB0",
+ "/zain.isol": "\uFEAF",
+ "/zainabove": "\u0617",
+ "/zainarabic": "\u0632",
+ "/zainfinalarabic": "\uFEB0",
+ "/zakatakana": "\u30B6",
+ "/zaqefGadol:hb": "\u0595",
+ "/zaqefQatan:hb": "\u0594",
+ "/zaqefgadolhebrew": "\u0595",
+ "/zaqefqatanhebrew": "\u0594",
+ "/zarqa:hb": "\u0598",
+ "/zarqahebrew": "\u0598",
+ "/zayin": "\u05D6",
+ "/zayin:hb": "\u05D6",
+ "/zayindagesh": "\uFB36",
+ "/zayindageshhebrew": "\uFB36",
+ "/zayinhebrew": "\u05D6",
+ "/zayinwithdagesh:hb": "\uFB36",
+ "/zbopomofo": "\u3117",
+ "/zcaron": "\u017E",
+ "/zcircle": "\u24E9",
+ "/zcircumflex": "\u1E91",
+ "/zcurl": "\u0291",
+ "/zdescender": "\u2C6C",
+ "/zdot": "\u017C",
+ "/zdotaccent": "\u017C",
+ "/zdotbelow": "\u1E93",
+ "/zecyr": "\u0437",
+ "/zecyrillic": "\u0437",
+ "/zedescendercyrillic": "\u0499",
+ "/zedieresiscyr": "\u04DF",
+ "/zedieresiscyrillic": "\u04DF",
+ "/zehiragana": "\u305C",
+ "/zekatakana": "\u30BC",
+ "/zero": "\u0030",
+ "/zero.inferior": "\u2080",
+ "/zero.superior": "\u2070",
+ "/zeroarabic": "\u0660",
+ "/zerobengali": "\u09E6",
+ "/zerocircle": "\u24EA",
+ "/zerocircleblack": "\u24FF",
+ "/zerocomma": "\u1F101",
+ "/zerodeva": "\u0966",
+ "/zerofar": "\u06F0",
+ "/zerofullstop": "\u1F100",
+ "/zerogujarati": "\u0AE6",
+ "/zerogurmukhi": "\u0A66",
+ "/zerohackarabic": "\u0660",
+ "/zeroinferior": "\u2080",
+ "/zeromonospace": "\uFF10",
+ "/zerooldstyle": "\uF730",
+ "/zeropersian": "\u06F0",
+ "/zerosquareabove": "\u06E0",
+ "/zerosuperior": "\u2070",
+ "/zerothai": "\u0E50",
+ "/zerothirds": "\u2189",
+ "/zerowidthjoiner": "\uFEFF",
+ "/zerowidthnobreakspace": "\uFEFF",
+ "/zerowidthnonjoiner": "\u200C",
+ "/zerowidthspace": "\u200B",
+ "/zeta": "\u03B6",
+ "/zetailcyr": "\u0499",
+ "/zhbopomofo": "\u3113",
+ "/zhearmenian": "\u056A",
+ "/zhebrevecyr": "\u04C2",
+ "/zhebrevecyrillic": "\u04C2",
+ "/zhecyr": "\u0436",
+ "/zhecyrillic": "\u0436",
+ "/zhedescendercyrillic": "\u0497",
+ "/zhedieresiscyr": "\u04DD",
+ "/zhedieresiscyrillic": "\u04DD",
+ "/zhetailcyr": "\u0497",
+ "/zhook": "\u0225",
+ "/zihiragana": "\u3058",
+ "/zikatakana": "\u30B8",
+ "/zildefunc": "\u236C",
+ "/zinorhebrew": "\u05AE",
+ "/zjekomicyr": "\u0505",
+ "/zlinebelow": "\u1E95",
+ "/zmonospace": "\uFF5A",
+ "/znotationbagmembership": "\u22FF",
+ "/zohiragana": "\u305E",
+ "/zokatakana": "\u30BE",
+ "/zparen": "\u24B5",
+ "/zparenthesized": "\u24B5",
+ "/zretroflex": "\u0290",
+ "/zretroflexhook": "\u0290",
+ "/zstroke": "\u01B6",
+ "/zswashtail": "\u0240",
+ "/zuhiragana": "\u305A",
+ "/zukatakana": "\u30BA",
+ "/zwarakay": "\u0659",
+ # manually added from
+ # https://github.com/serviceprototypinglab/latex-pdfa/blob/master/glyphtounicode-cmr.tex
+ "/angbracketleftBig": "\u28E8",
+ "/angbracketleftBigg": "\u27E8",
+ "/angbracketleftbig": "\u27E8",
+ "/angbracketleftbigg": "\u27E8",
+ "/angbracketrightBig": "\u27E9",
+ "/angbracketrightBigg": "\u27E9",
+ "/angbracketrightbig": "\u27E9",
+ "/angbracketrightbigg": "\u27E9",
+ "/arrowbt": "\u2193",
+ "/arrowdblbt": "\u21D3",
+ "/arrowdbltp": "\u21D1",
+ "/arrowhookleft": "\u21AA",
+ "/arrowhookright": "\u21A9",
+ "/arrowtp": "\u2191",
+ # diff : "/arrowvertex": "\u23D0",
+ "/arrowvertexdbl": "\uED12",
+ "/backslashBig": "\u005C",
+ "/backslashBigg": "\u005C",
+ "/backslashbig": "\u005C",
+ "/backslashbigg": "\u005C",
+ # diff : "/braceex": "\u23AA",
+ "/bracehtipdownleft": "\uED17",
+ "/bracehtipdownright": "\uED18",
+ "/bracehtipupleft": "\uED19",
+ "/bracehtipupright": "\uED1A",
+ "/braceleftBig": "\u007B",
+ "/braceleftBigg": "\u007B",
+ "/braceleftbig": "\u007B",
+ "/braceleftbigg": "\u007B",
+ # diff : "/braceleftbt": "\u23A9",
+ # diff : "/braceleftmid": "\u23A8",
+ # diff : "/bracelefttp": "\u23A7",
+ "/bracerightBig": "\u007D",
+ "/bracerightBigg": "\u007D",
+ "/bracerightbig": "\u007D",
+ "/bracerightbigg": "\u007D",
+ # diff : "/bracerightbt": "\u23AD",
+ # diff : "/bracerightmid": "\u23AC",
+ # diff : "/bracerighttp": "\u23AB",
+ "/bracketleftBig": "\u005B",
+ "/bracketleftBigg": "\u005B",
+ "/bracketleftbig": "\u005B",
+ "/bracketleftbigg": "\u005B",
+ # diff : "/bracketleftbt": "\u23A3",
+ # diff : "/bracketleftex": "\u23A2",
+ # diff : "/bracketlefttp": "\u23A1",
+ "/bracketrightBig": "\u005D",
+ "/bracketrightBigg": "\u005D",
+ "/bracketrightbig": "\u005D",
+ "/bracketrightbigg": "\u005D",
+ # diff : "/bracketrightbt": "\u23A6",
+ # diff : "/bracketrightex": "\u23A5",
+ # diff : "/bracketrighttp": "\u23A4",
+ "/ceilingleftBig": "\u2308",
+ "/ceilingleftBigg": "\u2308",
+ "/ceilingleftbig": "\u2308",
+ "/ceilingleftbigg": "\u2308",
+ "/ceilingrightBig": "\u2309",
+ "/ceilingrightBigg": "\u2309",
+ "/ceilingrightbig": "\u2309",
+ "/ceilingrightbigg": "\u2309",
+ "/circledotdisplay": "\u2A00",
+ "/circledottext": "\u2A00",
+ "/circlemultiplydisplay": "\u2A02",
+ "/circlemultiplytext": "\u2A02",
+ "/circleplusdisplay": "\u2A01",
+ "/circleplustext": "\u2A01",
+ "/contintegraldisplay": "\u222E",
+ "/contintegraltext": "\u222E",
+ "/coproductdisplay": "\u2210",
+ "/coproducttext": "\u2210",
+ "/floorleftBig": "\u230A",
+ "/floorleftBigg": "\u230A",
+ "/floorleftbig": "\u230A",
+ "/floorleftbigg": "\u230A",
+ "/floorrightBig": "\u230B",
+ "/floorrightBigg": "\u230B",
+ "/floorrightbig": "\u230B",
+ "/floorrightbigg": "\u230B",
+ "/hatwide": "\u02C6",
+ "/hatwider": "\u02C6",
+ "/hatwidest": "\u02C6",
+ "/integraldisplay": "\u222B",
+ "/integraltext": "\u222B",
+ "/intersectiondisplay": "\u22C2",
+ "/intersectiontext": "\u22C2",
+ "/logicalanddisplay": "\u22C0",
+ "/logicalandtext": "\u22C0",
+ "/logicalordisplay": "\u22C1",
+ "/logicalortext": "\u22C1",
+ "/mapsto": "\u21A6",
+ "/parenleftBig": "\u0028",
+ "/parenleftBigg": "\u0028",
+ "/parenleftbig": "\u0028",
+ "/parenleftbigg": "\u0028",
+ # diff : "/parenleftbt": "\u239D",
+ # diff : "/parenleftex": "\u239C",
+ # diff : "/parenlefttp": "\u239B",
+ "/parenrightBig": "\u0029",
+ "/parenrightBigg": "\u0029",
+ "/parenrightbig": "\u0029",
+ "/parenrightbigg": "\u0029",
+ # diff : "/parenrightbt": "\u23A0",
+ # diff : "/parenrightex": "\u239F",
+ # diff : "/parenrighttp": "\u239E",
+ "/productdisplay": "\u220F",
+ "/producttext": "\u220F",
+ "/radicalBig": "\u221A",
+ "/radicalBigg": "\u221A",
+ "/radicalbig": "\u221A",
+ "/radicalbigg": "\u221A",
+ "/radicalbt": "\u221A",
+ "/radicaltp": "\uED6A",
+ "/radicalvertex": "\uED6B",
+ "/slashBig": "\u002F",
+ "/slashBigg": "\u002F",
+ "/slashbig": "\u002F",
+ "/slashbigg": "\u002F",
+ "/summationdisplay": "\u2211",
+ "/summationtext": "\u2211",
+ "/tie": "\u2040",
+ "/tildewide": "\u02DC",
+ "/tildewider": "\u02DC",
+ "/tildewidest": "\u02DC",
+ "/uniondisplay": "\u22C3",
+ "/unionmultidisplay": "\u2A04",
+ "/unionmultitext": "\u2A04",
+ "/unionsqdisplay": "\u2A06",
+ "/unionsqtext": "\u2A06",
+ "/uniontext": "\u22C3",
+ "/vextenddouble": "\uED79",
+ "/vextendsingle": "\u23D0",
+ "/a1": "\u25C1",
+ "/a2": "\u22B4",
+ "/a3": "\u25B7",
+ "/a4": "\u22B5",
+ "/a40": "\u02C2",
+ "/a41": "\u02C3",
+ "/a42": "\u2303",
+ "/a43": "\u2304",
+ "/a48": "\u2127",
+ "/a49": "\u22C8",
+ "/a50": "\u25A1",
+ "/a51": "\u25C7",
+ "/a58": "\u2053",
+ "/a59": "\u219D",
+ "/a60": "\u228F",
+ "/a61": "\u2290",
+ "/d0": "\u2199",
+ "/d1": "\u2199",
+ "/d2": "\u2199",
+ "/d3": "\u2199",
+ "/d4": "\u2199",
+ "/d5": "\u2199",
+ "/d6": "\u2199",
+ "/d7": "\u2193",
+ "/d8": "\u2193",
+ "/d9": "\u2193",
+ "/d10": "\u2193",
+ "/d11": "\u2193",
+ "/d12": "\u2193",
+ "/d13": "\u2193",
+ "/d14": "\u2193",
+ "/d15": "\u2193",
+ "/d16": "\u2193",
+ "/d17": "\u2193",
+ "/d18": "\u2193",
+ "/d19": "\u2193",
+ "/d20": "\u2193",
+ "/d21": "\u2193",
+ "/d22": "\u2193",
+ "/d23": "\u2193",
+ "/d24": "\u2198",
+ "/d25": "\u2198",
+ "/d26": "\u2198",
+ "/d27": "\u2198",
+ "/d28": "\u2198",
+ "/d29": "\u2198",
+ "/d30": "\u2198",
+ "/d31": "\u2198",
+ "/d32": "\u2198",
+ "/d33": "\u2198",
+ "/d34": "\u2198",
+ "/d35": "\u2198",
+ "/d36": "\u2198",
+ "/d37": "\u2198",
+ "/d38": "\u2198",
+ "/d39": "\u2192",
+ "/d40": "\u2192",
+ "/d41": "\u2192",
+ "/d42": "\u2192",
+ "/d43": "\u2192",
+ "/d44": "\u2192",
+ "/d45": "\u2192",
+ "/d46": "\u2192",
+ "/d47": "\u2192",
+ "/d48": "\u2192",
+ "/d49": "\u2192",
+ "/d50": "\u2192",
+ "/d51": "\u2192",
+ "/d52": "\u2192",
+ "/d53": "\u2192",
+ "/d54": "\u2192",
+ "/d55": "\u2192",
+ "/d56": "\u2197",
+ "/d57": "\u2197",
+ "/d58": "\u2197",
+ "/d59": "\u2197",
+ "/d60": "\u2197",
+ "/d61": "\u2197",
+ "/d62": "\u2197",
+ "/d63": "\u2197",
+ "/d64": "\u2197",
+ "/d65": "\u2197",
+ "/d66": "\u2197",
+ "/d67": "\u2197",
+ "/d68": "\u2197",
+ "/d69": "\u2197",
+ "/d70": "\u2197",
+ "/d71": "\u2191",
+ "/d72": "\u2191",
+ "/d73": "\u2191",
+ "/d74": "\u2191",
+ "/d75": "\u2191",
+ "/d76": "\u2191",
+ "/d77": "\u2191",
+ "/d78": "\u2191",
+ "/d79": "\u2191",
+ "/d80": "\u2191",
+ "/d81": "\u2191",
+ "/d82": "\u2191",
+ "/d83": "\u2191",
+ "/d84": "\u2191",
+ "/d85": "\u2191",
+ "/d86": "\u2191",
+ "/d87": "\u2191",
+ "/d88": "\u2196",
+ "/d89": "\u2196",
+ "/d90": "\u2196",
+ "/d91": "\u2196",
+ "/d92": "\u2196",
+ "/d93": "\u2196",
+ "/d94": "\u2196",
+ "/d95": "\u2196",
+ "/d96": "\u2196",
+ "/d97": "\u2196",
+ "/d98": "\u2196",
+ "/d99": "\u2196",
+ "/d100": "\u2196",
+ "/d101": "\u2196",
+ "/d102": "\u2196",
+ "/d103": "\u2190",
+ "/d104": "\u2190",
+ "/d105": "\u2190",
+ "/d106": "\u2190",
+ "/d107": "\u2190",
+ "/d108": "\u2190",
+ "/d109": "\u2190",
+ "/d110": "\u2190",
+ "/d111": "\u2190",
+ "/d112": "\u2190",
+ "/d113": "\u2190",
+ "/d114": "\u2190",
+ "/d115": "\u2190",
+ "/d116": "\u2190",
+ "/d117": "\u2190",
+ "/d118": "\u2190",
+ "/d119": "\u2190",
+ "/d120": "\u2199",
+ "/d121": "\u2199",
+ "/d122": "\u2199",
+ "/d123": "\u2199",
+ "/d124": "\u2199",
+ "/d125": "\u2199",
+ "/d126": "\u2199",
+ "/d127": "\u2199",
+ # manually added from
+ # https://github.com/kohler/lcdf-typetools/blob/master/texglyphlist.txt
+ "/Ifractur": "\u2111",
+ "/FFsmall": "\uF766",
+ "/FFIsmall": "\uF766",
+ "/FFLsmall": "\uF766",
+ "/FIsmall": "\uF766",
+ "/FLsmall": "\uF766",
+ # diff : "/Germandbls": "\u0053",
+ "/Germandblssmall": "\uF773",
+ "/Ng": "\u014A",
+ "/Rfractur": "\u211C",
+ "/SS": "\u0053",
+ "/SSsmall": "\uF773",
+ "/altselector": "\uD802",
+ "/angbracketleft": "\u27E8",
+ "/angbracketright": "\u27E9",
+ "/arrowbothv": "\u2195",
+ "/arrowdblbothv": "\u21D5",
+ "/arrowleftbothalf": "\u21BD",
+ "/arrowlefttophalf": "\u21BC",
+ "/arrownortheast": "\u2197",
+ "/arrownorthwest": "\u2196",
+ "/arrowrightbothalf": "\u21C1",
+ "/arrowrighttophalf": "\u21C0",
+ "/arrowsoutheast": "\u2198",
+ "/arrowsouthwest": "\u2199",
+ "/ascendercompwordmark": "\uD80A",
+ "/asteriskcentered": "\u2217",
+ "/bardbl": "\u2225",
+ "/capitalcompwordmark": "\uD809",
+ "/circlecopyrt": "\u20DD",
+ "/circledivide": "\u2298",
+ "/circleminus": "\u2296",
+ "/coproduct": "\u2A3F",
+ "/ct": "\u0063",
+ "/cwm": "\u200C",
+ "/dblbracketleft": "\u27E6",
+ "/dblbracketright": "\u27E7",
+ # diff : "/diamond": "\u2662",
+ "/diamondmath": "\u22C4",
+ # diff : "/dotlessj": "\u0237",
+ "/emptyslot": "\uD801",
+ "/epsilon1": "\u03F5",
+ "/epsiloninv": "\u03F6",
+ "/equivasymptotic": "\u224D",
+ "/flat": "\u266D",
+ "/follows": "\u227B",
+ "/followsequal": "\u2AB0",
+ "/followsorcurly": "\u227D",
+ "/greatermuch": "\u226B",
+ # diff : "/heart": "\u2661",
+ "/interrobangdown": "\u2E18",
+ "/intersectionsq": "\u2293",
+ "/latticetop": "\u22A4",
+ "/lessmuch": "\u226A",
+ "/longdbls": "\u017F",
+ "/longsh": "\u017F",
+ "/longsi": "\u017F",
+ "/longsl": "\u017F",
+ "/longst": "\uFB05",
+ "/lscript": "\u2113",
+ "/natural": "\u266E",
+ "/negationslash": "\u0338",
+ "/ng": "\u014B",
+ "/owner": "\u220B",
+ "/pertenthousand": "\u2031",
+ # diff : "/phi": "\u03D5",
+ # diff : "/phi1": "\u03C6",
+ "/pi1": "\u03D6",
+ "/precedesequal": "\u2AAF",
+ "/precedesorcurly": "\u227C",
+ "/prime": "\u2032",
+ "/rho1": "\u03F1",
+ "/ringfitted": "\uD80D",
+ "/sharp": "\u266F",
+ "/similarequal": "\u2243",
+ "/slurabove": "\u2322",
+ "/slurbelow": "\u2323",
+ "/st": "\uFB06",
+ "/subsetsqequal": "\u2291",
+ "/supersetsqequal": "\u2292",
+ "/triangle": "\u25B3",
+ "/triangleinv": "\u25BD",
+ "/triangleleft": "\u25C1",
+ # diff : "/triangleright": "\u25B7",
+ "/turnstileleft": "\u22A2",
+ "/turnstileright": "\u22A3",
+ "/twelveudash": "\uD80C",
+ "/unionmulti": "\u228E",
+ "/unionsq": "\u2294",
+ "/vector": "\u20D7",
+ "/visualspace": "\u2423",
+ "/Dbar": "\u0110",
+ "/compwordmark": "\u200C",
+ "/dbar": "\u0111",
+ "/rangedash": "\u2013",
+ "/hyphenchar": "\u002D",
+ "/punctdash": "\u2014",
+ "/visiblespace": "\u2423",
+ "/Yen": "\u00A5",
+ "/anticlockwise": "\u27F2",
+ "/arrowparrleftright": "\u21C6",
+ "/arrowparrrightleft": "\u21C4",
+ "/arrowtailleft": "\u21A2",
+ "/arrowtailright": "\u21A3",
+ "/arrowtripleleft": "\u21DA",
+ "/arrowtripleright": "\u21DB",
+ "/check": "\u2713",
+ "/circleR": "\u00AE",
+ "/circleS": "\u24C8",
+ "/circleasterisk": "\u229B",
+ "/circleequal": "\u229C",
+ "/circlering": "\u229A",
+ "/clockwise": "\u27F3",
+ "/curlyleft": "\u21AB",
+ "/curlyright": "\u21AC",
+ "/dblarrowdwn": "\u21CA",
+ "/dblarrowheadleft": "\u219E",
+ "/dblarrowheadright": "\u21A0",
+ # diff : "/dblarrowup": "\u21C8",
+ "/defines": "\u225C",
+ "/diamondsolid": "\u2666",
+ "/difference": "\u224F",
+ "/downfall": "\u22CE",
+ "/equaldotleftright": "\u2252",
+ "/equaldotrightleft": "\u2253",
+ "/equalorfollows": "\u22DF",
+ # diff : "/equalorgreater": "\u2A96",
+ # diff : "/equalorless": "\u2A95",
+ "/equalsdots": "\u2251",
+ "/followsorequal": "\u227F",
+ "/forcesbar": "\u22AA",
+ # diff : "/fork": "\u22D4",
+ "/geomequivalent": "\u224E",
+ "/greaterdbleqlless": "\u2A8C",
+ "/greaterdblequal": "\u2267",
+ "/greaterlessequal": "\u22DB",
+ "/greaterorapproxeql": "\u2A86",
+ "/greaterorequalslant": "\u2A7E",
+ "/greaterorsimilar": "\u2273",
+ "/harpoondownleft": "\u21C3",
+ "/harpoondownright": "\u21C2",
+ "/harpoonleftright": "\u21CC",
+ "/harpoonrightleft": "\u21CB",
+ "/harpoonupleft": "\u21BF",
+ "/harpoonupright": "\u21BE",
+ "/intercal": "\u22BA",
+ "/lessdbleqlgreater": "\u2A8B",
+ "/lessdblequal": "\u2266",
+ "/lessequalgreater": "\u22DA",
+ "/lessorapproxeql": "\u2A85",
+ "/lessorequalslant": "\u2A7D",
+ "/lessorsimilar": "\u2272",
+ "/maltesecross": "\u2720",
+ "/multiopenleft": "\u22CB",
+ "/multiopenright": "\u22CC",
+ "/orunderscore": "\u22BB",
+ "/perpcorrespond": "\u2A5E",
+ # diff : "/precedesorequal": "\u227E",
+ "/primereverse": "\u2035",
+ "/revasymptequal": "\u22CD",
+ "/revsimilar": "\u223D",
+ "/rightanglene": "\u231D",
+ "/rightanglenw": "\u231C",
+ "/rightanglese": "\u231F",
+ "/rightanglesw": "\u231E",
+ "/satisfies": "\u22A8",
+ "/shiftleft": "\u21B0",
+ "/shiftright": "\u21B1",
+ "/square": "\u25A1",
+ "/squaredot": "\u22A1",
+ "/squareminus": "\u229F",
+ "/squaremultiply": "\u22A0",
+ "/squareplus": "\u229E",
+ "/squaresolid": "\u25A0",
+ "/squiggleleftright": "\u21AD",
+ "/squiggleright": "\u21DD",
+ "/subsetdblequal": "\u2AC5",
+ "/supersetdbl": "\u22D1",
+ "/supersetdblequal": "\u2AC6",
+ "/triangledownsld": "\u25BC",
+ "/triangleleftequal": "\u22B4",
+ "/triangleleftsld": "\u25C0",
+ "/trianglerightequal": "\u22B5",
+ "/trianglerightsld": "\u25B6",
+ "/trianglesolid": "\u25B2",
+ "/uprise": "\u22CF",
+ # diff : "/Digamma": "\u1D7C",
+ "/Finv": "\u2132",
+ "/Gmir": "\u2141",
+ "/Omegainv": "\u2127",
+ "/approxorequal": "\u224A",
+ "/archleftdown": "\u21B6",
+ "/archrightdown": "\u21B7",
+ "/beth": "\u2136",
+ "/daleth": "\u2138",
+ "/dividemultiply": "\u22C7",
+ "/downslope": "\u29F9",
+ "/equalorsimilar": "\u2242",
+ "/follownotdbleqv": "\u2ABA",
+ "/follownotslnteql": "\u2AB6",
+ "/followornoteqvlnt": "\u22E9",
+ "/greaternotdblequal": "\u2A8A",
+ "/greaternotequal": "\u2A88",
+ "/greaterornotdbleql": "\u2269",
+ "/greaterornotequal": "\u2269",
+ "/integerdivide": "\u2216",
+ "/lessnotdblequal": "\u2A89",
+ "/lessnotequal": "\u2A87",
+ "/lessornotdbleql": "\u2268",
+ "/lessornotequal": "\u2268",
+ "/multicloseleft": "\u22C9",
+ "/multicloseright": "\u22CA",
+ "/notapproxequal": "\u2247",
+ "/notarrowboth": "\u21AE",
+ "/notarrowleft": "\u219A",
+ "/notarrowright": "\u219B",
+ "/notbar": "\u2224",
+ "/notdblarrowboth": "\u21CE",
+ "/notdblarrowleft": "\u21CD",
+ "/notdblarrowright": "\u21CF",
+ "/notfollows": "\u2281",
+ "/notfollowsoreql": "\u2AB0",
+ "/notforces": "\u22AE",
+ "/notforcesextra": "\u22AF",
+ "/notgreaterdblequal": "\u2267",
+ "/notgreaterequal": "\u2271",
+ "/notgreaterorslnteql": "\u2A7E",
+ "/notlessdblequal": "\u2266",
+ "/notlessequal": "\u2270",
+ "/notlessorslnteql": "\u2A7D",
+ "/notprecedesoreql": "\u2AAF",
+ "/notsatisfies": "\u22AD",
+ "/notsimilar": "\u2241",
+ "/notsubseteql": "\u2288",
+ "/notsubsetordbleql": "\u2AC5",
+ "/notsubsetoreql": "\u228A",
+ "/notsuperseteql": "\u2289",
+ "/notsupersetordbleql": "\u2AC6",
+ "/notsupersetoreql": "\u228B",
+ "/nottriangeqlleft": "\u22EC",
+ "/nottriangeqlright": "\u22ED",
+ "/nottriangleleft": "\u22EA",
+ "/nottriangleright": "\u22EB",
+ "/notturnstile": "\u22AC",
+ "/planckover2pi": "\u210F",
+ "/planckover2pi1": "\u210F",
+ "/precedenotdbleqv": "\u2AB9",
+ "/precedenotslnteql": "\u2AB5",
+ "/precedeornoteqvlnt": "\u22E8",
+ "/subsetnoteql": "\u228A",
+ "/subsetornotdbleql": "\u2ACB",
+ "/supersetnoteql": "\u228B",
+ "/supersetornotdbleql": "\u2ACC",
+ "/upslope": "\u29F8",
+}
+
+
+def _complete() -> None:
+ global adobe_glyphs
+ for i in range(256):
+ adobe_glyphs[f"/a{i}"] = chr(i)
+ adobe_glyphs["/.notdef"] = "□"
+
+
+_complete()
diff --git a/.venv/lib/python3.12/site-packages/pypdf/_codecs/pdfdoc.py b/.venv/lib/python3.12/site-packages/pypdf/_codecs/pdfdoc.py
new file mode 100644
index 00000000..306357a5
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/pypdf/_codecs/pdfdoc.py
@@ -0,0 +1,264 @@
+# PDFDocEncoding Character Set: Table D.2 of PDF Reference 1.7
+# C.1 Predefined encodings sorted by character name of another PDF reference
+# Some indices have '\u0000' although they should have something else:
+# 22: should be '\u0017'
+_pdfdoc_encoding = [
+ "\u0000",
+ "\u0001",
+ "\u0002",
+ "\u0003",
+ "\u0004",
+ "\u0005",
+ "\u0006",
+ "\u0007", # 0 - 7
+ "\u0008",
+ "\u0009",
+ "\u000a",
+ "\u000b",
+ "\u000c",
+ "\u000d",
+ "\u000e",
+ "\u000f", # 8 - 15
+ "\u0010",
+ "\u0011",
+ "\u0012",
+ "\u0013",
+ "\u0014",
+ "\u0015",
+ "\u0000",
+ "\u0017", # 16 - 23
+ "\u02d8",
+ "\u02c7",
+ "\u02c6",
+ "\u02d9",
+ "\u02dd",
+ "\u02db",
+ "\u02da",
+ "\u02dc", # 24 - 31
+ "\u0020",
+ "\u0021",
+ "\u0022",
+ "\u0023",
+ "\u0024",
+ "\u0025",
+ "\u0026",
+ "\u0027", # 32 - 39
+ "\u0028",
+ "\u0029",
+ "\u002a",
+ "\u002b",
+ "\u002c",
+ "\u002d",
+ "\u002e",
+ "\u002f", # 40 - 47
+ "\u0030",
+ "\u0031",
+ "\u0032",
+ "\u0033",
+ "\u0034",
+ "\u0035",
+ "\u0036",
+ "\u0037", # 48 - 55
+ "\u0038",
+ "\u0039",
+ "\u003a",
+ "\u003b",
+ "\u003c",
+ "\u003d",
+ "\u003e",
+ "\u003f", # 56 - 63
+ "\u0040",
+ "\u0041",
+ "\u0042",
+ "\u0043",
+ "\u0044",
+ "\u0045",
+ "\u0046",
+ "\u0047", # 64 - 71
+ "\u0048",
+ "\u0049",
+ "\u004a",
+ "\u004b",
+ "\u004c",
+ "\u004d",
+ "\u004e",
+ "\u004f", # 72 - 79
+ "\u0050",
+ "\u0051",
+ "\u0052",
+ "\u0053",
+ "\u0054",
+ "\u0055",
+ "\u0056",
+ "\u0057", # 80 - 87
+ "\u0058",
+ "\u0059",
+ "\u005a",
+ "\u005b",
+ "\u005c",
+ "\u005d",
+ "\u005e",
+ "\u005f", # 88 - 95
+ "\u0060",
+ "\u0061",
+ "\u0062",
+ "\u0063",
+ "\u0064",
+ "\u0065",
+ "\u0066",
+ "\u0067", # 96 - 103
+ "\u0068",
+ "\u0069",
+ "\u006a",
+ "\u006b",
+ "\u006c",
+ "\u006d",
+ "\u006e",
+ "\u006f", # 104 - 111
+ "\u0070",
+ "\u0071",
+ "\u0072",
+ "\u0073",
+ "\u0074",
+ "\u0075",
+ "\u0076",
+ "\u0077", # 112 - 119
+ "\u0078",
+ "\u0079",
+ "\u007a",
+ "\u007b",
+ "\u007c",
+ "\u007d",
+ "\u007e",
+ "\u0000", # 120 - 127
+ "\u2022",
+ "\u2020",
+ "\u2021",
+ "\u2026",
+ "\u2014",
+ "\u2013",
+ "\u0192",
+ "\u2044", # 128 - 135
+ "\u2039",
+ "\u203a",
+ "\u2212",
+ "\u2030",
+ "\u201e",
+ "\u201c",
+ "\u201d",
+ "\u2018", # 136 - 143
+ "\u2019",
+ "\u201a",
+ "\u2122",
+ "\ufb01",
+ "\ufb02",
+ "\u0141",
+ "\u0152",
+ "\u0160", # 144 - 151
+ "\u0178",
+ "\u017d",
+ "\u0131",
+ "\u0142",
+ "\u0153",
+ "\u0161",
+ "\u017e",
+ "\u0000", # 152 - 159
+ "\u20ac",
+ "\u00a1",
+ "\u00a2",
+ "\u00a3",
+ "\u00a4",
+ "\u00a5",
+ "\u00a6",
+ "\u00a7", # 160 - 167
+ "\u00a8",
+ "\u00a9",
+ "\u00aa",
+ "\u00ab",
+ "\u00ac",
+ "\u0000",
+ "\u00ae",
+ "\u00af", # 168 - 175
+ "\u00b0",
+ "\u00b1",
+ "\u00b2",
+ "\u00b3",
+ "\u00b4",
+ "\u00b5",
+ "\u00b6",
+ "\u00b7", # 176 - 183
+ "\u00b8",
+ "\u00b9",
+ "\u00ba",
+ "\u00bb",
+ "\u00bc",
+ "\u00bd",
+ "\u00be",
+ "\u00bf", # 184 - 191
+ "\u00c0",
+ "\u00c1",
+ "\u00c2",
+ "\u00c3",
+ "\u00c4",
+ "\u00c5",
+ "\u00c6",
+ "\u00c7", # 192 - 199
+ "\u00c8",
+ "\u00c9",
+ "\u00ca",
+ "\u00cb",
+ "\u00cc",
+ "\u00cd",
+ "\u00ce",
+ "\u00cf", # 200 - 207
+ "\u00d0",
+ "\u00d1",
+ "\u00d2",
+ "\u00d3",
+ "\u00d4",
+ "\u00d5",
+ "\u00d6",
+ "\u00d7", # 208 - 215
+ "\u00d8",
+ "\u00d9",
+ "\u00da",
+ "\u00db",
+ "\u00dc",
+ "\u00dd",
+ "\u00de",
+ "\u00df", # 216 - 223
+ "\u00e0",
+ "\u00e1",
+ "\u00e2",
+ "\u00e3",
+ "\u00e4",
+ "\u00e5",
+ "\u00e6",
+ "\u00e7", # 224 - 231
+ "\u00e8",
+ "\u00e9",
+ "\u00ea",
+ "\u00eb",
+ "\u00ec",
+ "\u00ed",
+ "\u00ee",
+ "\u00ef", # 232 - 239
+ "\u00f0",
+ "\u00f1",
+ "\u00f2",
+ "\u00f3",
+ "\u00f4",
+ "\u00f5",
+ "\u00f6",
+ "\u00f7", # 240 - 247
+ "\u00f8",
+ "\u00f9",
+ "\u00fa",
+ "\u00fb",
+ "\u00fc",
+ "\u00fd",
+ "\u00fe",
+ "\u00ff", # 248 - 255
+]
+
+assert len(_pdfdoc_encoding) == 256
diff --git a/.venv/lib/python3.12/site-packages/pypdf/_codecs/std.py b/.venv/lib/python3.12/site-packages/pypdf/_codecs/std.py
new file mode 100644
index 00000000..a6057ff3
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/pypdf/_codecs/std.py
@@ -0,0 +1,258 @@
+_std_encoding = [
+ "\x00",
+ "\x01",
+ "\x02",
+ "\x03",
+ "\x04",
+ "\x05",
+ "\x06",
+ "\x07",
+ "\x08",
+ "\t",
+ "\n",
+ "\x0b",
+ "\x0c",
+ "\r",
+ "\x0e",
+ "\x0f",
+ "\x10",
+ "\x11",
+ "\x12",
+ "\x13",
+ "\x14",
+ "\x15",
+ "\x16",
+ "\x17",
+ "\x18",
+ "\x19",
+ "\x1a",
+ "\x1b",
+ "\x1c",
+ "\x1d",
+ "\x1e",
+ "\x1f",
+ " ",
+ "!",
+ '"',
+ "#",
+ "$",
+ "%",
+ "&",
+ "’",
+ "(",
+ ")",
+ "*",
+ "+",
+ ",",
+ "-",
+ ".",
+ "/",
+ "0",
+ "1",
+ "2",
+ "3",
+ "4",
+ "5",
+ "6",
+ "7",
+ "8",
+ "9",
+ ":",
+ ";",
+ "<",
+ "=",
+ ">",
+ "?",
+ "@",
+ "A",
+ "B",
+ "C",
+ "D",
+ "E",
+ "F",
+ "G",
+ "H",
+ "I",
+ "J",
+ "K",
+ "L",
+ "M",
+ "N",
+ "O",
+ "P",
+ "Q",
+ "R",
+ "S",
+ "T",
+ "U",
+ "V",
+ "W",
+ "X",
+ "Y",
+ "Z",
+ "[",
+ "\\",
+ "]",
+ "^",
+ "_",
+ "‘",
+ "a",
+ "b",
+ "c",
+ "d",
+ "e",
+ "f",
+ "g",
+ "h",
+ "i",
+ "j",
+ "k",
+ "l",
+ "m",
+ "n",
+ "o",
+ "p",
+ "q",
+ "r",
+ "s",
+ "t",
+ "u",
+ "v",
+ "w",
+ "x",
+ "y",
+ "z",
+ "{",
+ "|",
+ "}",
+ "~",
+ "\x7f",
+ "\x80",
+ "\x81",
+ "\x82",
+ "\x83",
+ "\x84",
+ "\x85",
+ "\x86",
+ "\x87",
+ "\x88",
+ "\x89",
+ "\x8a",
+ "\x8b",
+ "\x8c",
+ "\x8d",
+ "\x8e",
+ "\x8f",
+ "\x90",
+ "\x91",
+ "\x92",
+ "\x93",
+ "\x94",
+ "\x95",
+ "\x96",
+ "\x97",
+ "\x98",
+ "\x99",
+ "\x9a",
+ "\x9b",
+ "\x9c",
+ "\x9d",
+ "\x9e",
+ "\x9f",
+ "\xa0",
+ "¡",
+ "¢",
+ "£",
+ "⁄",
+ "¥",
+ "ƒ",
+ "§",
+ "¤",
+ "'",
+ "“",
+ "«",
+ "‹",
+ "›",
+ "fi",
+ "fl",
+ "°",
+ "–",
+ "†",
+ "‡",
+ "·",
+ "µ",
+ "¶",
+ "•",
+ "‚",
+ "„",
+ "”",
+ "»",
+ "…",
+ "‰",
+ "¾",
+ "¿",
+ "À",
+ "`",
+ "´",
+ "ˆ",
+ "˜",
+ "¯",
+ "˘",
+ "˙",
+ "¨",
+ "É",
+ "˚",
+ "¸",
+ "Ì",
+ "˝",
+ "˛",
+ "ˇ",
+ "—",
+ "Ñ",
+ "Ò",
+ "Ó",
+ "Ô",
+ "Õ",
+ "Ö",
+ "×",
+ "Ø",
+ "Ù",
+ "Ú",
+ "Û",
+ "Ü",
+ "Ý",
+ "Þ",
+ "ß",
+ "à",
+ "Æ",
+ "â",
+ "ª",
+ "ä",
+ "å",
+ "æ",
+ "ç",
+ "Ł",
+ "Ø",
+ "Œ",
+ "º",
+ "ì",
+ "í",
+ "î",
+ "ï",
+ "ð",
+ "æ",
+ "ò",
+ "ó",
+ "ô",
+ "ı",
+ "ö",
+ "÷",
+ "ł",
+ "ø",
+ "œ",
+ "ß",
+ "ü",
+ "ý",
+ "þ",
+ "ÿ",
+]
diff --git a/.venv/lib/python3.12/site-packages/pypdf/_codecs/symbol.py b/.venv/lib/python3.12/site-packages/pypdf/_codecs/symbol.py
new file mode 100644
index 00000000..4c0d680f
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/pypdf/_codecs/symbol.py
@@ -0,0 +1,260 @@
+# manually generated from https://www.unicode.org/Public/MAPPINGS/VENDORS/ADOBE/symbol.txt
+_symbol_encoding = [
+ "\u0000",
+ "\u0001",
+ "\u0002",
+ "\u0003",
+ "\u0004",
+ "\u0005",
+ "\u0006",
+ "\u0007",
+ "\u0008",
+ "\u0009",
+ "\u000A",
+ "\u000B",
+ "\u000C",
+ "\u000D",
+ "\u000E",
+ "\u000F",
+ "\u0010",
+ "\u0011",
+ "\u0012",
+ "\u0013",
+ "\u0014",
+ "\u0015",
+ "\u0016",
+ "\u0017",
+ "\u0018",
+ "\u0019",
+ "\u001A",
+ "\u001B",
+ "\u001C",
+ "\u001D",
+ "\u001E",
+ "\u001F",
+ "\u0020",
+ "\u0021",
+ "\u2200",
+ "\u0023",
+ "\u2203",
+ "\u0025",
+ "\u0026",
+ "\u220B",
+ "\u0028",
+ "\u0029",
+ "\u2217",
+ "\u002B",
+ "\u002C",
+ "\u2212",
+ "\u002E",
+ "\u002F",
+ "\u0030",
+ "\u0031",
+ "\u0032",
+ "\u0033",
+ "\u0034",
+ "\u0035",
+ "\u0036",
+ "\u0037",
+ "\u0038",
+ "\u0039",
+ "\u003A",
+ "\u003B",
+ "\u003C",
+ "\u003D",
+ "\u003E",
+ "\u003F",
+ "\u2245",
+ "\u0391",
+ "\u0392",
+ "\u03A7",
+ "\u0394",
+ "\u0395",
+ "\u03A6",
+ "\u0393",
+ "\u0397",
+ "\u0399",
+ "\u03D1",
+ "\u039A",
+ "\u039B",
+ "\u039C",
+ "\u039D",
+ "\u039F",
+ "\u03A0",
+ "\u0398",
+ "\u03A1",
+ "\u03A3",
+ "\u03A4",
+ "\u03A5",
+ "\u03C2",
+ "\u03A9",
+ "\u039E",
+ "\u03A8",
+ "\u0396",
+ "\u005B",
+ "\u2234",
+ "\u005D",
+ "\u22A5",
+ "\u005F",
+ "\uF8E5",
+ "\u03B1",
+ "\u03B2",
+ "\u03C7",
+ "\u03B4",
+ "\u03B5",
+ "\u03C6",
+ "\u03B3",
+ "\u03B7",
+ "\u03B9",
+ "\u03D5",
+ "\u03BA",
+ "\u03BB",
+ "\u00B5",
+ "\u03BD",
+ "\u03BF",
+ "\u03C0",
+ "\u03B8",
+ "\u03C1",
+ "\u03C3",
+ "\u03C4",
+ "\u03C5",
+ "\u03D6",
+ "\u03C9",
+ "\u03BE",
+ "\u03C8",
+ "\u03B6",
+ "\u007B",
+ "\u007C",
+ "\u007D",
+ "\u223C",
+ "\u007F",
+ "\u0080",
+ "\u0081",
+ "\u0082",
+ "\u0083",
+ "\u0084",
+ "\u0085",
+ "\u0086",
+ "\u0087",
+ "\u0088",
+ "\u0089",
+ "\u008A",
+ "\u008B",
+ "\u008C",
+ "\u008D",
+ "\u008E",
+ "\u008F",
+ "\u0090",
+ "\u0091",
+ "\u0092",
+ "\u0093",
+ "\u0094",
+ "\u0095",
+ "\u0096",
+ "\u0097",
+ "\u0098",
+ "\u0099",
+ "\u009A",
+ "\u009B",
+ "\u009C",
+ "\u009D",
+ "\u009E",
+ "\u009F",
+ "\u20AC",
+ "\u03D2",
+ "\u2032",
+ "\u2264",
+ "\u2044",
+ "\u221E",
+ "\u0192",
+ "\u2663",
+ "\u2666",
+ "\u2665",
+ "\u2660",
+ "\u2194",
+ "\u2190",
+ "\u2191",
+ "\u2192",
+ "\u2193",
+ "\u00B0",
+ "\u00B1",
+ "\u2033",
+ "\u2265",
+ "\u00D7",
+ "\u221D",
+ "\u2202",
+ "\u2022",
+ "\u00F7",
+ "\u2260",
+ "\u2261",
+ "\u2248",
+ "\u2026",
+ "\uF8E6",
+ "\uF8E7",
+ "\u21B5",
+ "\u2135",
+ "\u2111",
+ "\u211C",
+ "\u2118",
+ "\u2297",
+ "\u2295",
+ "\u2205",
+ "\u2229",
+ "\u222A",
+ "\u2283",
+ "\u2287",
+ "\u2284",
+ "\u2282",
+ "\u2286",
+ "\u2208",
+ "\u2209",
+ "\u2220",
+ "\u2207",
+ "\uF6DA",
+ "\uF6D9",
+ "\uF6DB",
+ "\u220F",
+ "\u221A",
+ "\u22C5",
+ "\u00AC",
+ "\u2227",
+ "\u2228",
+ "\u21D4",
+ "\u21D0",
+ "\u21D1",
+ "\u21D2",
+ "\u21D3",
+ "\u25CA",
+ "\u2329",
+ "\uF8E8",
+ "\uF8E9",
+ "\uF8EA",
+ "\u2211",
+ "\uF8EB",
+ "\uF8EC",
+ "\uF8ED",
+ "\uF8EE",
+ "\uF8EF",
+ "\uF8F0",
+ "\uF8F1",
+ "\uF8F2",
+ "\uF8F3",
+ "\uF8F4",
+ "\u00F0",
+ "\u232A",
+ "\u222B",
+ "\u2320",
+ "\uF8F5",
+ "\u2321",
+ "\uF8F6",
+ "\uF8F7",
+ "\uF8F8",
+ "\uF8F9",
+ "\uF8FA",
+ "\uF8FB",
+ "\uF8FC",
+ "\uF8FD",
+ "\uF8FE",
+ "\u00FF",
+]
+assert len(_symbol_encoding) == 256
diff --git a/.venv/lib/python3.12/site-packages/pypdf/_codecs/zapfding.py b/.venv/lib/python3.12/site-packages/pypdf/_codecs/zapfding.py
new file mode 100644
index 00000000..9b6cdbcc
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/pypdf/_codecs/zapfding.py
@@ -0,0 +1,261 @@
+# manually generated from https://www.unicode.org/Public/MAPPINGS/VENDORS/ADOBE/zdingbat.txt
+
+_zapfding_encoding = [
+ "\u0000",
+ "\u0001",
+ "\u0002",
+ "\u0003",
+ "\u0004",
+ "\u0005",
+ "\u0006",
+ "\u0007",
+ "\u0008",
+ "\u0009",
+ "\u000A",
+ "\u000B",
+ "\u000C",
+ "\u000D",
+ "\u000E",
+ "\u000F",
+ "\u0010",
+ "\u0011",
+ "\u0012",
+ "\u0013",
+ "\u0014",
+ "\u0015",
+ "\u0016",
+ "\u0017",
+ "\u0018",
+ "\u0019",
+ "\u001A",
+ "\u001B",
+ "\u001C",
+ "\u001D",
+ "\u001E",
+ "\u001F",
+ "\u0020",
+ "\u2701",
+ "\u2702",
+ "\u2703",
+ "\u2704",
+ "\u260E",
+ "\u2706",
+ "\u2707",
+ "\u2708",
+ "\u2709",
+ "\u261B",
+ "\u261E",
+ "\u270C",
+ "\u270D",
+ "\u270E",
+ "\u270F",
+ "\u2710",
+ "\u2711",
+ "\u2712",
+ "\u2713",
+ "\u2714",
+ "\u2715",
+ "\u2716",
+ "\u2717",
+ "\u2718",
+ "\u2719",
+ "\u271A",
+ "\u271B",
+ "\u271C",
+ "\u271D",
+ "\u271E",
+ "\u271F",
+ "\u2720",
+ "\u2721",
+ "\u2722",
+ "\u2723",
+ "\u2724",
+ "\u2725",
+ "\u2726",
+ "\u2727",
+ "\u2605",
+ "\u2729",
+ "\u272A",
+ "\u272B",
+ "\u272C",
+ "\u272D",
+ "\u272E",
+ "\u272F",
+ "\u2730",
+ "\u2731",
+ "\u2732",
+ "\u2733",
+ "\u2734",
+ "\u2735",
+ "\u2736",
+ "\u2737",
+ "\u2738",
+ "\u2739",
+ "\u273A",
+ "\u273B",
+ "\u273C",
+ "\u273D",
+ "\u273E",
+ "\u273F",
+ "\u2740",
+ "\u2741",
+ "\u2742",
+ "\u2743",
+ "\u2744",
+ "\u2745",
+ "\u2746",
+ "\u2747",
+ "\u2748",
+ "\u2749",
+ "\u274A",
+ "\u274B",
+ "\u25CF",
+ "\u274D",
+ "\u25A0",
+ "\u274F",
+ "\u2750",
+ "\u2751",
+ "\u2752",
+ "\u25B2",
+ "\u25BC",
+ "\u25C6",
+ "\u2756",
+ "\u25D7",
+ "\u2758",
+ "\u2759",
+ "\u275A",
+ "\u275B",
+ "\u275C",
+ "\u275D",
+ "\u275E",
+ "\u007F",
+ "\uF8D7",
+ "\uF8D8",
+ "\uF8D9",
+ "\uF8DA",
+ "\uF8DB",
+ "\uF8DC",
+ "\uF8DD",
+ "\uF8DE",
+ "\uF8DF",
+ "\uF8E0",
+ "\uF8E1",
+ "\uF8E2",
+ "\uF8E3",
+ "\uF8E4",
+ "\u008E",
+ "\u008F",
+ "\u0090",
+ "\u0091",
+ "\u0092",
+ "\u0093",
+ "\u0094",
+ "\u0095",
+ "\u0096",
+ "\u0097",
+ "\u0098",
+ "\u0099",
+ "\u009A",
+ "\u009B",
+ "\u009C",
+ "\u009D",
+ "\u009E",
+ "\u009F",
+ "\u00A0",
+ "\u2761",
+ "\u2762",
+ "\u2763",
+ "\u2764",
+ "\u2765",
+ "\u2766",
+ "\u2767",
+ "\u2663",
+ "\u2666",
+ "\u2665",
+ "\u2660",
+ "\u2460",
+ "\u2461",
+ "\u2462",
+ "\u2463",
+ "\u2464",
+ "\u2465",
+ "\u2466",
+ "\u2467",
+ "\u2468",
+ "\u2469",
+ "\u2776",
+ "\u2777",
+ "\u2778",
+ "\u2779",
+ "\u277A",
+ "\u277B",
+ "\u277C",
+ "\u277D",
+ "\u277E",
+ "\u277F",
+ "\u2780",
+ "\u2781",
+ "\u2782",
+ "\u2783",
+ "\u2784",
+ "\u2785",
+ "\u2786",
+ "\u2787",
+ "\u2788",
+ "\u2789",
+ "\u278A",
+ "\u278B",
+ "\u278C",
+ "\u278D",
+ "\u278E",
+ "\u278F",
+ "\u2790",
+ "\u2791",
+ "\u2792",
+ "\u2793",
+ "\u2794",
+ "\u2192",
+ "\u2194",
+ "\u2195",
+ "\u2798",
+ "\u2799",
+ "\u279A",
+ "\u279B",
+ "\u279C",
+ "\u279D",
+ "\u279E",
+ "\u279F",
+ "\u27A0",
+ "\u27A1",
+ "\u27A2",
+ "\u27A3",
+ "\u27A4",
+ "\u27A5",
+ "\u27A6",
+ "\u27A7",
+ "\u27A8",
+ "\u27A9",
+ "\u27AA",
+ "\u27AB",
+ "\u27AC",
+ "\u27AD",
+ "\u27AE",
+ "\u27AF",
+ "\u00F0",
+ "\u27B1",
+ "\u27B2",
+ "\u27B3",
+ "\u27B4",
+ "\u27B5",
+ "\u27B6",
+ "\u27B7",
+ "\u27B8",
+ "\u27B9",
+ "\u27BA",
+ "\u27BB",
+ "\u27BC",
+ "\u27BD",
+ "\u27BE",
+ "\u00FF",
+]
+assert len(_zapfding_encoding) == 256
diff --git a/.venv/lib/python3.12/site-packages/pypdf/_crypt_providers/__init__.py b/.venv/lib/python3.12/site-packages/pypdf/_crypt_providers/__init__.py
new file mode 100644
index 00000000..49d41128
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/pypdf/_crypt_providers/__init__.py
@@ -0,0 +1,86 @@
+# Copyright (c) 2023, exiledkingcc
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
+#
+# * Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
+# * Redistributions in binary form must reproduce the above copyright notice,
+# this list of conditions and the following disclaimer in the documentation
+# and/or other materials provided with the distribution.
+# * The name of the author may not be used to endorse or promote products
+# derived from this software without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+# POSSIBILITY OF SUCH DAMAGE.
+
+from pypdf._crypt_providers._base import CryptBase, CryptIdentity
+
+try:
+ from pypdf._crypt_providers._cryptography import (
+ CryptAES,
+ CryptRC4,
+ aes_cbc_decrypt,
+ aes_cbc_encrypt,
+ aes_ecb_decrypt,
+ aes_ecb_encrypt,
+ crypt_provider,
+ rc4_decrypt,
+ rc4_encrypt,
+ )
+ from pypdf._utils import Version
+
+ if Version(crypt_provider[1]) <= Version("3.0"):
+ # This is due to the backend parameter being required back then:
+ # https://cryptography.io/en/latest/changelog/#v3-1
+ raise ImportError("cryptography<=3.0 is not supported") # pragma: no cover
+except ImportError:
+ try:
+ from pypdf._crypt_providers._pycryptodome import ( # type: ignore
+ CryptAES,
+ CryptRC4,
+ aes_cbc_decrypt,
+ aes_cbc_encrypt,
+ aes_ecb_decrypt,
+ aes_ecb_encrypt,
+ crypt_provider,
+ rc4_decrypt,
+ rc4_encrypt,
+ )
+ except ImportError:
+ from pypdf._crypt_providers._fallback import ( # type: ignore
+ CryptAES,
+ CryptRC4,
+ aes_cbc_decrypt,
+ aes_cbc_encrypt,
+ aes_ecb_decrypt,
+ aes_ecb_encrypt,
+ crypt_provider,
+ rc4_decrypt,
+ rc4_encrypt,
+ )
+
+__all__ = [
+ "crypt_provider",
+ "CryptBase",
+ "CryptIdentity",
+ "CryptRC4",
+ "CryptAES",
+ "rc4_encrypt",
+ "rc4_decrypt",
+ "aes_ecb_encrypt",
+ "aes_ecb_decrypt",
+ "aes_cbc_encrypt",
+ "aes_cbc_decrypt",
+]
diff --git a/.venv/lib/python3.12/site-packages/pypdf/_crypt_providers/_base.py b/.venv/lib/python3.12/site-packages/pypdf/_crypt_providers/_base.py
new file mode 100644
index 00000000..894025f3
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/pypdf/_crypt_providers/_base.py
@@ -0,0 +1,38 @@
+# Copyright (c) 2023, exiledkingcc
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
+#
+# * Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
+# * Redistributions in binary form must reproduce the above copyright notice,
+# this list of conditions and the following disclaimer in the documentation
+# and/or other materials provided with the distribution.
+# * The name of the author may not be used to endorse or promote products
+# derived from this software without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+# POSSIBILITY OF SUCH DAMAGE.
+
+
+class CryptBase:
+ def encrypt(self, data: bytes) -> bytes: # pragma: no cover
+ return data
+
+ def decrypt(self, data: bytes) -> bytes: # pragma: no cover
+ return data
+
+
+class CryptIdentity(CryptBase):
+ pass
diff --git a/.venv/lib/python3.12/site-packages/pypdf/_crypt_providers/_cryptography.py b/.venv/lib/python3.12/site-packages/pypdf/_crypt_providers/_cryptography.py
new file mode 100644
index 00000000..f5537612
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/pypdf/_crypt_providers/_cryptography.py
@@ -0,0 +1,118 @@
+# Copyright (c) 2023, exiledkingcc
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
+#
+# * Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
+# * Redistributions in binary form must reproduce the above copyright notice,
+# this list of conditions and the following disclaimer in the documentation
+# and/or other materials provided with the distribution.
+# * The name of the author may not be used to endorse or promote products
+# derived from this software without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+# POSSIBILITY OF SUCH DAMAGE.
+
+import secrets
+
+from cryptography import __version__
+from cryptography.hazmat.primitives import padding
+from cryptography.hazmat.primitives.ciphers.algorithms import AES
+
+try:
+ # 43.0.0 - https://cryptography.io/en/latest/changelog/#v43-0-0
+ from cryptography.hazmat.decrepit.ciphers.algorithms import ARC4
+except ImportError:
+ from cryptography.hazmat.primitives.ciphers.algorithms import ARC4
+from cryptography.hazmat.primitives.ciphers.base import Cipher
+from cryptography.hazmat.primitives.ciphers.modes import CBC, ECB
+
+from pypdf._crypt_providers._base import CryptBase
+
+crypt_provider = ("cryptography", __version__)
+
+
+class CryptRC4(CryptBase):
+ def __init__(self, key: bytes) -> None:
+ self.cipher = Cipher(ARC4(key), mode=None)
+
+ def encrypt(self, data: bytes) -> bytes:
+ encryptor = self.cipher.encryptor()
+ return encryptor.update(data) + encryptor.finalize()
+
+ def decrypt(self, data: bytes) -> bytes:
+ decryptor = self.cipher.decryptor()
+ return decryptor.update(data) + decryptor.finalize()
+
+
+class CryptAES(CryptBase):
+ def __init__(self, key: bytes) -> None:
+ self.alg = AES(key)
+
+ def encrypt(self, data: bytes) -> bytes:
+ iv = secrets.token_bytes(16)
+ pad = padding.PKCS7(128).padder()
+ data = pad.update(data) + pad.finalize()
+
+ cipher = Cipher(self.alg, CBC(iv))
+ encryptor = cipher.encryptor()
+ return iv + encryptor.update(data) + encryptor.finalize()
+
+ def decrypt(self, data: bytes) -> bytes:
+ iv = data[:16]
+ data = data[16:]
+ # for empty encrypted data
+ if not data:
+ return data
+
+ # just for robustness, it does not happen under normal circumstances
+ if len(data) % 16 != 0:
+ pad = padding.PKCS7(128).padder()
+ data = pad.update(data) + pad.finalize()
+
+ cipher = Cipher(self.alg, CBC(iv))
+ decryptor = cipher.decryptor()
+ d = decryptor.update(data) + decryptor.finalize()
+ return d[: -d[-1]]
+
+
+def rc4_encrypt(key: bytes, data: bytes) -> bytes:
+ encryptor = Cipher(ARC4(key), mode=None).encryptor()
+ return encryptor.update(data) + encryptor.finalize()
+
+
+def rc4_decrypt(key: bytes, data: bytes) -> bytes:
+ decryptor = Cipher(ARC4(key), mode=None).decryptor()
+ return decryptor.update(data) + decryptor.finalize()
+
+
+def aes_ecb_encrypt(key: bytes, data: bytes) -> bytes:
+ encryptor = Cipher(AES(key), mode=ECB()).encryptor()
+ return encryptor.update(data) + encryptor.finalize()
+
+
+def aes_ecb_decrypt(key: bytes, data: bytes) -> bytes:
+ decryptor = Cipher(AES(key), mode=ECB()).decryptor()
+ return decryptor.update(data) + decryptor.finalize()
+
+
+def aes_cbc_encrypt(key: bytes, iv: bytes, data: bytes) -> bytes:
+ encryptor = Cipher(AES(key), mode=CBC(iv)).encryptor()
+ return encryptor.update(data) + encryptor.finalize()
+
+
+def aes_cbc_decrypt(key: bytes, iv: bytes, data: bytes) -> bytes:
+ decryptor = Cipher(AES(key), mode=CBC(iv)).decryptor()
+ return decryptor.update(data) + decryptor.finalize()
diff --git a/.venv/lib/python3.12/site-packages/pypdf/_crypt_providers/_fallback.py b/.venv/lib/python3.12/site-packages/pypdf/_crypt_providers/_fallback.py
new file mode 100644
index 00000000..631fec19
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/pypdf/_crypt_providers/_fallback.py
@@ -0,0 +1,93 @@
+# Copyright (c) 2023, exiledkingcc
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
+#
+# * Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
+# * Redistributions in binary form must reproduce the above copyright notice,
+# this list of conditions and the following disclaimer in the documentation
+# and/or other materials provided with the distribution.
+# * The name of the author may not be used to endorse or promote products
+# derived from this software without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+# POSSIBILITY OF SUCH DAMAGE.
+
+from pypdf._crypt_providers._base import CryptBase
+from pypdf.errors import DependencyError
+
+_DEPENDENCY_ERROR_STR = "cryptography>=3.1 is required for AES algorithm"
+
+
+crypt_provider = ("local_crypt_fallback", "0.0.0")
+
+
+class CryptRC4(CryptBase):
+ def __init__(self, key: bytes) -> None:
+ self.s = bytearray(range(256))
+ j = 0
+ for i in range(256):
+ j = (j + self.s[i] + key[i % len(key)]) % 256
+ self.s[i], self.s[j] = self.s[j], self.s[i]
+
+ def encrypt(self, data: bytes) -> bytes:
+ s = bytearray(self.s)
+ out = [0 for _ in range(len(data))]
+ i, j = 0, 0
+ for k in range(len(data)):
+ i = (i + 1) % 256
+ j = (j + s[i]) % 256
+ s[i], s[j] = s[j], s[i]
+ x = s[(s[i] + s[j]) % 256]
+ out[k] = data[k] ^ x
+ return bytes(bytearray(out))
+
+ def decrypt(self, data: bytes) -> bytes:
+ return self.encrypt(data)
+
+
+class CryptAES(CryptBase):
+ def __init__(self, key: bytes) -> None:
+ pass
+
+ def encrypt(self, data: bytes) -> bytes:
+ raise DependencyError(_DEPENDENCY_ERROR_STR)
+
+ def decrypt(self, data: bytes) -> bytes:
+ raise DependencyError(_DEPENDENCY_ERROR_STR)
+
+
+def rc4_encrypt(key: bytes, data: bytes) -> bytes:
+ return CryptRC4(key).encrypt(data)
+
+
+def rc4_decrypt(key: bytes, data: bytes) -> bytes:
+ return CryptRC4(key).decrypt(data)
+
+
+def aes_ecb_encrypt(key: bytes, data: bytes) -> bytes:
+ raise DependencyError(_DEPENDENCY_ERROR_STR)
+
+
+def aes_ecb_decrypt(key: bytes, data: bytes) -> bytes:
+ raise DependencyError(_DEPENDENCY_ERROR_STR)
+
+
+def aes_cbc_encrypt(key: bytes, iv: bytes, data: bytes) -> bytes:
+ raise DependencyError(_DEPENDENCY_ERROR_STR)
+
+
+def aes_cbc_decrypt(key: bytes, iv: bytes, data: bytes) -> bytes:
+ raise DependencyError(_DEPENDENCY_ERROR_STR)
diff --git a/.venv/lib/python3.12/site-packages/pypdf/_crypt_providers/_pycryptodome.py b/.venv/lib/python3.12/site-packages/pypdf/_crypt_providers/_pycryptodome.py
new file mode 100644
index 00000000..30a13e18
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/pypdf/_crypt_providers/_pycryptodome.py
@@ -0,0 +1,97 @@
+# Copyright (c) 2023, exiledkingcc
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
+#
+# * Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
+# * Redistributions in binary form must reproduce the above copyright notice,
+# this list of conditions and the following disclaimer in the documentation
+# and/or other materials provided with the distribution.
+# * The name of the author may not be used to endorse or promote products
+# derived from this software without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+# POSSIBILITY OF SUCH DAMAGE.
+
+import secrets
+
+from Crypto import __version__
+from Crypto.Cipher import AES, ARC4
+from Crypto.Util.Padding import pad
+
+from pypdf._crypt_providers._base import CryptBase
+
+crypt_provider = ("pycryptodome", __version__)
+
+
+class CryptRC4(CryptBase):
+ def __init__(self, key: bytes) -> None:
+ self.key = key
+
+ def encrypt(self, data: bytes) -> bytes:
+ return ARC4.ARC4Cipher(self.key).encrypt(data)
+
+ def decrypt(self, data: bytes) -> bytes:
+ return ARC4.ARC4Cipher(self.key).decrypt(data)
+
+
+class CryptAES(CryptBase):
+ def __init__(self, key: bytes) -> None:
+ self.key = key
+
+ def encrypt(self, data: bytes) -> bytes:
+ iv = secrets.token_bytes(16)
+ data = pad(data, 16)
+ aes = AES.new(self.key, AES.MODE_CBC, iv)
+ return iv + aes.encrypt(data)
+
+ def decrypt(self, data: bytes) -> bytes:
+ iv = data[:16]
+ data = data[16:]
+ # for empty encrypted data
+ if not data:
+ return data
+
+ # just for robustness, it does not happen under normal circumstances
+ if len(data) % 16 != 0:
+ data = pad(data, 16)
+
+ aes = AES.new(self.key, AES.MODE_CBC, iv)
+ d = aes.decrypt(data)
+ return d[: -d[-1]]
+
+
+def rc4_encrypt(key: bytes, data: bytes) -> bytes:
+ return ARC4.ARC4Cipher(key).encrypt(data)
+
+
+def rc4_decrypt(key: bytes, data: bytes) -> bytes:
+ return ARC4.ARC4Cipher(key).decrypt(data)
+
+
+def aes_ecb_encrypt(key: bytes, data: bytes) -> bytes:
+ return AES.new(key, AES.MODE_ECB).encrypt(data)
+
+
+def aes_ecb_decrypt(key: bytes, data: bytes) -> bytes:
+ return AES.new(key, AES.MODE_ECB).decrypt(data)
+
+
+def aes_cbc_encrypt(key: bytes, iv: bytes, data: bytes) -> bytes:
+ return AES.new(key, AES.MODE_CBC, iv).encrypt(data)
+
+
+def aes_cbc_decrypt(key: bytes, iv: bytes, data: bytes) -> bytes:
+ return AES.new(key, AES.MODE_CBC, iv).decrypt(data)
diff --git a/.venv/lib/python3.12/site-packages/pypdf/_doc_common.py b/.venv/lib/python3.12/site-packages/pypdf/_doc_common.py
new file mode 100644
index 00000000..d4c5c43c
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/pypdf/_doc_common.py
@@ -0,0 +1,1365 @@
+# Copyright (c) 2006, Mathieu Fenniak
+# Copyright (c) 2007, Ashish Kulkarni <kulkarni.ashish@gmail.com>
+# Copyright (c) 2024, Pubpub-ZZ
+#
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
+#
+# * Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
+# * Redistributions in binary form must reproduce the above copyright notice,
+# this list of conditions and the following disclaimer in the documentation
+# and/or other materials provided with the distribution.
+# * The name of the author may not be used to endorse or promote products
+# derived from this software without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+# POSSIBILITY OF SUCH DAMAGE.
+
+import struct
+import zlib
+from abc import abstractmethod
+from datetime import datetime
+from typing import (
+ Any,
+ Dict,
+ Iterable,
+ Iterator,
+ List,
+ Mapping,
+ Optional,
+ Tuple,
+ Union,
+ cast,
+)
+
+from ._encryption import Encryption
+from ._page import PageObject, _VirtualList
+from ._page_labels import index2label as page_index2page_label
+from ._utils import (
+ b_,
+ deprecate_with_replacement,
+ logger_warning,
+ parse_iso8824_date,
+)
+from .constants import CatalogAttributes as CA
+from .constants import CatalogDictionary as CD
+from .constants import (
+ CheckboxRadioButtonAttributes,
+ GoToActionArguments,
+ UserAccessPermissions,
+)
+from .constants import Core as CO
+from .constants import DocumentInformationAttributes as DI
+from .constants import FieldDictionaryAttributes as FA
+from .constants import PageAttributes as PG
+from .constants import PagesAttributes as PA
+from .errors import (
+ PdfReadError,
+)
+from .generic import (
+ ArrayObject,
+ BooleanObject,
+ ByteStringObject,
+ Destination,
+ DictionaryObject,
+ EncodedStreamObject,
+ Field,
+ Fit,
+ FloatObject,
+ IndirectObject,
+ NameObject,
+ NullObject,
+ NumberObject,
+ PdfObject,
+ TextStringObject,
+ TreeObject,
+ ViewerPreferences,
+ create_string_object,
+)
+from .types import OutlineType, PagemodeType
+from .xmp import XmpInformation
+
+
+def convert_to_int(d: bytes, size: int) -> Union[int, Tuple[Any, ...]]:
+ if size > 8:
+ raise PdfReadError("invalid size in convert_to_int")
+ d = b"\x00\x00\x00\x00\x00\x00\x00\x00" + d
+ d = d[-8:]
+ return struct.unpack(">q", d)[0]
+
+
+class DocumentInformation(DictionaryObject):
+ """
+ A class representing the basic document metadata provided in a PDF File.
+ This class is accessible through
+ :py:class:`PdfReader.metadata<pypdf.PdfReader.metadata>`.
+
+ All text properties of the document metadata have
+ *two* properties, e.g. author and author_raw. The non-raw property will
+ always return a ``TextStringObject``, making it ideal for a case where the
+ metadata is being displayed. The raw property can sometimes return a
+ ``ByteStringObject``, if pypdf was unable to decode the string's text
+ encoding; this requires additional safety in the caller and therefore is not
+ as commonly accessed.
+ """
+
+ def __init__(self) -> None:
+ DictionaryObject.__init__(self)
+
+ def _get_text(self, key: str) -> Optional[str]:
+ retval = self.get(key, None)
+ if isinstance(retval, TextStringObject):
+ return retval
+ return None
+
+ @property
+ def title(self) -> Optional[str]:
+ """
+ Read-only property accessing the document's title.
+
+ Returns a ``TextStringObject`` or ``None`` if the title is not
+ specified.
+ """
+ return (
+ self._get_text(DI.TITLE) or self.get(DI.TITLE).get_object() # type: ignore
+ if self.get(DI.TITLE)
+ else None
+ )
+
+ @property
+ def title_raw(self) -> Optional[str]:
+ """The "raw" version of title; can return a ``ByteStringObject``."""
+ return self.get(DI.TITLE)
+
+ @property
+ def author(self) -> Optional[str]:
+ """
+ Read-only property accessing the document's author.
+
+ Returns a ``TextStringObject`` or ``None`` if the author is not
+ specified.
+ """
+ return self._get_text(DI.AUTHOR)
+
+ @property
+ def author_raw(self) -> Optional[str]:
+ """The "raw" version of author; can return a ``ByteStringObject``."""
+ return self.get(DI.AUTHOR)
+
+ @property
+ def subject(self) -> Optional[str]:
+ """
+ Read-only property accessing the document's subject.
+
+ Returns a ``TextStringObject`` or ``None`` if the subject is not
+ specified.
+ """
+ return self._get_text(DI.SUBJECT)
+
+ @property
+ def subject_raw(self) -> Optional[str]:
+ """The "raw" version of subject; can return a ``ByteStringObject``."""
+ return self.get(DI.SUBJECT)
+
+ @property
+ def creator(self) -> Optional[str]:
+ """
+ Read-only property accessing the document's creator.
+
+ If the document was converted to PDF from another format, this is the
+ name of the application (e.g. OpenOffice) that created the original
+ document from which it was converted. Returns a ``TextStringObject`` or
+ ``None`` if the creator is not specified.
+ """
+ return self._get_text(DI.CREATOR)
+
+ @property
+ def creator_raw(self) -> Optional[str]:
+ """The "raw" version of creator; can return a ``ByteStringObject``."""
+ return self.get(DI.CREATOR)
+
+ @property
+ def producer(self) -> Optional[str]:
+ """
+ Read-only property accessing the document's producer.
+
+ If the document was converted to PDF from another format, this is the
+ name of the application (for example, macOS Quartz) that converted it to
+ PDF. Returns a ``TextStringObject`` or ``None`` if the producer is not
+ specified.
+ """
+ return self._get_text(DI.PRODUCER)
+
+ @property
+ def producer_raw(self) -> Optional[str]:
+ """The "raw" version of producer; can return a ``ByteStringObject``."""
+ return self.get(DI.PRODUCER)
+
+ @property
+ def creation_date(self) -> Optional[datetime]:
+ """Read-only property accessing the document's creation date."""
+ return parse_iso8824_date(self._get_text(DI.CREATION_DATE))
+
+ @property
+ def creation_date_raw(self) -> Optional[str]:
+ """
+ The "raw" version of creation date; can return a ``ByteStringObject``.
+
+ Typically in the format ``D:YYYYMMDDhhmmss[+Z-]hh'mm`` where the suffix
+ is the offset from UTC.
+ """
+ return self.get(DI.CREATION_DATE)
+
+ @property
+ def modification_date(self) -> Optional[datetime]:
+ """
+ Read-only property accessing the document's modification date.
+
+ The date and time the document was most recently modified.
+ """
+ return parse_iso8824_date(self._get_text(DI.MOD_DATE))
+
+ @property
+ def modification_date_raw(self) -> Optional[str]:
+ """
+ The "raw" version of modification date; can return a
+ ``ByteStringObject``.
+
+ Typically in the format ``D:YYYYMMDDhhmmss[+Z-]hh'mm`` where the suffix
+ is the offset from UTC.
+ """
+ return self.get(DI.MOD_DATE)
+
+
+class PdfDocCommon:
+ """
+ Common functions from PdfWriter and PdfReader objects.
+
+ This root class is strongly abstracted.
+ """
+
+ strict: bool = False # default
+
+ _encryption: Optional[Encryption] = None
+
+ @property
+ @abstractmethod
+ def root_object(self) -> DictionaryObject:
+ ... # pragma: no cover
+
+ @property
+ @abstractmethod
+ def pdf_header(self) -> str:
+ ... # pragma: no cover
+
+ @abstractmethod
+ def get_object(
+ self, indirect_reference: Union[int, IndirectObject]
+ ) -> Optional[PdfObject]:
+ ... # pragma: no cover
+
+ @abstractmethod
+ def _replace_object(self, indirect: IndirectObject, obj: PdfObject) -> PdfObject:
+ ... # pragma: no cover
+
+ @property
+ @abstractmethod
+ def _info(self) -> Optional[DictionaryObject]:
+ ... # pragma: no cover
+
+ @property
+ def metadata(self) -> Optional[DocumentInformation]:
+ """
+ Retrieve the PDF file's document information dictionary, if it exists.
+
+ Note that some PDF files use metadata streams instead of document
+ information dictionaries, and these metadata streams will not be
+ accessed by this function.
+ """
+ retval = DocumentInformation()
+ if self._info is None:
+ return None
+ retval.update(self._info)
+ return retval
+
+ @property
+ def xmp_metadata(self) -> Optional[XmpInformation]:
+ ... # pragma: no cover
+
+ @abstractmethod
+ def _repr_mimebundle_(
+ self,
+ include: Union[None, Iterable[str]] = None,
+ exclude: Union[None, Iterable[str]] = None,
+ ) -> Dict[str, Any]:
+ """
+ Integration into Jupyter Notebooks.
+
+ This method returns a dictionary that maps a mime-type to its
+ representation.
+
+ See https://ipython.readthedocs.io/en/stable/config/integrating.html
+ """
+ ... # pragma: no cover
+
+ @property
+ def viewer_preferences(self) -> Optional[ViewerPreferences]:
+ """Returns the existing ViewerPreferences as an overloaded dictionary."""
+ o = self.root_object.get(CD.VIEWER_PREFERENCES, None)
+ if o is None:
+ return None
+ o = o.get_object()
+ if not isinstance(o, ViewerPreferences):
+ o = ViewerPreferences(o)
+ if hasattr(o, "indirect_reference"):
+ self._replace_object(o.indirect_reference, o)
+ else:
+ self.root_object[NameObject(CD.VIEWER_PREFERENCES)] = o
+ return o
+
+ flattened_pages: Optional[List[PageObject]] = None
+
+ def get_num_pages(self) -> int:
+ """
+ Calculate the number of pages in this PDF file.
+
+ Returns:
+ The number of pages of the parsed PDF file.
+
+ Raises:
+ PdfReadError: if file is encrypted and restrictions prevent
+ this action.
+ """
+ # Flattened pages will not work on an encrypted PDF;
+ # the PDF file's page count is used in this case. Otherwise,
+ # the original method (flattened page count) is used.
+ if self.is_encrypted:
+ return self.root_object["/Pages"]["/Count"] # type: ignore
+ else:
+ if self.flattened_pages is None:
+ self._flatten()
+ assert self.flattened_pages is not None
+ return len(self.flattened_pages)
+
+ def get_page(self, page_number: int) -> PageObject:
+ """
+ Retrieve a page by number from this PDF file.
+ Most of the time ``.pages[page_number]`` is preferred.
+
+ Args:
+ page_number: The page number to retrieve
+ (pages begin at zero)
+
+ Returns:
+ A :class:`PageObject<pypdf._page.PageObject>` instance.
+ """
+ if self.flattened_pages is None:
+ self._flatten()
+ assert self.flattened_pages is not None, "hint for mypy"
+ return self.flattened_pages[page_number]
+
+ @property
+ def named_destinations(self) -> Dict[str, Any]:
+ """
+ A read-only dictionary which maps names to
+ :class:`Destinations<pypdf.generic.Destination>`
+ """
+ return self._get_named_destinations()
+
+ def get_named_dest_root(self) -> ArrayObject:
+ named_dest = ArrayObject()
+ if CA.NAMES in self.root_object and isinstance(
+ self.root_object[CA.NAMES], DictionaryObject
+ ):
+ names = cast(DictionaryObject, self.root_object[CA.NAMES])
+ names_ref = names.indirect_reference
+ if CA.DESTS in names and isinstance(names[CA.DESTS], DictionaryObject):
+ # 3.6.3 Name Dictionary (PDF spec 1.7)
+ dests = cast(DictionaryObject, names[CA.DESTS])
+ dests_ref = dests.indirect_reference
+ if CA.NAMES in dests:
+ # §7.9.6, entries in a name tree node dictionary
+ named_dest = cast(ArrayObject, dests[CA.NAMES])
+ else:
+ named_dest = ArrayObject()
+ dests[NameObject(CA.NAMES)] = named_dest
+ elif hasattr(self, "_add_object"):
+ dests = DictionaryObject()
+ dests_ref = self._add_object(dests)
+ names[NameObject(CA.DESTS)] = dests_ref
+ dests[NameObject(CA.NAMES)] = named_dest
+
+ elif hasattr(self, "_add_object"):
+ names = DictionaryObject()
+ names_ref = self._add_object(names)
+ self.root_object[NameObject(CA.NAMES)] = names_ref
+ dests = DictionaryObject()
+ dests_ref = self._add_object(dests)
+ names[NameObject(CA.DESTS)] = dests_ref
+ dests[NameObject(CA.NAMES)] = named_dest
+
+ return named_dest
+
+ ## common
+ def _get_named_destinations(
+ self,
+ tree: Union[TreeObject, None] = None,
+ retval: Optional[Any] = None,
+ ) -> Dict[str, Any]:
+ """
+ Retrieve the named destinations present in the document.
+
+ Args:
+ tree:
+ retval:
+
+ Returns:
+ A dictionary which maps names to
+ :class:`Destinations<pypdf.generic.Destination>`.
+ """
+ if retval is None:
+ retval = {}
+ catalog = self.root_object
+
+ # get the name tree
+ if CA.DESTS in catalog:
+ tree = cast(TreeObject, catalog[CA.DESTS])
+ elif CA.NAMES in catalog:
+ names = cast(DictionaryObject, catalog[CA.NAMES])
+ if CA.DESTS in names:
+ tree = cast(TreeObject, names[CA.DESTS])
+
+ if tree is None:
+ return retval
+
+ if PA.KIDS in tree:
+ # recurse down the tree
+ for kid in cast(ArrayObject, tree[PA.KIDS]):
+ self._get_named_destinations(kid.get_object(), retval)
+ # §7.9.6, entries in a name tree node dictionary
+ elif CA.NAMES in tree: # /Kids and /Names are exclusives (§7.9.6)
+ names = cast(DictionaryObject, tree[CA.NAMES])
+ i = 0
+ while i < len(names):
+ key = cast(str, names[i].get_object())
+ i += 1
+ if not isinstance(key, str):
+ continue
+ try:
+ value = names[i].get_object()
+ except IndexError:
+ break
+ i += 1
+ if isinstance(value, DictionaryObject):
+ if "/D" in value:
+ value = value["/D"]
+ else:
+ continue
+ dest = self._build_destination(key, value) # type: ignore
+ if dest is not None:
+ retval[key] = dest
+ else: # case where Dests is in root catalog (PDF 1.7 specs, §2 about PDF 1.1)
+ for k__, v__ in tree.items():
+ val = v__.get_object()
+ if isinstance(val, DictionaryObject):
+ if "/D" in val:
+ val = val["/D"].get_object()
+ else:
+ continue
+ dest = self._build_destination(k__, val)
+ if dest is not None:
+ retval[k__] = dest
+ return retval
+
+ # A select group of relevant field attributes. For the complete list.
+ # See §12.3.2 of the PDF 1.7 or PDF 2.0 specification.
+
+ def get_fields(
+ self,
+ tree: Optional[TreeObject] = None,
+ retval: Optional[Dict[Any, Any]] = None,
+ fileobj: Optional[Any] = None,
+ stack: Optional[List[PdfObject]] = None,
+ ) -> Optional[Dict[str, Any]]:
+ """
+ Extract field data if this PDF contains interactive form fields.
+
+ The *tree*, *retval*, *stack* parameters are for recursive use.
+
+ Args:
+ tree: Current object to parse.
+ retval: In-progress list of fields.
+ fileobj: A file object (usually a text file) to write
+ a report to on all interactive form fields found.
+ stack: List of already parsed objects.
+
+ Returns:
+ A dictionary where each key is a field name, and each
+ value is a :class:`Field<pypdf.generic.Field>` object. By
+ default, the mapping name is used for keys.
+ ``None`` if form data could not be located.
+ """
+ field_attributes = FA.attributes_dict()
+ field_attributes.update(CheckboxRadioButtonAttributes.attributes_dict())
+ if retval is None:
+ retval = {}
+ catalog = self.root_object
+ stack = []
+ # get the AcroForm tree
+ if CD.ACRO_FORM in catalog:
+ tree = cast(Optional[TreeObject], catalog[CD.ACRO_FORM])
+ else:
+ return None
+ if tree is None:
+ return retval
+ assert stack is not None
+ if "/Fields" in tree:
+ fields = cast(ArrayObject, tree["/Fields"])
+ for f in fields:
+ field = f.get_object()
+ self._build_field(field, retval, fileobj, field_attributes, stack)
+ elif any(attr in tree for attr in field_attributes):
+ # Tree is a field
+ self._build_field(tree, retval, fileobj, field_attributes, stack)
+ return retval
+
+ def _get_qualified_field_name(self, parent: DictionaryObject) -> str:
+ if "/TM" in parent:
+ return cast(str, parent["/TM"])
+ elif "/Parent" in parent:
+ return (
+ self._get_qualified_field_name(
+ cast(DictionaryObject, parent["/Parent"])
+ )
+ + "."
+ + cast(str, parent.get("/T", ""))
+ )
+ else:
+ return cast(str, parent.get("/T", ""))
+
+ def _build_field(
+ self,
+ field: Union[TreeObject, DictionaryObject],
+ retval: Dict[Any, Any],
+ fileobj: Any,
+ field_attributes: Any,
+ stack: List[PdfObject],
+ ) -> None:
+ if all(attr not in field for attr in ("/T", "/TM")):
+ return
+ key = self._get_qualified_field_name(field)
+ if fileobj:
+ self._write_field(fileobj, field, field_attributes)
+ fileobj.write("\n")
+ retval[key] = Field(field)
+ obj = retval[key].indirect_reference.get_object() # to get the full object
+ if obj.get(FA.FT, "") == "/Ch":
+ retval[key][NameObject("/_States_")] = obj[NameObject(FA.Opt)]
+ if obj.get(FA.FT, "") == "/Btn" and "/AP" in obj:
+ # Checkbox
+ retval[key][NameObject("/_States_")] = ArrayObject(
+ list(obj["/AP"]["/N"].keys())
+ )
+ if "/Off" not in retval[key]["/_States_"]:
+ retval[key][NameObject("/_States_")].append(NameObject("/Off"))
+ elif obj.get(FA.FT, "") == "/Btn" and obj.get(FA.Ff, 0) & FA.FfBits.Radio != 0:
+ states: List[str] = []
+ retval[key][NameObject("/_States_")] = ArrayObject(states)
+ for k in obj.get(FA.Kids, {}):
+ k = k.get_object()
+ for s in list(k["/AP"]["/N"].keys()):
+ if s not in states:
+ states.append(s)
+ retval[key][NameObject("/_States_")] = ArrayObject(states)
+ if (
+ obj.get(FA.Ff, 0) & FA.FfBits.NoToggleToOff != 0
+ and "/Off" in retval[key]["/_States_"]
+ ):
+ del retval[key]["/_States_"][retval[key]["/_States_"].index("/Off")]
+ # at last for order
+ self._check_kids(field, retval, fileobj, stack)
+
+ def _check_kids(
+ self,
+ tree: Union[TreeObject, DictionaryObject],
+ retval: Any,
+ fileobj: Any,
+ stack: List[PdfObject],
+ ) -> None:
+ if tree in stack:
+ logger_warning(
+ f"{self._get_qualified_field_name(tree)} already parsed", __name__
+ )
+ return
+ stack.append(tree)
+ if PA.KIDS in tree:
+ # recurse down the tree
+ for kid in tree[PA.KIDS]: # type: ignore
+ kid = kid.get_object()
+ self.get_fields(kid, retval, fileobj, stack)
+
+ def _write_field(self, fileobj: Any, field: Any, field_attributes: Any) -> None:
+ field_attributes_tuple = FA.attributes()
+ field_attributes_tuple = (
+ field_attributes_tuple + CheckboxRadioButtonAttributes.attributes()
+ )
+
+ for attr in field_attributes_tuple:
+ if attr in (
+ FA.Kids,
+ FA.AA,
+ ):
+ continue
+ attr_name = field_attributes[attr]
+ try:
+ if attr == FA.FT:
+ # Make the field type value more clear
+ types = {
+ "/Btn": "Button",
+ "/Tx": "Text",
+ "/Ch": "Choice",
+ "/Sig": "Signature",
+ }
+ if field[attr] in types:
+ fileobj.write(f"{attr_name}: {types[field[attr]]}\n")
+ elif attr == FA.Parent:
+ # Let's just write the name of the parent
+ try:
+ name = field[attr][FA.TM]
+ except KeyError:
+ name = field[attr][FA.T]
+ fileobj.write(f"{attr_name}: {name}\n")
+ else:
+ fileobj.write(f"{attr_name}: {field[attr]}\n")
+ except KeyError:
+ # Field attribute is N/A or unknown, so don't write anything
+ pass
+
+ def get_form_text_fields(self, full_qualified_name: bool = False) -> Dict[str, Any]:
+ """
+ Retrieve form fields from the document with textual data.
+
+ Args:
+ full_qualified_name: to get full name
+
+ Returns:
+ A dictionary. The key is the name of the form field,
+ the value is the content of the field.
+
+ If the document contains multiple form fields with the same name, the
+ second and following will get the suffix .2, .3, ...
+ """
+
+ def indexed_key(k: str, fields: Dict[Any, Any]) -> str:
+ if k not in fields:
+ return k
+ else:
+ return (
+ k
+ + "."
+ + str(sum([1 for kk in fields if kk.startswith(k + ".")]) + 2)
+ )
+
+ # Retrieve document form fields
+ formfields = self.get_fields()
+ if formfields is None:
+ return {}
+ ff = {}
+ for field, value in formfields.items():
+ if value.get("/FT") == "/Tx":
+ if full_qualified_name:
+ ff[field] = value.get("/V")
+ else:
+ ff[indexed_key(cast(str, value["/T"]), ff)] = value.get("/V")
+ return ff
+
+ def get_pages_showing_field(
+ self, field: Union[Field, PdfObject, IndirectObject]
+ ) -> List[PageObject]:
+ """
+ Provides list of pages where the field is called.
+
+ Args:
+ field: Field Object, PdfObject or IndirectObject referencing a Field
+
+ Returns:
+ List of pages:
+ - Empty list:
+ The field has no widgets attached
+ (either hidden field or ancestor field).
+ - Single page list:
+ Page where the widget is present
+ (most common).
+ - Multi-page list:
+ Field with multiple kids widgets
+ (example: radio buttons, field repeated on multiple pages).
+ """
+
+ def _get_inherited(obj: DictionaryObject, key: str) -> Any:
+ if key in obj:
+ return obj[key]
+ elif "/Parent" in obj:
+ return _get_inherited(
+ cast(DictionaryObject, obj["/Parent"].get_object()), key
+ )
+ else:
+ return None
+
+ try:
+ # to cope with all types
+ field = cast(DictionaryObject, field.indirect_reference.get_object()) # type: ignore
+ except Exception as exc:
+ raise ValueError("field type is invalid") from exc
+ if _get_inherited(field, "/FT") is None:
+ raise ValueError("field is not valid")
+ ret = []
+ if field.get("/Subtype", "") == "/Widget":
+ if "/P" in field:
+ ret = [field["/P"].get_object()]
+ else:
+ ret = [
+ p
+ for p in self.pages
+ if field.indirect_reference in p.get("/Annots", "")
+ ]
+ else:
+ kids = field.get("/Kids", ())
+ for k in kids:
+ k = k.get_object()
+ if (k.get("/Subtype", "") == "/Widget") and ("/T" not in k):
+ # Kid that is just a widget, not a field:
+ if "/P" in k:
+ ret += [k["/P"].get_object()]
+ else:
+ ret += [
+ p
+ for p in self.pages
+ if k.indirect_reference in p.get("/Annots", "")
+ ]
+ return [
+ x
+ if isinstance(x, PageObject)
+ else (self.pages[self._get_page_number_by_indirect(x.indirect_reference)]) # type: ignore
+ for x in ret
+ ]
+
+ @property
+ def open_destination(
+ self,
+ ) -> Union[None, Destination, TextStringObject, ByteStringObject]:
+ """
+ Property to access the opening destination (``/OpenAction`` entry in
+ the PDF catalog). It returns ``None`` if the entry does not exist
+ or is not set.
+
+ Raises:
+ Exception: If a destination is invalid.
+ """
+ if "/OpenAction" not in self.root_object:
+ return None
+ oa: Any = self.root_object["/OpenAction"]
+ if isinstance(oa, bytes): # pragma: no cover
+ oa = oa.decode()
+ if isinstance(oa, str):
+ return create_string_object(oa)
+ elif isinstance(oa, ArrayObject):
+ try:
+ page, typ = oa[0:2]
+ array = oa[2:]
+ fit = Fit(typ, tuple(array))
+ return Destination("OpenAction", page, fit)
+ except Exception as exc:
+ raise Exception(f"Invalid Destination {oa}: {exc}")
+ else:
+ return None
+
+ @open_destination.setter
+ def open_destination(self, dest: Union[None, str, Destination, PageObject]) -> None:
+ raise NotImplementedError("no setter for open_destination")
+
+ @property
+ def outline(self) -> OutlineType:
+ """
+ Read-only property for the outline present in the document
+ (i.e., a collection of 'outline items' which are also known as
+ 'bookmarks').
+ """
+ return self._get_outline()
+
+ def _get_outline(
+ self, node: Optional[DictionaryObject] = None, outline: Optional[Any] = None
+ ) -> OutlineType:
+ if outline is None:
+ outline = []
+ catalog = self.root_object
+
+ # get the outline dictionary and named destinations
+ if CO.OUTLINES in catalog:
+ lines = cast(DictionaryObject, catalog[CO.OUTLINES])
+
+ if isinstance(lines, NullObject):
+ return outline
+
+ # §12.3.3 Document outline, entries in the outline dictionary
+ if lines is not None and "/First" in lines:
+ node = cast(DictionaryObject, lines["/First"])
+ self._namedDests = self._get_named_destinations()
+
+ if node is None:
+ return outline
+
+ # see if there are any more outline items
+ while True:
+ outline_obj = self._build_outline_item(node)
+ if outline_obj:
+ outline.append(outline_obj)
+
+ # check for sub-outline
+ if "/First" in node:
+ sub_outline: List[Any] = []
+ self._get_outline(cast(DictionaryObject, node["/First"]), sub_outline)
+ if sub_outline:
+ outline.append(sub_outline)
+
+ if "/Next" not in node:
+ break
+ node = cast(DictionaryObject, node["/Next"])
+
+ return outline
+
+ @property
+ def threads(self) -> Optional[ArrayObject]:
+ """
+ Read-only property for the list of threads.
+
+ See §12.4.3 from the PDF 1.7 or 2.0 specification.
+
+ It is an array of dictionaries with "/F" (the first bead in the thread)
+ and "/I" (a thread information dictionary containing information about
+ the thread, such as its title, author, and creation date) properties or
+ None if there are no articles.
+
+ Since PDF 2.0 it can also contain an indirect reference to a metadata
+ stream containing information about the thread, such as its title,
+ author, and creation date.
+ """
+ catalog = self.root_object
+ if CO.THREADS in catalog:
+ return cast("ArrayObject", catalog[CO.THREADS])
+ else:
+ return None
+
+ @abstractmethod
+ def _get_page_number_by_indirect(
+ self, indirect_reference: Union[None, int, NullObject, IndirectObject]
+ ) -> Optional[int]:
+ ... # pragma: no cover
+
+ def get_page_number(self, page: PageObject) -> Optional[int]:
+ """
+ Retrieve page number of a given PageObject.
+
+ Args:
+ page: The page to get page number. Should be
+ an instance of :class:`PageObject<pypdf._page.PageObject>`
+
+ Returns:
+ The page number or None if page is not found
+ """
+ return self._get_page_number_by_indirect(page.indirect_reference)
+
+ def get_destination_page_number(self, destination: Destination) -> Optional[int]:
+ """
+ Retrieve page number of a given Destination object.
+
+ Args:
+ destination: The destination to get page number.
+
+ Returns:
+ The page number or None if page is not found
+ """
+ return self._get_page_number_by_indirect(destination.page)
+
+ def _build_destination(
+ self,
+ title: str,
+ array: Optional[
+ List[
+ Union[NumberObject, IndirectObject, None, NullObject, DictionaryObject]
+ ]
+ ],
+ ) -> Destination:
+ page, typ = None, None
+ # handle outline items with missing or invalid destination
+ if (
+ isinstance(array, (NullObject, str))
+ or (isinstance(array, ArrayObject) and len(array) == 0)
+ or array is None
+ ):
+ page = NullObject()
+ return Destination(title, page, Fit.fit())
+ else:
+ page, typ = array[0:2] # type: ignore
+ array = array[2:]
+ try:
+ return Destination(title, page, Fit(fit_type=typ, fit_args=array)) # type: ignore
+ except PdfReadError:
+ logger_warning(f"Unknown destination: {title} {array}", __name__)
+ if self.strict:
+ raise
+ # create a link to first Page
+ tmp = self.pages[0].indirect_reference
+ indirect_reference = NullObject() if tmp is None else tmp
+ return Destination(title, indirect_reference, Fit.fit()) # type: ignore
+
+ def _build_outline_item(self, node: DictionaryObject) -> Optional[Destination]:
+ dest, title, outline_item = None, None, None
+
+ # title required for valid outline
+ # § 12.3.3, entries in an outline item dictionary
+ try:
+ title = cast("str", node["/Title"])
+ except KeyError:
+ if self.strict:
+ raise PdfReadError(f"Outline Entry Missing /Title attribute: {node!r}")
+ title = ""
+
+ if "/A" in node:
+ # Action, PDFv1.7 Section 12.6 (only type GoTo supported)
+ action = cast(DictionaryObject, node["/A"])
+ action_type = cast(NameObject, action[GoToActionArguments.S])
+ if action_type == "/GoTo":
+ dest = action[GoToActionArguments.D]
+ elif "/Dest" in node:
+ # Destination, PDFv1.7 Section 12.3.2
+ dest = node["/Dest"]
+ # if array was referenced in another object, will be a dict w/ key "/D"
+ if isinstance(dest, DictionaryObject) and "/D" in dest:
+ dest = dest["/D"]
+
+ if isinstance(dest, ArrayObject):
+ outline_item = self._build_destination(title, dest)
+ elif isinstance(dest, str):
+ # named destination, addresses NameObject Issue #193
+ # TODO : keep named destination instead of replacing it ?
+ try:
+ outline_item = self._build_destination(
+ title, self._namedDests[dest].dest_array
+ )
+ except KeyError:
+ # named destination not found in Name Dict
+ outline_item = self._build_destination(title, None)
+ elif dest is None:
+ # outline item not required to have destination or action
+ # PDFv1.7 Table 153
+ outline_item = self._build_destination(title, dest)
+ else:
+ if self.strict:
+ raise PdfReadError(f"Unexpected destination {dest!r}")
+ else:
+ logger_warning(
+ f"Removed unexpected destination {dest!r} from destination",
+ __name__,
+ )
+ outline_item = self._build_destination(title, None)
+
+ # if outline item created, add color, format, and child count if present
+ if outline_item:
+ if "/C" in node:
+ # Color of outline item font in (R, G, B) with values ranging 0.0-1.0
+ outline_item[NameObject("/C")] = ArrayObject(FloatObject(c) for c in node["/C"]) # type: ignore
+ if "/F" in node:
+ # specifies style characteristics bold and/or italic
+ # with 1=italic, 2=bold, 3=both
+ outline_item[NameObject("/F")] = node["/F"]
+ if "/Count" in node:
+ # absolute value = num. visible children
+ # with positive = open/unfolded, negative = closed/folded
+ outline_item[NameObject("/Count")] = node["/Count"]
+ # if count is 0 we will consider it as open ( in order to have always an is_open to simplify
+ outline_item[NameObject("/%is_open%")] = BooleanObject(
+ node.get("/Count", 0) >= 0
+ )
+ outline_item.node = node
+ try:
+ outline_item.indirect_reference = node.indirect_reference
+ except AttributeError:
+ pass
+ return outline_item
+
+ @property
+ def pages(self) -> List[PageObject]:
+ """
+ Property that emulates a list of :class:`PageObject<pypdf._page.PageObject>`.
+ This property allows to get a page or a range of pages.
+
+ Note:
+ For PdfWriter only: Provides the capability to remove a page/range of
+ page from the list (using the del operator). Remember: Only the page
+ entry is removed, as the objects beneath can be used elsewhere. A
+ solution to completely remove them - if they are not used anywhere - is
+ to write to a buffer/temporary file and then load it into a new
+ PdfWriter.
+
+ """
+ return _VirtualList(self.get_num_pages, self.get_page) # type: ignore
+
+ @property
+ def page_labels(self) -> List[str]:
+ """
+ A list of labels for the pages in this document.
+
+ This property is read-only. The labels are in the order that the pages
+ appear in the document.
+ """
+ return [page_index2page_label(self, i) for i in range(len(self.pages))]
+
+ @property
+ def page_layout(self) -> Optional[str]:
+ """
+ Get the page layout currently being used.
+
+ .. list-table:: Valid ``layout`` values
+ :widths: 50 200
+
+ * - /NoLayout
+ - Layout explicitly not specified
+ * - /SinglePage
+ - Show one page at a time
+ * - /OneColumn
+ - Show one column at a time
+ * - /TwoColumnLeft
+ - Show pages in two columns, odd-numbered pages on the left
+ * - /TwoColumnRight
+ - Show pages in two columns, odd-numbered pages on the right
+ * - /TwoPageLeft
+ - Show two pages at a time, odd-numbered pages on the left
+ * - /TwoPageRight
+ - Show two pages at a time, odd-numbered pages on the right
+ """
+ try:
+ return cast(NameObject, self.root_object[CD.PAGE_LAYOUT])
+ except KeyError:
+ return None
+
+ @property
+ def page_mode(self) -> Optional[PagemodeType]:
+ """
+ Get the page mode currently being used.
+
+ .. list-table:: Valid ``mode`` values
+ :widths: 50 200
+
+ * - /UseNone
+ - Do not show outline or thumbnails panels
+ * - /UseOutlines
+ - Show outline (aka bookmarks) panel
+ * - /UseThumbs
+ - Show page thumbnails panel
+ * - /FullScreen
+ - Fullscreen view
+ * - /UseOC
+ - Show Optional Content Group (OCG) panel
+ * - /UseAttachments
+ - Show attachments panel
+ """
+ try:
+ return self.root_object["/PageMode"] # type: ignore
+ except KeyError:
+ return None
+
+ def _flatten(
+ self,
+ pages: Union[None, DictionaryObject, PageObject] = None,
+ inherit: Optional[Dict[str, Any]] = None,
+ indirect_reference: Optional[IndirectObject] = None,
+ ) -> None:
+ inheritable_page_attributes = (
+ NameObject(PG.RESOURCES),
+ NameObject(PG.MEDIABOX),
+ NameObject(PG.CROPBOX),
+ NameObject(PG.ROTATE),
+ )
+ if inherit is None:
+ inherit = {}
+ if pages is None:
+ # Fix issue 327: set flattened_pages attribute only for
+ # decrypted file
+ catalog = self.root_object
+ pages = catalog["/Pages"].get_object() # type: ignore
+ assert isinstance(pages, DictionaryObject)
+ self.flattened_pages = []
+
+ if PA.TYPE in pages:
+ t = cast(str, pages[PA.TYPE])
+ # if pdf has no type, considered as a page if /Kids is missing
+ elif PA.KIDS not in pages:
+ t = "/Page"
+ else:
+ t = "/Pages"
+
+ if t == "/Pages":
+ for attr in inheritable_page_attributes:
+ if attr in pages:
+ inherit[attr] = pages[attr]
+ for page in cast(ArrayObject, pages[PA.KIDS]):
+ addt = {}
+ if isinstance(page, IndirectObject):
+ addt["indirect_reference"] = page
+ obj = page.get_object()
+ if obj:
+ # damaged file may have invalid child in /Pages
+ self._flatten(obj, inherit, **addt)
+ elif t == "/Page":
+ for attr_in, value in list(inherit.items()):
+ # if the page has it's own value, it does not inherit the
+ # parent's value:
+ if attr_in not in pages:
+ pages[attr_in] = value
+ page_obj = PageObject(self, indirect_reference)
+ page_obj.update(pages)
+
+ # TODO: Could flattened_pages be None at this point?
+ self.flattened_pages.append(page_obj) # type: ignore
+
+ def remove_page(
+ self,
+ page: Union[int, PageObject, IndirectObject],
+ clean: bool = False,
+ ) -> None:
+ """
+ Remove page from pages list.
+
+ Args:
+ page:
+ * :class:`int`: Page number to be removed.
+ * :class:`~pypdf._page.PageObject`: page to be removed. If the page appears many times
+ only the first one will be removed.
+ * :class:`~pypdf.generic.IndirectObject`: Reference to page to be removed.
+
+ clean: replace PageObject with NullObject to prevent annotations
+ or destinations to reference a detached page.
+ """
+ if self.flattened_pages is None:
+ self._flatten()
+ assert self.flattened_pages is not None
+ if isinstance(page, IndirectObject):
+ p = page.get_object()
+ if not isinstance(p, PageObject):
+ logger_warning("IndirectObject is not referencing a page", __name__)
+ return
+ page = p
+
+ if not isinstance(page, int):
+ try:
+ page = self.flattened_pages.index(page)
+ except ValueError:
+ logger_warning("Cannot find page in pages", __name__)
+ return
+ if not (0 <= page < len(self.flattened_pages)):
+ logger_warning("Page number is out of range", __name__)
+ return
+
+ ind = self.pages[page].indirect_reference
+ del self.pages[page]
+ if clean and ind is not None:
+ self._replace_object(ind, NullObject())
+
+ def _get_indirect_object(self, num: int, gen: int) -> Optional[PdfObject]:
+ """
+ Used to ease development.
+
+ This is equivalent to generic.IndirectObject(num,gen,self).get_object()
+
+ Args:
+ num: The object number of the indirect object.
+ gen: The generation number of the indirect object.
+
+ Returns:
+ A PdfObject
+ """
+ return IndirectObject(num, gen, self).get_object()
+
+ def decode_permissions(
+ self, permissions_code: int
+ ) -> Dict[str, bool]: # pragma: no cover
+ """Take the permissions as an integer, return the allowed access."""
+ deprecate_with_replacement(
+ old_name="decode_permissions",
+ new_name="user_access_permissions",
+ removed_in="5.0.0",
+ )
+
+ permissions_mapping = {
+ "print": UserAccessPermissions.PRINT,
+ "modify": UserAccessPermissions.MODIFY,
+ "copy": UserAccessPermissions.EXTRACT,
+ "annotations": UserAccessPermissions.ADD_OR_MODIFY,
+ "forms": UserAccessPermissions.FILL_FORM_FIELDS,
+ # Do not fix typo, as part of official, but deprecated API.
+ "accessability": UserAccessPermissions.EXTRACT_TEXT_AND_GRAPHICS,
+ "assemble": UserAccessPermissions.ASSEMBLE_DOC,
+ "print_high_quality": UserAccessPermissions.PRINT_TO_REPRESENTATION,
+ }
+
+ return {
+ key: permissions_code & flag != 0
+ for key, flag in permissions_mapping.items()
+ }
+
+ @property
+ def user_access_permissions(self) -> Optional[UserAccessPermissions]:
+ """Get the user access permissions for encrypted documents. Returns None if not encrypted."""
+ if self._encryption is None:
+ return None
+ return UserAccessPermissions(self._encryption.P)
+
+ @property
+ @abstractmethod
+ def is_encrypted(self) -> bool:
+ """
+ Read-only boolean property showing whether this PDF file is encrypted.
+
+ Note that this property, if true, will remain true even after the
+ :meth:`decrypt()<pypdf.PdfReader.decrypt>` method is called.
+ """
+ ... # pragma: no cover
+
+ @property
+ def xfa(self) -> Optional[Dict[str, Any]]:
+ tree: Optional[TreeObject] = None
+ retval: Dict[str, Any] = {}
+ catalog = self.root_object
+
+ if "/AcroForm" not in catalog or not catalog["/AcroForm"]:
+ return None
+
+ tree = cast(TreeObject, catalog["/AcroForm"])
+
+ if "/XFA" in tree:
+ fields = cast(ArrayObject, tree["/XFA"])
+ i = iter(fields)
+ for f in i:
+ tag = f
+ f = next(i)
+ if isinstance(f, IndirectObject):
+ field = cast(Optional[EncodedStreamObject], f.get_object())
+ if field:
+ es = zlib.decompress(b_(field._data))
+ retval[tag] = es
+ return retval
+
+ @property
+ def attachments(self) -> Mapping[str, List[bytes]]:
+ return LazyDict(
+ {
+ name: (self._get_attachment_list, name)
+ for name in self._list_attachments()
+ }
+ )
+
+ def _list_attachments(self) -> List[str]:
+ """
+ Retrieves the list of filenames of file attachments.
+
+ Returns:
+ list of filenames
+ """
+ catalog = self.root_object
+ # From the catalog get the embedded file names
+ try:
+ filenames = cast(
+ ArrayObject,
+ cast(
+ DictionaryObject,
+ cast(DictionaryObject, catalog["/Names"])["/EmbeddedFiles"],
+ )["/Names"],
+ )
+ except KeyError:
+ return []
+ attachments_names = [f for f in filenames if isinstance(f, str)]
+ return attachments_names
+
+ def _get_attachment_list(self, name: str) -> List[bytes]:
+ out = self._get_attachments(name)[name]
+ if isinstance(out, list):
+ return out
+ return [out]
+
+ def _get_attachments(
+ self, filename: Optional[str] = None
+ ) -> Dict[str, Union[bytes, List[bytes]]]:
+ """
+ Retrieves all or selected file attachments of the PDF as a dictionary of file names
+ and the file data as a bytestring.
+
+ Args:
+ filename: If filename is None, then a dictionary of all attachments
+ will be returned, where the key is the filename and the value
+ is the content. Otherwise, a dictionary with just a single key
+ - the filename - and its content will be returned.
+
+ Returns:
+ dictionary of filename -> Union[bytestring or List[ByteString]]
+ If the filename exists multiple times a list of the different versions will be provided.
+ """
+ catalog = self.root_object
+ # From the catalog get the embedded file names
+ try:
+ filenames = cast(
+ ArrayObject,
+ cast(
+ DictionaryObject,
+ cast(DictionaryObject, catalog["/Names"])["/EmbeddedFiles"],
+ )["/Names"],
+ )
+ except KeyError:
+ return {}
+ attachments: Dict[str, Union[bytes, List[bytes]]] = {}
+ # Loop through attachments
+ for i in range(len(filenames)):
+ f = filenames[i]
+ if isinstance(f, str):
+ if filename is not None and f != filename:
+ continue
+ name = f
+ f_dict = filenames[i + 1].get_object()
+ f_data = f_dict["/EF"]["/F"].get_data()
+ if name in attachments:
+ if not isinstance(attachments[name], list):
+ attachments[name] = [attachments[name]] # type:ignore
+ attachments[name].append(f_data) # type:ignore
+ else:
+ attachments[name] = f_data
+ return attachments
+
+
+class LazyDict(Mapping[Any, Any]):
+ def __init__(self, *args: Any, **kw: Any) -> None:
+ self._raw_dict = dict(*args, **kw)
+
+ def __getitem__(self, key: str) -> Any:
+ func, arg = self._raw_dict.__getitem__(key)
+ return func(arg)
+
+ def __iter__(self) -> Iterator[Any]:
+ return iter(self._raw_dict)
+
+ def __len__(self) -> int:
+ return len(self._raw_dict)
+
+ def __str__(self) -> str:
+ return f"LazyDict(keys={list(self.keys())})"
diff --git a/.venv/lib/python3.12/site-packages/pypdf/_encryption.py b/.venv/lib/python3.12/site-packages/pypdf/_encryption.py
new file mode 100644
index 00000000..5ddd8d0e
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/pypdf/_encryption.py
@@ -0,0 +1,1168 @@
+# Copyright (c) 2022, exiledkingcc
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
+#
+# * Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
+# * Redistributions in binary form must reproduce the above copyright notice,
+# this list of conditions and the following disclaimer in the documentation
+# and/or other materials provided with the distribution.
+# * The name of the author may not be used to endorse or promote products
+# derived from this software without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+# POSSIBILITY OF SUCH DAMAGE.
+import hashlib
+import secrets
+import struct
+from enum import Enum, IntEnum
+from typing import Any, Dict, Optional, Tuple, Union, cast
+
+from pypdf._crypt_providers import (
+ CryptAES,
+ CryptBase,
+ CryptIdentity,
+ CryptRC4,
+ aes_cbc_decrypt,
+ aes_cbc_encrypt,
+ aes_ecb_decrypt,
+ aes_ecb_encrypt,
+ rc4_decrypt,
+ rc4_encrypt,
+)
+
+from ._utils import b_, logger_warning
+from .generic import (
+ ArrayObject,
+ ByteStringObject,
+ DictionaryObject,
+ NameObject,
+ NumberObject,
+ PdfObject,
+ StreamObject,
+ TextStringObject,
+ create_string_object,
+)
+
+
+class CryptFilter:
+ def __init__(
+ self,
+ stm_crypt: CryptBase,
+ str_crypt: CryptBase,
+ ef_crypt: CryptBase,
+ ) -> None:
+ self.stm_crypt = stm_crypt
+ self.str_crypt = str_crypt
+ self.ef_crypt = ef_crypt
+
+ def encrypt_object(self, obj: PdfObject) -> PdfObject:
+ if isinstance(obj, ByteStringObject):
+ data = self.str_crypt.encrypt(obj.original_bytes)
+ obj = ByteStringObject(data)
+ if isinstance(obj, TextStringObject):
+ data = self.str_crypt.encrypt(obj.get_encoded_bytes())
+ obj = ByteStringObject(data)
+ elif isinstance(obj, StreamObject):
+ obj2 = StreamObject()
+ obj2.update(obj)
+ obj2.set_data(self.stm_crypt.encrypt(b_(obj._data)))
+ for key, value in obj.items(): # Dont forget the Stream dict.
+ obj2[key] = self.encrypt_object(value)
+ obj = obj2
+ elif isinstance(obj, DictionaryObject):
+ obj2 = DictionaryObject() # type: ignore
+ for key, value in obj.items():
+ obj2[key] = self.encrypt_object(value)
+ obj = obj2
+ elif isinstance(obj, ArrayObject):
+ obj = ArrayObject(self.encrypt_object(x) for x in obj)
+ return obj
+
+ def decrypt_object(self, obj: PdfObject) -> PdfObject:
+ if isinstance(obj, (ByteStringObject, TextStringObject)):
+ data = self.str_crypt.decrypt(obj.original_bytes)
+ obj = create_string_object(data)
+ elif isinstance(obj, StreamObject):
+ obj._data = self.stm_crypt.decrypt(b_(obj._data))
+ for key, value in obj.items(): # Dont forget the Stream dict.
+ obj[key] = self.decrypt_object(value)
+ elif isinstance(obj, DictionaryObject):
+ for key, value in obj.items():
+ obj[key] = self.decrypt_object(value)
+ elif isinstance(obj, ArrayObject):
+ for i in range(len(obj)):
+ obj[i] = self.decrypt_object(obj[i])
+ return obj
+
+
+_PADDING = (
+ b"\x28\xbf\x4e\x5e\x4e\x75\x8a\x41\x64\x00\x4e\x56\xff\xfa\x01\x08"
+ b"\x2e\x2e\x00\xb6\xd0\x68\x3e\x80\x2f\x0c\xa9\xfe\x64\x53\x69\x7a"
+)
+
+
+def _padding(data: bytes) -> bytes:
+ return (data + _PADDING)[:32]
+
+
+class AlgV4:
+ @staticmethod
+ def compute_key(
+ password: bytes,
+ rev: int,
+ key_size: int,
+ o_entry: bytes,
+ P: int,
+ id1_entry: bytes,
+ metadata_encrypted: bool,
+ ) -> bytes:
+ """
+ Algorithm 2: Computing an encryption key.
+
+ a) Pad or truncate the password string to exactly 32 bytes. If the
+ password string is more than 32 bytes long,
+ use only its first 32 bytes; if it is less than 32 bytes long, pad it
+ by appending the required number of
+ additional bytes from the beginning of the following padding string:
+ < 28 BF 4E 5E 4E 75 8A 41 64 00 4E 56 FF FA 01 08
+ 2E 2E 00 B6 D0 68 3E 80 2F 0C A9 FE 64 53 69 7A >
+ That is, if the password string is n bytes long, append
+ the first 32 - n bytes of the padding string to the end
+ of the password string. If the password string is empty
+ (zero-length), meaning there is no user password,
+ substitute the entire padding string in its place.
+
+ b) Initialize the MD5 hash function and pass the result of step (a)
+ as input to this function.
+ c) Pass the value of the encryption dictionary’s O entry to the
+ MD5 hash function. ("Algorithm 3: Computing
+ the encryption dictionary’s O (owner password) value" shows how the
+ O value is computed.)
+ d) Convert the integer value of the P entry to a 32-bit unsigned binary
+ number and pass these bytes to the
+ MD5 hash function, low-order byte first.
+ e) Pass the first element of the file’s file identifier array (the value
+ of the ID entry in the document’s trailer
+ dictionary; see Table 15) to the MD5 hash function.
+ f) (Security handlers of revision 4 or greater) If document metadata is
+ not being encrypted, pass 4 bytes with
+ the value 0xFFFFFFFF to the MD5 hash function.
+ g) Finish the hash.
+ h) (Security handlers of revision 3 or greater) Do the following
+ 50 times: Take the output from the previous
+ MD5 hash and pass the first n bytes of the output as input into a new
+ MD5 hash, where n is the number of
+ bytes of the encryption key as defined by the value of the encryption
+ dictionary’s Length entry.
+ i) Set the encryption key to the first n bytes of the output from the
+ final MD5 hash, where n shall always be 5
+ for security handlers of revision 2 but, for security handlers of
+ revision 3 or greater, shall depend on the
+ value of the encryption dictionary’s Length entry.
+
+ Args:
+ password: The encryption secret as a bytes-string
+ rev: The encryption revision (see PDF standard)
+ key_size: The size of the key in bytes
+ o_entry: The owner entry
+ P: A set of flags specifying which operations shall be permitted
+ when the document is opened with user access. If bit 2 is set to 1,
+ all other bits are ignored and all operations are permitted.
+ If bit 2 is set to 0, permission for operations are based on the
+ values of the remaining flags defined in Table 24.
+ id1_entry:
+ metadata_encrypted: A boolean indicating if the metadata is encrypted.
+
+ Returns:
+ The u_hash digest of length key_size
+ """
+ a = _padding(password)
+ u_hash = hashlib.md5(a)
+ u_hash.update(o_entry)
+ u_hash.update(struct.pack("<I", P))
+ u_hash.update(id1_entry)
+ if rev >= 4 and not metadata_encrypted:
+ u_hash.update(b"\xff\xff\xff\xff")
+ u_hash_digest = u_hash.digest()
+ length = key_size // 8
+ if rev >= 3:
+ for _ in range(50):
+ u_hash_digest = hashlib.md5(u_hash_digest[:length]).digest()
+ return u_hash_digest[:length]
+
+ @staticmethod
+ def compute_O_value_key(owner_password: bytes, rev: int, key_size: int) -> bytes:
+ """
+ Algorithm 3: Computing the encryption dictionary’s O (owner password) value.
+
+ a) Pad or truncate the owner password string as described in step (a)
+ of "Algorithm 2: Computing an encryption key".
+ If there is no owner password, use the user password instead.
+ b) Initialize the MD5 hash function and pass the result of step (a) as
+ input to this function.
+ c) (Security handlers of revision 3 or greater) Do the following 50 times:
+ Take the output from the previous
+ MD5 hash and pass it as input into a new MD5 hash.
+ d) Create an RC4 encryption key using the first n bytes of the output
+ from the final MD5 hash, where n shall
+ always be 5 for security handlers of revision 2 but, for security
+ handlers of revision 3 or greater, shall
+ depend on the value of the encryption dictionary’s Length entry.
+ e) Pad or truncate the user password string as described in step (a) of
+ "Algorithm 2: Computing an encryption key".
+ f) Encrypt the result of step (e), using an RC4 encryption function with
+ the encryption key obtained in step (d).
+ g) (Security handlers of revision 3 or greater) Do the following 19 times:
+ Take the output from the previous
+ invocation of the RC4 function and pass it as input to a new
+ invocation of the function; use an encryption
+ key generated by taking each byte of the encryption key obtained in
+ step (d) and performing an XOR
+ (exclusive or) operation between that byte and the single-byte value
+ of the iteration counter (from 1 to 19).
+ h) Store the output from the final invocation of the RC4 function as
+ the value of the O entry in the encryption dictionary.
+
+ Args:
+ owner_password:
+ rev: The encryption revision (see PDF standard)
+ key_size: The size of the key in bytes
+
+ Returns:
+ The RC4 key
+ """
+ a = _padding(owner_password)
+ o_hash_digest = hashlib.md5(a).digest()
+
+ if rev >= 3:
+ for _ in range(50):
+ o_hash_digest = hashlib.md5(o_hash_digest).digest()
+
+ rc4_key = o_hash_digest[: key_size // 8]
+ return rc4_key
+
+ @staticmethod
+ def compute_O_value(rc4_key: bytes, user_password: bytes, rev: int) -> bytes:
+ """
+ See :func:`compute_O_value_key`.
+
+ Args:
+ rc4_key:
+ user_password:
+ rev: The encryption revision (see PDF standard)
+
+ Returns:
+ The RC4 encrypted
+ """
+ a = _padding(user_password)
+ rc4_enc = rc4_encrypt(rc4_key, a)
+ if rev >= 3:
+ for i in range(1, 20):
+ key = bytes(bytearray(x ^ i for x in rc4_key))
+ rc4_enc = rc4_encrypt(key, rc4_enc)
+ return rc4_enc
+
+ @staticmethod
+ def compute_U_value(key: bytes, rev: int, id1_entry: bytes) -> bytes:
+ """
+ Algorithm 4: Computing the encryption dictionary’s U (user password) value.
+
+ (Security handlers of revision 2)
+
+ a) Create an encryption key based on the user password string, as
+ described in "Algorithm 2: Computing an encryption key".
+ b) Encrypt the 32-byte padding string shown in step (a) of
+ "Algorithm 2: Computing an encryption key", using an RC4 encryption
+ function with the encryption key from the preceding step.
+ c) Store the result of step (b) as the value of the U entry in the
+ encryption dictionary.
+
+ Args:
+ key:
+ rev: The encryption revision (see PDF standard)
+ id1_entry:
+
+ Returns:
+ The value
+ """
+ if rev <= 2:
+ value = rc4_encrypt(key, _PADDING)
+ return value
+
+ """
+ Algorithm 5: Computing the encryption dictionary’s U (user password) value.
+
+ (Security handlers of revision 3 or greater)
+
+ a) Create an encryption key based on the user password string, as
+ described in "Algorithm 2: Computing an encryption key".
+ b) Initialize the MD5 hash function and pass the 32-byte padding string
+ shown in step (a) of "Algorithm 2:
+ Computing an encryption key" as input to this function.
+ c) Pass the first element of the file’s file identifier array (the value
+ of the ID entry in the document’s trailer
+ dictionary; see Table 15) to the hash function and finish the hash.
+ d) Encrypt the 16-byte result of the hash, using an RC4 encryption
+ function with the encryption key from step (a).
+ e) Do the following 19 times: Take the output from the previous
+ invocation of the RC4 function and pass it as input to a new
+ invocation of the function; use an encryption key generated by
+ taking each byte of the original encryption key obtained in
+ step (a) and performing an XOR (exclusive or) operation between that
+ byte and the single-byte value of the iteration counter (from 1 to 19).
+ f) Append 16 bytes of arbitrary padding to the output from the final
+ invocation of the RC4 function and store the 32-byte result as the
+ value of the U entry in the encryption dictionary.
+ """
+ u_hash = hashlib.md5(_PADDING)
+ u_hash.update(id1_entry)
+ rc4_enc = rc4_encrypt(key, u_hash.digest())
+ for i in range(1, 20):
+ rc4_key = bytes(bytearray(x ^ i for x in key))
+ rc4_enc = rc4_encrypt(rc4_key, rc4_enc)
+ return _padding(rc4_enc)
+
+ @staticmethod
+ def verify_user_password(
+ user_password: bytes,
+ rev: int,
+ key_size: int,
+ o_entry: bytes,
+ u_entry: bytes,
+ P: int,
+ id1_entry: bytes,
+ metadata_encrypted: bool,
+ ) -> bytes:
+ """
+ Algorithm 6: Authenticating the user password.
+
+ a) Perform all but the last step of "Algorithm 4: Computing the
+ encryption dictionary’s U (user password) value (Security handlers of
+ revision 2)" or "Algorithm 5: Computing the encryption dictionary’s U
+ (user password) value (Security handlers of revision 3 or greater)"
+ using the supplied password string.
+ b) If the result of step (a) is equal to the value of the encryption
+ dictionary’s U entry (comparing on the first 16 bytes in the case of
+ security handlers of revision 3 or greater), the password supplied is
+ the correct user password. The key obtained in step (a) (that is, in
+ the first step of "Algorithm 4: Computing the encryption
+ dictionary’s U (user password) value
+ (Security handlers of revision 2)" or
+ "Algorithm 5: Computing the encryption dictionary’s U (user password)
+ value (Security handlers of revision 3 or greater)") shall be used
+ to decrypt the document.
+
+ Args:
+ user_password: The user password as a bytes stream
+ rev: The encryption revision (see PDF standard)
+ key_size: The size of the key in bytes
+ o_entry: The owner entry
+ u_entry: The user entry
+ P: A set of flags specifying which operations shall be permitted
+ when the document is opened with user access. If bit 2 is set to 1,
+ all other bits are ignored and all operations are permitted.
+ If bit 2 is set to 0, permission for operations are based on the
+ values of the remaining flags defined in Table 24.
+ id1_entry:
+ metadata_encrypted: A boolean indicating if the metadata is encrypted.
+
+ Returns:
+ The key
+ """
+ key = AlgV4.compute_key(
+ user_password, rev, key_size, o_entry, P, id1_entry, metadata_encrypted
+ )
+ u_value = AlgV4.compute_U_value(key, rev, id1_entry)
+ if rev >= 3:
+ u_value = u_value[:16]
+ u_entry = u_entry[:16]
+ if u_value != u_entry:
+ key = b""
+ return key
+
+ @staticmethod
+ def verify_owner_password(
+ owner_password: bytes,
+ rev: int,
+ key_size: int,
+ o_entry: bytes,
+ u_entry: bytes,
+ P: int,
+ id1_entry: bytes,
+ metadata_encrypted: bool,
+ ) -> bytes:
+ """
+ Algorithm 7: Authenticating the owner password.
+
+ a) Compute an encryption key from the supplied password string, as
+ described in steps (a) to (d) of
+ "Algorithm 3: Computing the encryption dictionary’s O (owner password)
+ value".
+ b) (Security handlers of revision 2 only) Decrypt the value of the
+ encryption dictionary’s O entry, using an RC4
+ encryption function with the encryption key computed in step (a).
+ (Security handlers of revision 3 or greater) Do the following 20 times:
+ Decrypt the value of the encryption dictionary’s O entry (first iteration)
+ or the output from the previous iteration (all subsequent iterations),
+ using an RC4 encryption function with a different encryption key at
+ each iteration. The key shall be generated by taking the original key
+ (obtained in step (a)) and performing an XOR (exclusive or) operation
+ between each byte of the key and the single-byte value of the
+ iteration counter (from 19 to 0).
+ c) The result of step (b) purports to be the user password.
+ Authenticate this user password using
+ "Algorithm 6: Authenticating the user password".
+ If it is correct, the password supplied is the correct owner password.
+
+ Args:
+ owner_password:
+ rev: The encryption revision (see PDF standard)
+ key_size: The size of the key in bytes
+ o_entry: The owner entry
+ u_entry: The user entry
+ P: A set of flags specifying which operations shall be permitted
+ when the document is opened with user access. If bit 2 is set to 1,
+ all other bits are ignored and all operations are permitted.
+ If bit 2 is set to 0, permission for operations are based on the
+ values of the remaining flags defined in Table 24.
+ id1_entry:
+ metadata_encrypted: A boolean indicating if the metadata is encrypted.
+
+ Returns:
+ bytes
+ """
+ rc4_key = AlgV4.compute_O_value_key(owner_password, rev, key_size)
+
+ if rev <= 2:
+ user_password = rc4_decrypt(rc4_key, o_entry)
+ else:
+ user_password = o_entry
+ for i in range(19, -1, -1):
+ key = bytes(bytearray(x ^ i for x in rc4_key))
+ user_password = rc4_decrypt(key, user_password)
+ return AlgV4.verify_user_password(
+ user_password,
+ rev,
+ key_size,
+ o_entry,
+ u_entry,
+ P,
+ id1_entry,
+ metadata_encrypted,
+ )
+
+
+class AlgV5:
+ @staticmethod
+ def verify_owner_password(
+ R: int, password: bytes, o_value: bytes, oe_value: bytes, u_value: bytes
+ ) -> bytes:
+ """
+ Algorithm 3.2a Computing an encryption key.
+
+ To understand the algorithm below, it is necessary to treat the O and U
+ strings in the Encrypt dictionary as made up of three sections.
+ The first 32 bytes are a hash value (explained below). The next 8 bytes
+ are called the Validation Salt. The final 8 bytes are called the Key Salt.
+
+ 1. The password string is generated from Unicode input by processing the
+ input string with the SASLprep (IETF RFC 4013) profile of
+ stringprep (IETF RFC 3454), and then converting to a UTF-8
+ representation.
+ 2. Truncate the UTF-8 representation to 127 bytes if it is longer than
+ 127 bytes.
+ 3. Test the password against the owner key by computing the SHA-256 hash
+ of the UTF-8 password concatenated with the 8 bytes of owner
+ Validation Salt, concatenated with the 48-byte U string. If the
+ 32-byte result matches the first 32 bytes of the O string, this is
+ the owner password.
+ Compute an intermediate owner key by computing the SHA-256 hash of
+ the UTF-8 password concatenated with the 8 bytes of owner Key Salt,
+ concatenated with the 48-byte U string. The 32-byte result is the
+ key used to decrypt the 32-byte OE string using AES-256 in CBC mode
+ with no padding and an initialization vector of zero.
+ The 32-byte result is the file encryption key.
+ 4. Test the password against the user key by computing the SHA-256 hash
+ of the UTF-8 password concatenated with the 8 bytes of user
+ Validation Salt. If the 32 byte result matches the first 32 bytes of
+ the U string, this is the user password.
+ Compute an intermediate user key by computing the SHA-256 hash of the
+ UTF-8 password concatenated with the 8 bytes of user Key Salt.
+ The 32-byte result is the key used to decrypt the 32-byte
+ UE string using AES-256 in CBC mode with no padding and an
+ initialization vector of zero. The 32-byte result is the file
+ encryption key.
+ 5. Decrypt the 16-byte Perms string using AES-256 in ECB mode with an
+ initialization vector of zero and the file encryption key as the key.
+ Verify that bytes 9-11 of the result are the characters ‘a’, ‘d’, ‘b’.
+ Bytes 0-3 of the decrypted Perms entry, treated as a little-endian
+ integer, are the user permissions.
+ They should match the value in the P key.
+
+ Args:
+ R: A number specifying which revision of the standard security
+ handler shall be used to interpret this dictionary
+ password: The owner password
+ o_value: A 32-byte string, based on both the owner and user passwords,
+ that shall be used in computing the encryption key and in
+ determining whether a valid owner password was entered
+ oe_value:
+ u_value: A 32-byte string, based on the user password, that shall be
+ used in determining whether to prompt the user for a password and,
+ if so, whether a valid user or owner password was entered.
+
+ Returns:
+ The key
+ """
+ password = password[:127]
+ if (
+ AlgV5.calculate_hash(R, password, o_value[32:40], u_value[:48])
+ != o_value[:32]
+ ):
+ return b""
+ iv = bytes(0 for _ in range(16))
+ tmp_key = AlgV5.calculate_hash(R, password, o_value[40:48], u_value[:48])
+ key = aes_cbc_decrypt(tmp_key, iv, oe_value)
+ return key
+
+ @staticmethod
+ def verify_user_password(
+ R: int, password: bytes, u_value: bytes, ue_value: bytes
+ ) -> bytes:
+ """
+ See :func:`verify_owner_password`.
+
+ Args:
+ R: A number specifying which revision of the standard security
+ handler shall be used to interpret this dictionary
+ password: The user password
+ u_value: A 32-byte string, based on the user password, that shall be
+ used in determining whether to prompt the user for a password
+ and, if so, whether a valid user or owner password was entered.
+ ue_value:
+
+ Returns:
+ bytes
+ """
+ password = password[:127]
+ if AlgV5.calculate_hash(R, password, u_value[32:40], b"") != u_value[:32]:
+ return b""
+ iv = bytes(0 for _ in range(16))
+ tmp_key = AlgV5.calculate_hash(R, password, u_value[40:48], b"")
+ return aes_cbc_decrypt(tmp_key, iv, ue_value)
+
+ @staticmethod
+ def calculate_hash(R: int, password: bytes, salt: bytes, udata: bytes) -> bytes:
+ # from https://github.com/qpdf/qpdf/blob/main/libqpdf/QPDF_encryption.cc
+ k = hashlib.sha256(password + salt + udata).digest()
+ if R < 6:
+ return k
+ count = 0
+ while True:
+ count += 1
+ k1 = password + k + udata
+ e = aes_cbc_encrypt(k[:16], k[16:32], k1 * 64)
+ hash_fn = (
+ hashlib.sha256,
+ hashlib.sha384,
+ hashlib.sha512,
+ )[sum(e[:16]) % 3]
+ k = hash_fn(e).digest()
+ if count >= 64 and e[-1] <= count - 32:
+ break
+ return k[:32]
+
+ @staticmethod
+ def verify_perms(
+ key: bytes, perms: bytes, p: int, metadata_encrypted: bool
+ ) -> bool:
+ """
+ See :func:`verify_owner_password` and :func:`compute_perms_value`.
+
+ Args:
+ key: The owner password
+ perms:
+ p: A set of flags specifying which operations shall be permitted
+ when the document is opened with user access.
+ If bit 2 is set to 1, all other bits are ignored and all
+ operations are permitted.
+ If bit 2 is set to 0, permission for operations are based on
+ the values of the remaining flags defined in Table 24.
+ metadata_encrypted:
+
+ Returns:
+ A boolean
+ """
+ b8 = b"T" if metadata_encrypted else b"F"
+ p1 = struct.pack("<I", p) + b"\xff\xff\xff\xff" + b8 + b"adb"
+ p2 = aes_ecb_decrypt(key, perms)
+ return p1 == p2[:12]
+
+ @staticmethod
+ def generate_values(
+ R: int,
+ user_password: bytes,
+ owner_password: bytes,
+ key: bytes,
+ p: int,
+ metadata_encrypted: bool,
+ ) -> Dict[Any, Any]:
+ user_password = user_password[:127]
+ owner_password = owner_password[:127]
+ u_value, ue_value = AlgV5.compute_U_value(R, user_password, key)
+ o_value, oe_value = AlgV5.compute_O_value(R, owner_password, key, u_value)
+ perms = AlgV5.compute_Perms_value(key, p, metadata_encrypted)
+ return {
+ "/U": u_value,
+ "/UE": ue_value,
+ "/O": o_value,
+ "/OE": oe_value,
+ "/Perms": perms,
+ }
+
+ @staticmethod
+ def compute_U_value(R: int, password: bytes, key: bytes) -> Tuple[bytes, bytes]:
+ """
+ Algorithm 3.8 Computing the encryption dictionary’s U (user password)
+ and UE (user encryption key) values.
+
+ 1. Generate 16 random bytes of data using a strong random number generator.
+ The first 8 bytes are the User Validation Salt. The second 8 bytes
+ are the User Key Salt. Compute the 32-byte SHA-256 hash of the
+ password concatenated with the User Validation Salt. The 48-byte
+ string consisting of the 32-byte hash followed by the User
+ Validation Salt followed by the User Key Salt is stored as the U key.
+ 2. Compute the 32-byte SHA-256 hash of the password concatenated with
+ the User Key Salt. Using this hash as the key, encrypt the file
+ encryption key using AES-256 in CBC mode with no padding and an
+ initialization vector of zero. The resulting 32-byte string is stored
+ as the UE key.
+
+ Args:
+ R:
+ password:
+ key:
+
+ Returns:
+ A tuple (u-value, ue value)
+ """
+ random_bytes = secrets.token_bytes(16)
+ val_salt = random_bytes[:8]
+ key_salt = random_bytes[8:]
+ u_value = AlgV5.calculate_hash(R, password, val_salt, b"") + val_salt + key_salt
+
+ tmp_key = AlgV5.calculate_hash(R, password, key_salt, b"")
+ iv = bytes(0 for _ in range(16))
+ ue_value = aes_cbc_encrypt(tmp_key, iv, key)
+ return u_value, ue_value
+
+ @staticmethod
+ def compute_O_value(
+ R: int, password: bytes, key: bytes, u_value: bytes
+ ) -> Tuple[bytes, bytes]:
+ """
+ Algorithm 3.9 Computing the encryption dictionary’s O (owner password)
+ and OE (owner encryption key) values.
+
+ 1. Generate 16 random bytes of data using a strong random number
+ generator. The first 8 bytes are the Owner Validation Salt. The
+ second 8 bytes are the Owner Key Salt. Compute the 32-byte SHA-256
+ hash of the password concatenated with the Owner Validation Salt and
+ then concatenated with the 48-byte U string as generated in
+ Algorithm 3.8. The 48-byte string consisting of the 32-byte hash
+ followed by the Owner Validation Salt followed by the Owner Key Salt
+ is stored as the O key.
+ 2. Compute the 32-byte SHA-256 hash of the password concatenated with
+ the Owner Key Salt and then concatenated with the 48-byte U string as
+ generated in Algorithm 3.8. Using this hash as the key,
+ encrypt the file encryption key using AES-256 in CBC mode with
+ no padding and an initialization vector of zero.
+ The resulting 32-byte string is stored as the OE key.
+
+ Args:
+ R:
+ password:
+ key:
+ u_value: A 32-byte string, based on the user password, that shall be
+ used in determining whether to prompt the user for a password
+ and, if so, whether a valid user or owner password was entered.
+
+ Returns:
+ A tuple (O value, OE value)
+ """
+ random_bytes = secrets.token_bytes(16)
+ val_salt = random_bytes[:8]
+ key_salt = random_bytes[8:]
+ o_value = (
+ AlgV5.calculate_hash(R, password, val_salt, u_value) + val_salt + key_salt
+ )
+ tmp_key = AlgV5.calculate_hash(R, password, key_salt, u_value[:48])
+ iv = bytes(0 for _ in range(16))
+ oe_value = aes_cbc_encrypt(tmp_key, iv, key)
+ return o_value, oe_value
+
+ @staticmethod
+ def compute_Perms_value(key: bytes, p: int, metadata_encrypted: bool) -> bytes:
+ """
+ Algorithm 3.10 Computing the encryption dictionary’s Perms
+ (permissions) value.
+
+ 1. Extend the permissions (contents of the P integer) to 64 bits by
+ setting the upper 32 bits to all 1’s.
+ (This allows for future extension without changing the format.)
+ 2. Record the 8 bytes of permission in the bytes 0-7 of the block,
+ low order byte first.
+ 3. Set byte 8 to the ASCII value ' T ' or ' F ' according to the
+ EncryptMetadata Boolean.
+ 4. Set bytes 9-11 to the ASCII characters ' a ', ' d ', ' b '.
+ 5. Set bytes 12-15 to 4 bytes of random data, which will be ignored.
+ 6. Encrypt the 16-byte block using AES-256 in ECB mode with an
+ initialization vector of zero, using the file encryption key as the
+ key. The result (16 bytes) is stored as the Perms string, and checked
+ for validity when the file is opened.
+
+ Args:
+ key:
+ p: A set of flags specifying which operations shall be permitted
+ when the document is opened with user access. If bit 2 is set to 1,
+ all other bits are ignored and all operations are permitted.
+ If bit 2 is set to 0, permission for operations are based on the
+ values of the remaining flags defined in Table 24.
+ metadata_encrypted: A boolean indicating if the metadata is encrypted.
+
+ Returns:
+ The perms value
+ """
+ b8 = b"T" if metadata_encrypted else b"F"
+ rr = secrets.token_bytes(4)
+ data = struct.pack("<I", p) + b"\xff\xff\xff\xff" + b8 + b"adb" + rr
+ perms = aes_ecb_encrypt(key, data)
+ return perms
+
+
+class PasswordType(IntEnum):
+ NOT_DECRYPTED = 0
+ USER_PASSWORD = 1
+ OWNER_PASSWORD = 2
+
+
+class EncryptAlgorithm(tuple, Enum): # type: ignore # noqa: SLOT001
+ # V, R, Length
+ RC4_40 = (1, 2, 40)
+ RC4_128 = (2, 3, 128)
+ AES_128 = (4, 4, 128)
+ AES_256_R5 = (5, 5, 256)
+ AES_256 = (5, 6, 256)
+
+
+class EncryptionValues:
+ O: bytes # noqa
+ U: bytes
+ OE: bytes
+ UE: bytes
+ Perms: bytes
+
+
+class Encryption:
+ """
+ Collects and manages parameters for PDF document encryption and decryption.
+
+ Args:
+ V: A code specifying the algorithm to be used in encrypting and
+ decrypting the document.
+ R: The revision of the standard security handler.
+ Length: The length of the encryption key in bits.
+ P: A set of flags specifying which operations shall be permitted
+ when the document is opened with user access
+ entry: The encryption dictionary object.
+ EncryptMetadata: Whether to encrypt metadata in the document.
+ first_id_entry: The first 16 bytes of the file's original ID.
+ StmF: The name of the crypt filter that shall be used by default
+ when decrypting streams.
+ StrF: The name of the crypt filter that shall be used when decrypting
+ all strings in the document.
+ EFF: The name of the crypt filter that shall be used when
+ encrypting embedded file streams that do not have their own
+ crypt filter specifier.
+ values: Additional encryption parameters.
+ """
+
+ def __init__(
+ self,
+ V: int,
+ R: int,
+ Length: int,
+ P: int,
+ entry: DictionaryObject,
+ EncryptMetadata: bool,
+ first_id_entry: bytes,
+ StmF: str,
+ StrF: str,
+ EFF: str,
+ values: Optional[EncryptionValues],
+ ) -> None:
+ # §7.6.2, entries common to all encryption dictionaries
+ # use same name as keys of encryption dictionaries entries
+ self.V = V
+ self.R = R
+ self.Length = Length # key_size
+ self.P = (P + 0x100000000) % 0x100000000 # maybe P < 0
+ self.EncryptMetadata = EncryptMetadata
+ self.id1_entry = first_id_entry
+ self.StmF = StmF
+ self.StrF = StrF
+ self.EFF = EFF
+ self.values: EncryptionValues = values if values else EncryptionValues()
+
+ self._password_type = PasswordType.NOT_DECRYPTED
+ self._key: Optional[bytes] = None
+
+ def is_decrypted(self) -> bool:
+ return self._password_type != PasswordType.NOT_DECRYPTED
+
+ def encrypt_object(self, obj: PdfObject, idnum: int, generation: int) -> PdfObject:
+ # skip calculate key
+ if not self._is_encryption_object(obj):
+ return obj
+
+ cf = self._make_crypt_filter(idnum, generation)
+ return cf.encrypt_object(obj)
+
+ def decrypt_object(self, obj: PdfObject, idnum: int, generation: int) -> PdfObject:
+ # skip calculate key
+ if not self._is_encryption_object(obj):
+ return obj
+
+ cf = self._make_crypt_filter(idnum, generation)
+ return cf.decrypt_object(obj)
+
+ @staticmethod
+ def _is_encryption_object(obj: PdfObject) -> bool:
+ return isinstance(
+ obj,
+ (
+ ByteStringObject,
+ TextStringObject,
+ StreamObject,
+ ArrayObject,
+ DictionaryObject,
+ ),
+ )
+
+ def _make_crypt_filter(self, idnum: int, generation: int) -> CryptFilter:
+ """
+ Algorithm 1: Encryption of data using the RC4 or AES algorithms.
+
+ a) Obtain the object number and generation number from the object
+ identifier of the string or stream to be encrypted
+ (see 7.3.10, "Indirect Objects"). If the string is a direct object,
+ use the identifier of the indirect object containing it.
+ b) For all strings and streams without crypt filter specifier; treating
+ the object number and generation number as binary integers, extend
+ the original n-byte encryption key to n + 5 bytes by appending the
+ low-order 3 bytes of the object number and the low-order 2 bytes of
+ the generation number in that order, low-order byte first.
+ (n is 5 unless the value of V in the encryption dictionary is greater
+ than 1, in which case n is the value of Length divided by 8.)
+ If using the AES algorithm, extend the encryption key an additional
+ 4 bytes by adding the value “sAlT”, which corresponds to the
+ hexadecimal values 0x73, 0x41, 0x6C, 0x54. (This addition is done for
+ backward compatibility and is not intended to provide additional
+ security.)
+ c) Initialize the MD5 hash function and pass the result of step (b) as
+ input to this function.
+ d) Use the first (n + 5) bytes, up to a maximum of 16, of the output
+ from the MD5 hash as the key for the RC4 or AES symmetric key
+ algorithms, along with the string or stream data to be encrypted.
+ If using the AES algorithm, the Cipher Block Chaining (CBC) mode,
+ which requires an initialization vector, is used. The block size
+ parameter is set to 16 bytes, and the initialization vector is a
+ 16-byte random number that is stored as the first 16 bytes of the
+ encrypted stream or string.
+
+ Algorithm 3.1a Encryption of data using the AES algorithm
+ 1. Use the 32-byte file encryption key for the AES-256 symmetric key
+ algorithm, along with the string or stream data to be encrypted.
+ Use the AES algorithm in Cipher Block Chaining (CBC) mode, which
+ requires an initialization vector. The block size parameter is set to
+ 16 bytes, and the initialization vector is a 16-byte random number
+ that is stored as the first 16 bytes of the encrypted stream or string.
+ The output is the encrypted data to be stored in the PDF file.
+ """
+ pack1 = struct.pack("<i", idnum)[:3]
+ pack2 = struct.pack("<i", generation)[:2]
+
+ assert self._key
+ key = self._key
+ n = 5 if self.V == 1 else self.Length // 8
+ key_data = key[:n] + pack1 + pack2
+ key_hash = hashlib.md5(key_data)
+ rc4_key = key_hash.digest()[: min(n + 5, 16)]
+ # for AES-128
+ key_hash.update(b"sAlT")
+ aes128_key = key_hash.digest()[: min(n + 5, 16)]
+
+ # for AES-256
+ aes256_key = key
+
+ stm_crypt = self._get_crypt(self.StmF, rc4_key, aes128_key, aes256_key)
+ str_crypt = self._get_crypt(self.StrF, rc4_key, aes128_key, aes256_key)
+ ef_crypt = self._get_crypt(self.EFF, rc4_key, aes128_key, aes256_key)
+
+ return CryptFilter(stm_crypt, str_crypt, ef_crypt)
+
+ @staticmethod
+ def _get_crypt(
+ method: str, rc4_key: bytes, aes128_key: bytes, aes256_key: bytes
+ ) -> CryptBase:
+ if method == "/AESV3":
+ return CryptAES(aes256_key)
+ if method == "/AESV2":
+ return CryptAES(aes128_key)
+ elif method == "/Identity":
+ return CryptIdentity()
+ else:
+ return CryptRC4(rc4_key)
+
+ @staticmethod
+ def _encode_password(password: Union[bytes, str]) -> bytes:
+ if isinstance(password, str):
+ try:
+ pwd = password.encode("latin-1")
+ except Exception:
+ pwd = password.encode("utf-8")
+ else:
+ pwd = password
+ return pwd
+
+ def verify(self, password: Union[bytes, str]) -> PasswordType:
+ pwd = self._encode_password(password)
+ key, rc = self.verify_v4(pwd) if self.V <= 4 else self.verify_v5(pwd)
+ if rc != PasswordType.NOT_DECRYPTED:
+ self._password_type = rc
+ self._key = key
+ return rc
+
+ def verify_v4(self, password: bytes) -> Tuple[bytes, PasswordType]:
+ # verify owner password first
+ key = AlgV4.verify_owner_password(
+ password,
+ self.R,
+ self.Length,
+ self.values.O,
+ self.values.U,
+ self.P,
+ self.id1_entry,
+ self.EncryptMetadata,
+ )
+ if key:
+ return key, PasswordType.OWNER_PASSWORD
+ key = AlgV4.verify_user_password(
+ password,
+ self.R,
+ self.Length,
+ self.values.O,
+ self.values.U,
+ self.P,
+ self.id1_entry,
+ self.EncryptMetadata,
+ )
+ if key:
+ return key, PasswordType.USER_PASSWORD
+ return b"", PasswordType.NOT_DECRYPTED
+
+ def verify_v5(self, password: bytes) -> Tuple[bytes, PasswordType]:
+ # TODO: use SASLprep process
+ # verify owner password first
+ key = AlgV5.verify_owner_password(
+ self.R, password, self.values.O, self.values.OE, self.values.U
+ )
+ rc = PasswordType.OWNER_PASSWORD
+ if not key:
+ key = AlgV5.verify_user_password(
+ self.R, password, self.values.U, self.values.UE
+ )
+ rc = PasswordType.USER_PASSWORD
+ if not key:
+ return b"", PasswordType.NOT_DECRYPTED
+
+ # verify Perms
+ if not AlgV5.verify_perms(key, self.values.Perms, self.P, self.EncryptMetadata):
+ logger_warning("ignore '/Perms' verify failed", __name__)
+ return key, rc
+
+ def write_entry(
+ self, user_password: str, owner_password: Optional[str]
+ ) -> DictionaryObject:
+ user_pwd = self._encode_password(user_password)
+ owner_pwd = self._encode_password(owner_password) if owner_password else None
+ if owner_pwd is None:
+ owner_pwd = user_pwd
+
+ if self.V <= 4:
+ self.compute_values_v4(user_pwd, owner_pwd)
+ else:
+ self._key = secrets.token_bytes(self.Length // 8)
+ values = AlgV5.generate_values(
+ self.R, user_pwd, owner_pwd, self._key, self.P, self.EncryptMetadata
+ )
+ self.values.O = values["/O"]
+ self.values.U = values["/U"]
+ self.values.OE = values["/OE"]
+ self.values.UE = values["/UE"]
+ self.values.Perms = values["/Perms"]
+
+ dict_obj = DictionaryObject()
+ dict_obj[NameObject("/V")] = NumberObject(self.V)
+ dict_obj[NameObject("/R")] = NumberObject(self.R)
+ dict_obj[NameObject("/Length")] = NumberObject(self.Length)
+ dict_obj[NameObject("/P")] = NumberObject(self.P)
+ dict_obj[NameObject("/Filter")] = NameObject("/Standard")
+ # ignore /EncryptMetadata
+
+ dict_obj[NameObject("/O")] = ByteStringObject(self.values.O)
+ dict_obj[NameObject("/U")] = ByteStringObject(self.values.U)
+
+ if self.V >= 4:
+ # TODO: allow different method
+ std_cf = DictionaryObject()
+ std_cf[NameObject("/AuthEvent")] = NameObject("/DocOpen")
+ std_cf[NameObject("/CFM")] = NameObject(self.StmF)
+ std_cf[NameObject("/Length")] = NumberObject(self.Length // 8)
+ cf = DictionaryObject()
+ cf[NameObject("/StdCF")] = std_cf
+ dict_obj[NameObject("/CF")] = cf
+ dict_obj[NameObject("/StmF")] = NameObject("/StdCF")
+ dict_obj[NameObject("/StrF")] = NameObject("/StdCF")
+ # ignore EFF
+ # dict_obj[NameObject("/EFF")] = NameObject("/StdCF")
+
+ if self.V >= 5:
+ dict_obj[NameObject("/OE")] = ByteStringObject(self.values.OE)
+ dict_obj[NameObject("/UE")] = ByteStringObject(self.values.UE)
+ dict_obj[NameObject("/Perms")] = ByteStringObject(self.values.Perms)
+ return dict_obj
+
+ def compute_values_v4(self, user_password: bytes, owner_password: bytes) -> None:
+ rc4_key = AlgV4.compute_O_value_key(owner_password, self.R, self.Length)
+ o_value = AlgV4.compute_O_value(rc4_key, user_password, self.R)
+
+ key = AlgV4.compute_key(
+ user_password,
+ self.R,
+ self.Length,
+ o_value,
+ self.P,
+ self.id1_entry,
+ self.EncryptMetadata,
+ )
+ u_value = AlgV4.compute_U_value(key, self.R, self.id1_entry)
+
+ self._key = key
+ self.values.O = o_value
+ self.values.U = u_value
+
+ @staticmethod
+ def read(encryption_entry: DictionaryObject, first_id_entry: bytes) -> "Encryption":
+ if encryption_entry.get("/Filter") != "/Standard":
+ raise NotImplementedError(
+ "only Standard PDF encryption handler is available"
+ )
+ if "/SubFilter" in encryption_entry:
+ raise NotImplementedError("/SubFilter NOT supported")
+
+ stm_filter = "/V2"
+ str_filter = "/V2"
+ ef_filter = "/V2"
+
+ alg_ver = encryption_entry.get("/V", 0)
+ if alg_ver not in (1, 2, 3, 4, 5):
+ raise NotImplementedError(f"Encryption V={alg_ver} NOT supported")
+ if alg_ver >= 4:
+ filters = encryption_entry["/CF"]
+
+ stm_filter = encryption_entry.get("/StmF", "/Identity")
+ str_filter = encryption_entry.get("/StrF", "/Identity")
+ ef_filter = encryption_entry.get("/EFF", stm_filter)
+
+ if stm_filter != "/Identity":
+ stm_filter = filters[stm_filter]["/CFM"] # type: ignore
+ if str_filter != "/Identity":
+ str_filter = filters[str_filter]["/CFM"] # type: ignore
+ if ef_filter != "/Identity":
+ ef_filter = filters[ef_filter]["/CFM"] # type: ignore
+
+ allowed_methods = ("/Identity", "/V2", "/AESV2", "/AESV3")
+ if stm_filter not in allowed_methods:
+ raise NotImplementedError(f"StmF Method {stm_filter} NOT supported!")
+ if str_filter not in allowed_methods:
+ raise NotImplementedError(f"StrF Method {str_filter} NOT supported!")
+ if ef_filter not in allowed_methods:
+ raise NotImplementedError(f"EFF Method {ef_filter} NOT supported!")
+
+ alg_rev = cast(int, encryption_entry["/R"])
+ perm_flags = cast(int, encryption_entry["/P"])
+ key_bits = encryption_entry.get("/Length", 40)
+ encrypt_metadata = encryption_entry.get("/EncryptMetadata")
+ encrypt_metadata = (
+ encrypt_metadata.value if encrypt_metadata is not None else True
+ )
+ values = EncryptionValues()
+ values.O = cast(ByteStringObject, encryption_entry["/O"]).original_bytes
+ values.U = cast(ByteStringObject, encryption_entry["/U"]).original_bytes
+ values.OE = encryption_entry.get("/OE", ByteStringObject()).original_bytes
+ values.UE = encryption_entry.get("/UE", ByteStringObject()).original_bytes
+ values.Perms = encryption_entry.get("/Perms", ByteStringObject()).original_bytes
+ return Encryption(
+ V=alg_ver,
+ R=alg_rev,
+ Length=key_bits,
+ P=perm_flags,
+ EncryptMetadata=encrypt_metadata,
+ first_id_entry=first_id_entry,
+ values=values,
+ StrF=str_filter,
+ StmF=stm_filter,
+ EFF=ef_filter,
+ entry=encryption_entry, # Dummy entry for the moment; will get removed
+ )
+
+ @staticmethod
+ def make(
+ alg: EncryptAlgorithm, permissions: int, first_id_entry: bytes
+ ) -> "Encryption":
+ alg_ver, alg_rev, key_bits = alg
+
+ stm_filter, str_filter, ef_filter = "/V2", "/V2", "/V2"
+
+ if alg == EncryptAlgorithm.AES_128:
+ stm_filter, str_filter, ef_filter = "/AESV2", "/AESV2", "/AESV2"
+ elif alg in (EncryptAlgorithm.AES_256_R5, EncryptAlgorithm.AES_256):
+ stm_filter, str_filter, ef_filter = "/AESV3", "/AESV3", "/AESV3"
+
+ return Encryption(
+ V=alg_ver,
+ R=alg_rev,
+ Length=key_bits,
+ P=permissions,
+ EncryptMetadata=True,
+ first_id_entry=first_id_entry,
+ values=None,
+ StrF=str_filter,
+ StmF=stm_filter,
+ EFF=ef_filter,
+ entry=DictionaryObject(), # Dummy entry for the moment; will get removed
+ )
diff --git a/.venv/lib/python3.12/site-packages/pypdf/_merger.py b/.venv/lib/python3.12/site-packages/pypdf/_merger.py
new file mode 100644
index 00000000..7176a1ad
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/pypdf/_merger.py
@@ -0,0 +1,678 @@
+# Copyright (c) 2006, Mathieu Fenniak
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
+#
+# * Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
+# * Redistributions in binary form must reproduce the above copyright notice,
+# this list of conditions and the following disclaimer in the documentation
+# and/or other materials provided with the distribution.
+# * The name of the author may not be used to endorse or promote products
+# derived from this software without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+# POSSIBILITY OF SUCH DAMAGE.
+
+from io import BytesIO, FileIO, IOBase
+from pathlib import Path
+from types import TracebackType
+from typing import (
+ Any,
+ Dict,
+ Iterable,
+ List,
+ Optional,
+ Tuple,
+ Type,
+ Union,
+ cast,
+)
+
+from ._encryption import Encryption
+from ._page import PageObject
+from ._reader import PdfReader
+from ._utils import (
+ StrByteType,
+ deprecate_with_replacement,
+ str_,
+)
+from ._writer import PdfWriter
+from .constants import GoToActionArguments, TypArguments, TypFitArguments
+from .constants import PagesAttributes as PA
+from .generic import (
+ PAGE_FIT,
+ ArrayObject,
+ Destination,
+ DictionaryObject,
+ Fit,
+ FloatObject,
+ IndirectObject,
+ NameObject,
+ NullObject,
+ NumberObject,
+ OutlineItem,
+ TextStringObject,
+ TreeObject,
+)
+from .pagerange import PageRange, PageRangeSpec
+from .types import LayoutType, OutlineType, PagemodeType
+
+ERR_CLOSED_WRITER = "close() was called and thus the writer cannot be used anymore"
+
+
+class _MergedPage:
+ """Collect necessary information on each page that is being merged."""
+
+ def __init__(self, pagedata: PageObject, src: PdfReader, id: int) -> None:
+ self.src = src
+ self.pagedata = pagedata
+ self.out_pagedata = None
+ self.id = id
+
+
+class PdfMerger:
+ """
+ Use :class:`PdfWriter` instead.
+
+ .. deprecated:: 5.0.0
+ """
+
+ def __init__(
+ self, strict: bool = False, fileobj: Union[Path, StrByteType] = ""
+ ) -> None:
+ deprecate_with_replacement("PdfMerger", "PdfWriter", "5.0.0")
+ self.inputs: List[Tuple[Any, PdfReader]] = []
+ self.pages: List[Any] = []
+ self.output: Optional[PdfWriter] = PdfWriter()
+ self.outline: OutlineType = []
+ self.named_dests: List[Any] = []
+ self.id_count = 0
+ self.fileobj = fileobj
+ self.strict = strict
+
+ def __enter__(self) -> "PdfMerger":
+ # There is nothing to do.
+ deprecate_with_replacement("PdfMerger", "PdfWriter", "5.0.0")
+ return self
+
+ def __exit__(
+ self,
+ exc_type: Optional[Type[BaseException]],
+ exc: Optional[BaseException],
+ traceback: Optional[TracebackType],
+ ) -> None:
+ """Write to the fileobj and close the merger."""
+ if self.fileobj:
+ self.write(self.fileobj)
+ self.close()
+
+ def merge(
+ self,
+ page_number: int,
+ fileobj: Union[Path, StrByteType, PdfReader],
+ outline_item: Optional[str] = None,
+ pages: Optional[PageRangeSpec] = None,
+ import_outline: bool = True,
+ ) -> None:
+ """
+ Merge the pages from the given file into the output file at the
+ specified page number.
+
+ Args:
+ page_number: The *page number* to insert this file. File will
+ be inserted after the given number.
+ fileobj: A File Object or an object that supports the standard
+ read and seek methods similar to a File Object. Could also be a
+ string representing a path to a PDF file.
+ outline_item: Optionally, you may specify an outline item
+ (previously referred to as a 'bookmark') to be applied at the
+ beginning of the included file by supplying the text of the outline item.
+ pages: can be a :class:`PageRange<pypdf.pagerange.PageRange>`
+ or a ``(start, stop[, step])`` tuple
+ to merge only the specified range of pages from the source
+ document into the output document.
+ Can also be a list of pages to merge.
+ import_outline: You may prevent the source document's
+ outline (collection of outline items, previously referred to as
+ 'bookmarks') from being imported by specifying this as ``False``.
+ """
+ stream, encryption_obj = self._create_stream(fileobj)
+
+ # Create a new PdfReader instance using the stream
+ # (either file or BytesIO or StringIO) created above
+ reader = PdfReader(stream, strict=self.strict) # type: ignore[arg-type]
+ self.inputs.append((stream, reader))
+ if encryption_obj is not None:
+ reader._encryption = encryption_obj
+
+ # Find the range of pages to merge.
+ if pages is None:
+ pages = (0, len(reader.pages))
+ elif isinstance(pages, PageRange):
+ pages = pages.indices(len(reader.pages))
+ elif isinstance(pages, list):
+ pass
+ elif not isinstance(pages, tuple):
+ raise TypeError('"pages" must be a tuple of (start, stop[, step])')
+
+ srcpages = []
+
+ outline = []
+ if import_outline:
+ outline = reader.outline
+ outline = self._trim_outline(reader, outline, pages)
+
+ if outline_item:
+ outline_item_typ = OutlineItem(
+ TextStringObject(outline_item),
+ NumberObject(self.id_count),
+ Fit.fit(),
+ )
+ self.outline += [outline_item_typ, outline] # type: ignore
+ else:
+ self.outline += outline
+
+ dests = reader.named_destinations
+ trimmed_dests = self._trim_dests(reader, dests, pages)
+ self.named_dests += trimmed_dests
+
+ # Gather all the pages that are going to be merged
+ for i in range(*pages):
+ page = reader.pages[i]
+
+ id = self.id_count
+ self.id_count += 1
+
+ mp = _MergedPage(page, reader, id)
+
+ srcpages.append(mp)
+
+ self._associate_dests_to_pages(srcpages)
+ self._associate_outline_items_to_pages(srcpages)
+
+ # Slice to insert the pages at the specified page_number
+ self.pages[page_number:page_number] = srcpages
+
+ def _create_stream(
+ self, fileobj: Union[Path, StrByteType, PdfReader]
+ ) -> Tuple[IOBase, Optional[Encryption]]:
+ # If the fileobj parameter is a string, assume it is a path
+ # and create a file object at that location. If it is a file,
+ # copy the file's contents into a BytesIO stream object; if
+ # it is a PdfReader, copy that reader's stream into a
+ # BytesIO stream.
+ # If fileobj is none of the above types, it is not modified
+ encryption_obj = None
+ stream: IOBase
+ if isinstance(fileobj, (str, Path)):
+ stream = FileIO(fileobj, "rb")
+ elif isinstance(fileobj, PdfReader):
+ if fileobj._encryption:
+ encryption_obj = fileobj._encryption
+ orig_tell = fileobj.stream.tell()
+ fileobj.stream.seek(0)
+ stream = BytesIO(fileobj.stream.read())
+
+ # reset the stream to its original location
+ fileobj.stream.seek(orig_tell)
+ elif hasattr(fileobj, "seek") and hasattr(fileobj, "read"):
+ fileobj.seek(0)
+ file_content = fileobj.read()
+ stream = BytesIO(file_content)
+ else:
+ raise NotImplementedError(
+ "PdfMerger.merge requires an object that PdfReader can parse. "
+ "Typically, that is a Path or a string representing a Path, "
+ "a file object, or an object implementing .seek and .read. "
+ "Passing a PdfReader directly works as well."
+ )
+ return stream, encryption_obj
+
+ def append(
+ self,
+ fileobj: Union[StrByteType, PdfReader, Path],
+ outline_item: Optional[str] = None,
+ pages: Union[
+ None, PageRange, Tuple[int, int], Tuple[int, int, int], List[int]
+ ] = None,
+ import_outline: bool = True,
+ ) -> None:
+ """
+ Identical to the :meth:`merge()<merge>` method, but assumes you want to
+ concatenate all pages onto the end of the file instead of specifying a
+ position.
+
+ Args:
+ fileobj: A File Object or an object that supports the standard
+ read and seek methods similar to a File Object. Could also be a
+ string representing a path to a PDF file.
+ outline_item: Optionally, you may specify an outline item
+ (previously referred to as a 'bookmark') to be applied at the
+ beginning of the included file by supplying the text of the outline item.
+ pages: can be a :class:`PageRange<pypdf.pagerange.PageRange>`
+ or a ``(start, stop[, step])`` tuple
+ to merge only the specified range of pages from the source
+ document into the output document.
+ Can also be a list of pages to append.
+ import_outline: You may prevent the source document's
+ outline (collection of outline items, previously referred to as
+ 'bookmarks') from being imported by specifying this as ``False``.
+ """
+ self.merge(len(self.pages), fileobj, outline_item, pages, import_outline)
+
+ def write(self, fileobj: Union[Path, StrByteType]) -> None:
+ """
+ Write all data that has been merged to the given output file.
+
+ Args:
+ fileobj: Output file. Can be a filename or any kind of
+ file-like object.
+ """
+ if self.output is None:
+ raise RuntimeError(ERR_CLOSED_WRITER)
+
+ # Add pages to the PdfWriter
+ # The commented out line below was replaced with the two lines below it
+ # to allow PdfMerger to work with PyPdf 1.13
+ for page in self.pages:
+ self.output.add_page(page.pagedata)
+ pages_obj = cast(Dict[str, Any], self.output._pages.get_object())
+ page.out_pagedata = self.output.get_reference(
+ pages_obj[PA.KIDS][-1].get_object()
+ )
+
+ # Once all pages are added, create outline items to point at those pages
+ self._write_dests()
+ self._write_outline()
+
+ # Write the output to the file
+ my_file, ret_fileobj = self.output.write(fileobj)
+
+ if my_file:
+ ret_fileobj.close()
+
+ def close(self) -> None:
+ """Shut all file descriptors (input and output) and clear all memory usage."""
+ self.pages = []
+ for file_descriptor, _reader in self.inputs:
+ file_descriptor.close()
+
+ self.inputs = []
+ self.output = None
+
+ def add_metadata(self, infos: Dict[str, Any]) -> None:
+ """
+ Add custom metadata to the output.
+
+ Args:
+ infos: a Python dictionary where each key is a field
+ and each value is your new metadata.
+ An example is ``{'/Title': 'My title'}``
+ """
+ if self.output is None:
+ raise RuntimeError(ERR_CLOSED_WRITER)
+ self.output.add_metadata(infos)
+
+ def set_page_layout(self, layout: LayoutType) -> None:
+ """
+ Set the page layout.
+
+ Args:
+ layout: The page layout to be used
+
+ .. list-table:: Valid ``layout`` arguments
+ :widths: 50 200
+
+ * - /NoLayout
+ - Layout explicitly not specified
+ * - /SinglePage
+ - Show one page at a time
+ * - /OneColumn
+ - Show one column at a time
+ * - /TwoColumnLeft
+ - Show pages in two columns, odd-numbered pages on the left
+ * - /TwoColumnRight
+ - Show pages in two columns, odd-numbered pages on the right
+ * - /TwoPageLeft
+ - Show two pages at a time, odd-numbered pages on the left
+ * - /TwoPageRight
+ - Show two pages at a time, odd-numbered pages on the right
+ """
+ if self.output is None:
+ raise RuntimeError(ERR_CLOSED_WRITER)
+ self.output._set_page_layout(layout)
+
+ def set_page_mode(self, mode: PagemodeType) -> None:
+ """
+ Set the page mode.
+
+ Args:
+ mode: The page mode to use.
+
+ .. list-table:: Valid ``mode`` arguments
+ :widths: 50 200
+
+ * - /UseNone
+ - Do not show outline or thumbnails panels
+ * - /UseOutlines
+ - Show outline (aka bookmarks) panel
+ * - /UseThumbs
+ - Show page thumbnails panel
+ * - /FullScreen
+ - Fullscreen view
+ * - /UseOC
+ - Show Optional Content Group (OCG) panel
+ * - /UseAttachments
+ - Show attachments panel
+ """
+ self.page_mode = mode
+
+ @property
+ def page_mode(self) -> Optional[PagemodeType]:
+ """
+ Set the page mode.
+
+ Args:
+ mode: The page mode to use.
+
+ .. list-table:: Valid ``mode`` arguments
+ :widths: 50 200
+
+ * - /UseNone
+ - Do not show outline or thumbnails panels
+ * - /UseOutlines
+ - Show outline (aka bookmarks) panel
+ * - /UseThumbs
+ - Show page thumbnails panel
+ * - /FullScreen
+ - Fullscreen view
+ * - /UseOC
+ - Show Optional Content Group (OCG) panel
+ * - /UseAttachments
+ - Show attachments panel
+ """
+ if self.output is None:
+ raise RuntimeError(ERR_CLOSED_WRITER)
+ return self.output.page_mode
+
+ @page_mode.setter
+ def page_mode(self, mode: PagemodeType) -> None:
+ if self.output is None:
+ raise RuntimeError(ERR_CLOSED_WRITER)
+ self.output.page_mode = mode
+
+ def _trim_dests(
+ self,
+ pdf: PdfReader,
+ dests: Dict[str, Dict[str, Any]],
+ pages: Union[Tuple[int, int], Tuple[int, int, int], List[int]],
+ ) -> List[Dict[str, Any]]:
+ """
+ Remove named destinations that are not a part of the specified page set.
+
+ Args:
+ pdf:
+ dests:
+ pages:
+ """
+ new_dests = []
+ lst = pages if isinstance(pages, list) else list(range(*pages))
+ for key, obj in dests.items():
+ for j in lst:
+ if pdf.pages[j].get_object() == obj["/Page"].get_object():
+ obj[NameObject("/Page")] = obj["/Page"].get_object()
+ assert str_(key) == str_(obj["/Title"])
+ new_dests.append(obj)
+ break
+ return new_dests
+
+ def _trim_outline(
+ self,
+ pdf: PdfReader,
+ outline: OutlineType,
+ pages: Union[Tuple[int, int], Tuple[int, int, int], List[int]],
+ ) -> OutlineType:
+ """
+ Remove outline item entries that are not a part of the specified page set.
+
+ Args:
+ pdf:
+ outline:
+ pages:
+
+ Returns:
+ An outline type
+ """
+ new_outline = []
+ prev_header_added = True
+ lst = pages if isinstance(pages, list) else list(range(*pages))
+ for i, outline_item in enumerate(outline):
+ if isinstance(outline_item, list):
+ sub = self._trim_outline(pdf, outline_item, lst) # type: ignore
+ if sub:
+ if not prev_header_added:
+ new_outline.append(outline[i - 1])
+ new_outline.append(sub) # type: ignore
+ else:
+ prev_header_added = False
+ for j in lst:
+ if outline_item["/Page"] is None:
+ continue
+ if pdf.pages[j].get_object() == outline_item["/Page"].get_object():
+ outline_item[NameObject("/Page")] = outline_item[
+ "/Page"
+ ].get_object()
+ new_outline.append(outline_item)
+ prev_header_added = True
+ break
+ return new_outline
+
+ def _write_dests(self) -> None:
+ if self.output is None:
+ raise RuntimeError(ERR_CLOSED_WRITER)
+ for named_dest in self.named_dests:
+ page_index = None
+ if "/Page" in named_dest: # deprecated
+ for page_index, page in enumerate(self.pages): # noqa: B007
+ if page.id == named_dest["/Page"]:
+ named_dest[NameObject("/Page")] = page.out_pagedata
+ break
+
+ if page_index is not None: # deprecated
+ self.output.add_named_destination_object(named_dest)
+
+ def _write_outline(
+ self,
+ outline: Optional[Iterable[OutlineItem]] = None,
+ parent: Optional[TreeObject] = None,
+ ) -> None:
+ if self.output is None:
+ raise RuntimeError(ERR_CLOSED_WRITER)
+ if outline is None:
+ outline = self.outline # type: ignore
+ assert outline is not None, "hint for mypy" # TODO: is that true?
+
+ last_added = None
+ for outline_item in outline:
+ if isinstance(outline_item, list):
+ self._write_outline(outline_item, last_added)
+ continue
+
+ page_no = None
+ if "/Page" in outline_item:
+ for page_no, page in enumerate(self.pages): # noqa: B007
+ if page.id == outline_item["/Page"]:
+ self._write_outline_item_on_page(outline_item, page)
+ break
+ if page_no is not None:
+ del outline_item["/Page"], outline_item["/Type"]
+ last_added = self.output.add_outline_item_dict(outline_item, parent)
+
+ def _write_outline_item_on_page(
+ self, outline_item: Union[OutlineItem, Destination], page: _MergedPage
+ ) -> None:
+ oi_type = cast(str, outline_item["/Type"])
+ args = [NumberObject(page.id), NameObject(oi_type)]
+ fit2arg_keys: Dict[str, Tuple[str, ...]] = {
+ TypFitArguments.FIT_H: (TypArguments.TOP,),
+ TypFitArguments.FIT_BH: (TypArguments.TOP,),
+ TypFitArguments.FIT_V: (TypArguments.LEFT,),
+ TypFitArguments.FIT_BV: (TypArguments.LEFT,),
+ TypFitArguments.XYZ: (TypArguments.LEFT, TypArguments.TOP, "/Zoom"),
+ TypFitArguments.FIT_R: (
+ TypArguments.LEFT,
+ TypArguments.BOTTOM,
+ TypArguments.RIGHT,
+ TypArguments.TOP,
+ ),
+ }
+ for arg_key in fit2arg_keys.get(oi_type, ()):
+ if arg_key in outline_item and not isinstance(
+ outline_item[arg_key], NullObject
+ ):
+ args.append(FloatObject(outline_item[arg_key]))
+ else:
+ args.append(FloatObject(0))
+ del outline_item[arg_key]
+
+ outline_item[NameObject("/A")] = DictionaryObject(
+ {
+ NameObject(GoToActionArguments.S): NameObject("/GoTo"),
+ NameObject(GoToActionArguments.D): ArrayObject(args),
+ }
+ )
+
+ def _associate_dests_to_pages(self, pages: List[_MergedPage]) -> None:
+ for named_dest in self.named_dests:
+ page_index = None
+ np = named_dest["/Page"]
+
+ if isinstance(np, NumberObject):
+ continue
+
+ for page in pages:
+ if np.get_object() == page.pagedata.get_object():
+ page_index = page.id
+
+ if page_index is None: # deprecated
+ raise ValueError(
+ f"Unresolved named destination '{named_dest['/Title']}'"
+ )
+ named_dest[NameObject("/Page")] = NumberObject(page_index)
+
+ def _associate_outline_items_to_pages(
+ self, pages: List[_MergedPage], outline: Optional[Iterable[OutlineItem]] = None
+ ) -> None:
+ if outline is None:
+ outline = self.outline # type: ignore # TODO: self.bookmarks can be None!
+ assert outline is not None, "hint for mypy"
+ for outline_item in outline:
+ if isinstance(outline_item, list):
+ self._associate_outline_items_to_pages(pages, outline_item)
+ continue
+
+ page_index = None
+ outline_item_page = outline_item["/Page"]
+
+ if isinstance(outline_item_page, NumberObject):
+ continue
+
+ for p in pages:
+ if outline_item_page.get_object() == p.pagedata.get_object():
+ page_index = p.id
+
+ if page_index is not None:
+ outline_item[NameObject("/Page")] = NumberObject(page_index)
+
+ def find_outline_item(
+ self,
+ outline_item: Dict[str, Any],
+ root: Optional[OutlineType] = None,
+ ) -> Optional[List[int]]:
+ if root is None:
+ root = self.outline
+
+ for i, oi_enum in enumerate(root):
+ if isinstance(oi_enum, list):
+ # oi_enum is still an inner node
+ # (OutlineType, if recursive types were supported by mypy)
+ res = self.find_outline_item(outline_item, oi_enum) # type: ignore
+ if res: # deprecated
+ return [i] + res
+ elif (
+ oi_enum == outline_item
+ or cast(Dict[Any, Any], oi_enum["/Title"]) == outline_item
+ ):
+ # we found a leaf node
+ return [i]
+
+ return None
+
+ def add_outline_item(
+ self,
+ title: str,
+ page_number: int,
+ parent: Union[None, TreeObject, IndirectObject] = None,
+ color: Optional[Tuple[float, float, float]] = None,
+ bold: bool = False,
+ italic: bool = False,
+ fit: Fit = PAGE_FIT,
+ ) -> IndirectObject:
+ """
+ Add an outline item (commonly referred to as a "Bookmark") to this PDF file.
+
+ Args:
+ title: Title to use for this outline item.
+ page_number: Page number this outline item will point to.
+ parent: A reference to a parent outline item to create nested
+ outline items.
+ color: Color of the outline item's font as a red, green, blue tuple
+ from 0.0 to 1.0
+ bold: Outline item font is bold
+ italic: Outline item font is italic
+ fit: The fit of the destination page.
+ """
+ writer = self.output
+ if writer is None:
+ raise RuntimeError(ERR_CLOSED_WRITER)
+ return writer.add_outline_item(
+ title,
+ page_number,
+ parent,
+ None,
+ color,
+ bold,
+ italic,
+ fit,
+ )
+
+ def add_named_destination(
+ self,
+ title: str,
+ page_number: int,
+ ) -> None:
+ """
+ Add a destination to the output.
+
+ Args:
+ title: Title to use
+ page_number: Page number this destination points at.
+ """
+ dest = Destination(
+ TextStringObject(title),
+ NumberObject(page_number),
+ Fit.fit_horizontally(top=826),
+ )
+ self.named_dests.append(dest)
diff --git a/.venv/lib/python3.12/site-packages/pypdf/_page.py b/.venv/lib/python3.12/site-packages/pypdf/_page.py
new file mode 100644
index 00000000..63038d9d
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/pypdf/_page.py
@@ -0,0 +1,2458 @@
+# Copyright (c) 2006, Mathieu Fenniak
+# Copyright (c) 2007, Ashish Kulkarni <kulkarni.ashish@gmail.com>
+#
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
+#
+# * Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
+# * Redistributions in binary form must reproduce the above copyright notice,
+# this list of conditions and the following disclaimer in the documentation
+# and/or other materials provided with the distribution.
+# * The name of the author may not be used to endorse or promote products
+# derived from this software without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+# POSSIBILITY OF SUCH DAMAGE.
+
+import math
+import sys
+from decimal import Decimal
+from pathlib import Path
+from typing import (
+ Any,
+ Callable,
+ Dict,
+ Iterable,
+ Iterator,
+ List,
+ Optional,
+ Sequence,
+ Set,
+ Tuple,
+ Union,
+ cast,
+ overload,
+)
+
+from ._cmap import build_char_map, unknown_char_map
+from ._protocols import PdfCommonDocProtocol
+from ._text_extraction import (
+ OrientationNotFoundError,
+ _layout_mode,
+ crlf_space_check,
+ handle_tj,
+ mult,
+)
+from ._utils import (
+ CompressedTransformationMatrix,
+ File,
+ ImageFile,
+ TransformationMatrixType,
+ logger_warning,
+ matrix_multiply,
+)
+from .constants import AnnotationDictionaryAttributes as ADA
+from .constants import ImageAttributes as IA
+from .constants import PageAttributes as PG
+from .constants import Resources as RES
+from .errors import PageSizeNotDefinedError, PdfReadError
+from .filters import _xobj_to_image
+from .generic import (
+ ArrayObject,
+ ContentStream,
+ DictionaryObject,
+ EncodedStreamObject,
+ FloatObject,
+ IndirectObject,
+ NameObject,
+ NullObject,
+ NumberObject,
+ PdfObject,
+ RectangleObject,
+ StreamObject,
+)
+
+if sys.version_info >= (3, 8):
+ from typing import Literal
+else:
+ from typing_extensions import Literal
+
+
+MERGE_CROP_BOX = "cropbox" # pypdf<=3.4.0 used 'trimbox'
+
+
+def _get_rectangle(self: Any, name: str, defaults: Iterable[str]) -> RectangleObject:
+ retval: Union[None, RectangleObject, IndirectObject] = self.get(name)
+ if isinstance(retval, RectangleObject):
+ return retval
+ if retval is None:
+ for d in defaults:
+ retval = self.get(d)
+ if retval is not None:
+ break
+ if isinstance(retval, IndirectObject):
+ retval = self.pdf.get_object(retval)
+ retval = RectangleObject(retval) # type: ignore
+ _set_rectangle(self, name, retval)
+ return retval
+
+
+def _set_rectangle(self: Any, name: str, value: Union[RectangleObject, float]) -> None:
+ name = NameObject(name)
+ self[name] = value
+
+
+def _delete_rectangle(self: Any, name: str) -> None:
+ del self[name]
+
+
+def _create_rectangle_accessor(name: str, fallback: Iterable[str]) -> property:
+ return property(
+ lambda self: _get_rectangle(self, name, fallback),
+ lambda self, value: _set_rectangle(self, name, value),
+ lambda self: _delete_rectangle(self, name),
+ )
+
+
+class Transformation:
+ """
+ Represent a 2D transformation.
+
+ The transformation between two coordinate systems is represented by a 3-by-3
+ transformation matrix matrix with the following form::
+
+ a b 0
+ c d 0
+ e f 1
+
+ Because a transformation matrix has only six elements that can be changed,
+ it is usually specified in PDF as the six-element array [ a b c d e f ].
+
+ Coordinate transformations are expressed as matrix multiplications::
+
+ a b 0
+ [ x′ y′ 1 ] = [ x y 1 ] × c d 0
+ e f 1
+
+
+ Example:
+ >>> from pypdf import Transformation
+ >>> op = Transformation().scale(sx=2, sy=3).translate(tx=10, ty=20)
+ >>> page.add_transformation(op)
+ """
+
+ # 9.5.4 Coordinate Systems for 3D
+ # 4.2.2 Common Transformations
+ def __init__(self, ctm: CompressedTransformationMatrix = (1, 0, 0, 1, 0, 0)):
+ self.ctm = ctm
+
+ @property
+ def matrix(self) -> TransformationMatrixType:
+ """
+ Return the transformation matrix as a tuple of tuples in the form:
+
+ ((a, b, 0), (c, d, 0), (e, f, 1))
+ """
+ return (
+ (self.ctm[0], self.ctm[1], 0),
+ (self.ctm[2], self.ctm[3], 0),
+ (self.ctm[4], self.ctm[5], 1),
+ )
+
+ @staticmethod
+ def compress(matrix: TransformationMatrixType) -> CompressedTransformationMatrix:
+ """
+ Compresses the transformation matrix into a tuple of (a, b, c, d, e, f).
+
+ Args:
+ matrix: The transformation matrix as a tuple of tuples.
+
+ Returns:
+ A tuple representing the transformation matrix as (a, b, c, d, e, f)
+ """
+ return (
+ matrix[0][0],
+ matrix[0][1],
+ matrix[1][0],
+ matrix[1][1],
+ matrix[2][0],
+ matrix[2][1],
+ )
+
+ def transform(self, m: "Transformation") -> "Transformation":
+ """
+ Apply one transformation to another.
+
+ Args:
+ m: a Transformation to apply.
+
+ Returns:
+ A new ``Transformation`` instance
+
+ Example:
+ >>> from pypdf import Transformation
+ >>> op = Transformation((1, 0, 0, -1, 0, height)) # vertical mirror
+ >>> op = Transformation().transform(Transformation((-1, 0, 0, 1, iwidth, 0))) # horizontal mirror
+ >>> page.add_transformation(op)
+ """
+ ctm = Transformation.compress(matrix_multiply(self.matrix, m.matrix))
+ return Transformation(ctm)
+
+ def translate(self, tx: float = 0, ty: float = 0) -> "Transformation":
+ """
+ Translate the contents of a page.
+
+ Args:
+ tx: The translation along the x-axis.
+ ty: The translation along the y-axis.
+
+ Returns:
+ A new ``Transformation`` instance
+ """
+ m = self.ctm
+ return Transformation(ctm=(m[0], m[1], m[2], m[3], m[4] + tx, m[5] + ty))
+
+ def scale(
+ self, sx: Optional[float] = None, sy: Optional[float] = None
+ ) -> "Transformation":
+ """
+ Scale the contents of a page towards the origin of the coordinate system.
+
+ Typically, that is the lower-left corner of the page. That can be
+ changed by translating the contents / the page boxes.
+
+ Args:
+ sx: The scale factor along the x-axis.
+ sy: The scale factor along the y-axis.
+
+ Returns:
+ A new Transformation instance with the scaled matrix.
+ """
+ if sx is None and sy is None:
+ raise ValueError("Either sx or sy must be specified")
+ if sx is None:
+ sx = sy
+ if sy is None:
+ sy = sx
+ assert sx is not None
+ assert sy is not None
+ op: TransformationMatrixType = ((sx, 0, 0), (0, sy, 0), (0, 0, 1))
+ ctm = Transformation.compress(matrix_multiply(self.matrix, op))
+ return Transformation(ctm)
+
+ def rotate(self, rotation: float) -> "Transformation":
+ """
+ Rotate the contents of a page.
+
+ Args:
+ rotation: The angle of rotation in degrees.
+
+ Returns:
+ A new ``Transformation`` instance with the rotated matrix.
+ """
+ rotation = math.radians(rotation)
+ op: TransformationMatrixType = (
+ (math.cos(rotation), math.sin(rotation), 0),
+ (-math.sin(rotation), math.cos(rotation), 0),
+ (0, 0, 1),
+ )
+ ctm = Transformation.compress(matrix_multiply(self.matrix, op))
+ return Transformation(ctm)
+
+ def __repr__(self) -> str:
+ return f"Transformation(ctm={self.ctm})"
+
+ @overload
+ def apply_on(self, pt: List[float], as_object: bool = False) -> List[float]:
+ ...
+
+ @overload
+ def apply_on(
+ self, pt: Tuple[float, float], as_object: bool = False
+ ) -> Tuple[float, float]:
+ ...
+
+ def apply_on(
+ self,
+ pt: Union[Tuple[float, float], List[float]],
+ as_object: bool = False,
+ ) -> Union[Tuple[float, float], List[float]]:
+ """
+ Apply the transformation matrix on the given point.
+
+ Args:
+ pt: A tuple or list representing the point in the form (x, y)
+
+ Returns:
+ A tuple or list representing the transformed point in the form (x', y')
+ """
+ typ = FloatObject if as_object else float
+ pt1 = (
+ typ(float(pt[0]) * self.ctm[0] + float(pt[1]) * self.ctm[2] + self.ctm[4]),
+ typ(float(pt[0]) * self.ctm[1] + float(pt[1]) * self.ctm[3] + self.ctm[5]),
+ )
+ return list(pt1) if isinstance(pt, list) else pt1
+
+
+class PageObject(DictionaryObject):
+ """
+ PageObject represents a single page within a PDF file.
+
+ Typically these objects will be created by accessing the
+ :attr:`pages<pypdf.PdfReader.pages>` property of the
+ :class:`PdfReader<pypdf.PdfReader>` class, but it is
+ also possible to create an empty page with the
+ :meth:`create_blank_page()<pypdf._page.PageObject.create_blank_page>` static method.
+
+ Args:
+ pdf: PDF file the page belongs to.
+ indirect_reference: Stores the original indirect reference to
+ this object in its source PDF
+ """
+
+ original_page: "PageObject" # very local use in writer when appending
+
+ def __init__(
+ self,
+ pdf: Optional[PdfCommonDocProtocol] = None,
+ indirect_reference: Optional[IndirectObject] = None,
+ ) -> None:
+ DictionaryObject.__init__(self)
+ self.pdf = pdf
+ self.inline_images: Optional[Dict[str, ImageFile]] = None
+ # below Union for mypy but actually Optional[List[str]]
+ self.indirect_reference = indirect_reference
+
+ def hash_value_data(self) -> bytes:
+ data = super().hash_value_data()
+ data += b"%d" % id(self)
+ return data
+
+ @property
+ def user_unit(self) -> float:
+ """
+ A read-only positive number giving the size of user space units.
+
+ It is in multiples of 1/72 inch. Hence a value of 1 means a user
+ space unit is 1/72 inch, and a value of 3 means that a user
+ space unit is 3/72 inch.
+ """
+ return self.get(PG.USER_UNIT, 1)
+
+ @staticmethod
+ def create_blank_page(
+ pdf: Optional[PdfCommonDocProtocol] = None,
+ width: Union[float, Decimal, None] = None,
+ height: Union[float, Decimal, None] = None,
+ ) -> "PageObject":
+ """
+ Return a new blank page.
+
+ If ``width`` or ``height`` is ``None``, try to get the page size
+ from the last page of *pdf*.
+
+ Args:
+ pdf: PDF file the page is within.
+ width: The width of the new page expressed in default user
+ space units.
+ height: The height of the new page expressed in default user
+ space units.
+
+ Returns:
+ The new blank page
+
+ Raises:
+ PageSizeNotDefinedError: if ``pdf`` is ``None`` or contains
+ no page
+ """
+ page = PageObject(pdf)
+
+ # Creates a new page (cf PDF Reference 7.7.3.3)
+ page.__setitem__(NameObject(PG.TYPE), NameObject("/Page"))
+ page.__setitem__(NameObject(PG.PARENT), NullObject())
+ page.__setitem__(NameObject(PG.RESOURCES), DictionaryObject())
+ if width is None or height is None:
+ if pdf is not None and len(pdf.pages) > 0:
+ lastpage = pdf.pages[len(pdf.pages) - 1]
+ width = lastpage.mediabox.width
+ height = lastpage.mediabox.height
+ else:
+ raise PageSizeNotDefinedError
+ page.__setitem__(
+ NameObject(PG.MEDIABOX), RectangleObject((0, 0, width, height)) # type: ignore
+ )
+
+ return page
+
+ @property
+ def _old_images(self) -> List[File]: # deprecated
+ """
+ Get a list of all images of the page.
+
+ This requires pillow. You can install it via 'pip install pypdf[image]'.
+
+ For the moment, this does NOT include inline images. They will be added
+ in future.
+ """
+ images_extracted: List[File] = []
+ if RES.XOBJECT not in self[PG.RESOURCES]: # type: ignore
+ return images_extracted
+
+ x_object = self[PG.RESOURCES][RES.XOBJECT].get_object() # type: ignore
+ for obj in x_object:
+ if x_object[obj][IA.SUBTYPE] == "/Image":
+ extension, byte_stream, img = _xobj_to_image(x_object[obj])
+ if extension is not None:
+ filename = f"{obj[1:]}{extension}"
+ images_extracted.append(File(name=filename, data=byte_stream))
+ images_extracted[-1].image = img
+ images_extracted[-1].indirect_reference = x_object[
+ obj
+ ].indirect_reference
+ return images_extracted
+
+ def _get_ids_image(
+ self,
+ obj: Optional[DictionaryObject] = None,
+ ancest: Optional[List[str]] = None,
+ call_stack: Optional[List[Any]] = None,
+ ) -> List[Union[str, List[str]]]:
+ if call_stack is None:
+ call_stack = []
+ _i = getattr(obj, "indirect_reference", None)
+ if _i in call_stack:
+ return []
+ else:
+ call_stack.append(_i)
+ if self.inline_images is None:
+ self.inline_images = self._get_inline_images()
+ if obj is None:
+ obj = self
+ if ancest is None:
+ ancest = []
+ lst: List[Union[str, List[str]]] = []
+ if PG.RESOURCES not in obj or RES.XOBJECT not in cast(
+ DictionaryObject, obj[PG.RESOURCES]
+ ):
+ return [] if self.inline_images is None else list(self.inline_images.keys())
+
+ x_object = obj[PG.RESOURCES][RES.XOBJECT].get_object() # type: ignore
+ for o in x_object:
+ if not isinstance(x_object[o], StreamObject):
+ continue
+ if x_object[o][IA.SUBTYPE] == "/Image":
+ lst.append(o if len(ancest) == 0 else ancest + [o])
+ else: # is a form with possible images inside
+ lst.extend(self._get_ids_image(x_object[o], ancest + [o], call_stack))
+ assert self.inline_images is not None
+ lst.extend(list(self.inline_images.keys()))
+ return lst
+
+ def _get_image(
+ self,
+ id: Union[str, List[str], Tuple[str]],
+ obj: Optional[DictionaryObject] = None,
+ ) -> ImageFile:
+ if obj is None:
+ obj = cast(DictionaryObject, self)
+ if isinstance(id, tuple):
+ id = list(id)
+ if isinstance(id, List) and len(id) == 1:
+ id = id[0]
+ try:
+ xobjs = cast(
+ DictionaryObject, cast(DictionaryObject, obj[PG.RESOURCES])[RES.XOBJECT]
+ )
+ except KeyError:
+ if not (id[0] == "~" and id[-1] == "~"):
+ raise
+ if isinstance(id, str):
+ if id[0] == "~" and id[-1] == "~":
+ if self.inline_images is None:
+ self.inline_images = self._get_inline_images()
+ if self.inline_images is None: # pragma: no cover
+ raise KeyError("no inline image can be found")
+ return self.inline_images[id]
+
+ imgd = _xobj_to_image(cast(DictionaryObject, xobjs[id]))
+ extension, byte_stream = imgd[:2]
+ f = ImageFile(
+ name=f"{id[1:]}{extension}",
+ data=byte_stream,
+ image=imgd[2],
+ indirect_reference=xobjs[id].indirect_reference,
+ )
+ return f
+ else: # in a sub object
+ ids = id[1:]
+ return self._get_image(ids, cast(DictionaryObject, xobjs[id[0]]))
+
+ @property
+ def images(self) -> List[ImageFile]:
+ """
+ Read-only property emulating a list of images on a page.
+
+ Get a list of all images on the page. The key can be:
+ - A string (for the top object)
+ - A tuple (for images within XObject forms)
+ - An integer
+
+ Examples:
+ reader.pages[0].images[0] # return fist image
+ reader.pages[0].images['/I0'] # return image '/I0'
+ # return image '/Image1' within '/TP1' Xobject/Form:
+ reader.pages[0].images['/TP1','/Image1']
+ for img in reader.pages[0].images: # loop within all objects
+
+ images.keys() and images.items() can be used.
+
+ The ImageFile has the following properties:
+
+ `.name` : name of the object
+ `.data` : bytes of the object
+ `.image` : PIL Image Object
+ `.indirect_reference` : object reference
+
+ and the following methods:
+ `.replace(new_image: PIL.Image.Image, **kwargs)` :
+ replace the image in the pdf with the new image
+ applying the saving parameters indicated (such as quality)
+
+ Example usage:
+
+ reader.pages[0].images[0]=replace(Image.open("new_image.jpg", quality = 20)
+
+ Inline images are extracted and named ~0~, ~1~, ..., with the
+ indirect_reference set to None.
+ """
+ return _VirtualListImages(self._get_ids_image, self._get_image) # type: ignore
+
+ def _translate_value_inlineimage(self, k: str, v: PdfObject) -> PdfObject:
+ """Translate values used in inline image"""
+ try:
+ v = NameObject(
+ {
+ "/G": "/DeviceGray",
+ "/RGB": "/DeviceRGB",
+ "/CMYK": "/DeviceCMYK",
+ "/I": "/Indexed",
+ "/AHx": "/ASCIIHexDecode",
+ "/A85": "/ASCII85Decode",
+ "/LZW": "/LZWDecode",
+ "/Fl": "/FlateDecode",
+ "/RL": "/RunLengthDecode",
+ "/CCF": "/CCITTFaxDecode",
+ "/DCT": "/DCTDecode",
+ "/DeviceGray": "/DeviceGray",
+ "/DeviceRGB": "/DeviceRGB",
+ "/DeviceCMYK": "/DeviceCMYK",
+ "/Indexed": "/Indexed",
+ "/ASCIIHexDecode": "/ASCIIHexDecode",
+ "/ASCII85Decode": "/ASCII85Decode",
+ "/LZWDecode": "/LZWDecode",
+ "/FlateDecode": "/FlateDecode",
+ "/RunLengthDecode": "/RunLengthDecode",
+ "/CCITTFaxDecode": "/CCITTFaxDecode",
+ "/DCTDecode": "/DCTDecode",
+ }[cast(str, v)]
+ )
+ except (TypeError, KeyError):
+ if isinstance(v, NameObject):
+ # It is a custom name, thus we have to look in resources.
+ # The only applicable case is for ColorSpace.
+ try:
+ res = cast(DictionaryObject, self["/Resources"])["/ColorSpace"]
+ v = cast(DictionaryObject, res)[v]
+ except KeyError: # for res and v
+ raise PdfReadError(f"Cannot find resource entry {v} for {k}")
+ return v
+
+ def _get_inline_images(self) -> Dict[str, ImageFile]:
+ """
+ get inline_images
+ entries will be identified as ~1~
+ """
+ content = self.get_contents()
+ if content is None:
+ return {}
+ imgs_data = []
+ for param, ope in content.operations:
+ if ope == b"INLINE IMAGE":
+ imgs_data.append(
+ {"settings": param["settings"], "__streamdata__": param["data"]}
+ )
+ elif ope in (b"BI", b"EI", b"ID"): # pragma: no cover
+ raise PdfReadError(
+ f"{ope} operator met whereas not expected,"
+ "please share usecase with pypdf dev team"
+ )
+ """backup
+ elif ope == b"BI":
+ img_data["settings"] = {}
+ elif ope == b"EI":
+ imgs_data.append(img_data)
+ img_data = {}
+ elif ope == b"ID":
+ img_data["__streamdata__"] = b""
+ elif "__streamdata__" in img_data:
+ if len(img_data["__streamdata__"]) > 0:
+ img_data["__streamdata__"] += b"\n"
+ raise Exception("check append")
+ img_data["__streamdata__"] += param
+ elif "settings" in img_data:
+ img_data["settings"][ope.decode()] = param
+ """
+ files = {}
+ for num, ii in enumerate(imgs_data):
+ init = {
+ "__streamdata__": ii["__streamdata__"],
+ "/Length": len(ii["__streamdata__"]),
+ }
+ for k, v in ii["settings"].items():
+ if k in {"/Length", "/L"}: # no length is expected
+ continue
+ if isinstance(v, list):
+ v = ArrayObject(
+ [self._translate_value_inlineimage(k, x) for x in v]
+ )
+ else:
+ v = self._translate_value_inlineimage(k, v)
+ k = NameObject(
+ {
+ "/BPC": "/BitsPerComponent",
+ "/CS": "/ColorSpace",
+ "/D": "/Decode",
+ "/DP": "/DecodeParms",
+ "/F": "/Filter",
+ "/H": "/Height",
+ "/W": "/Width",
+ "/I": "/Interpolate",
+ "/Intent": "/Intent",
+ "/IM": "/ImageMask",
+ "/BitsPerComponent": "/BitsPerComponent",
+ "/ColorSpace": "/ColorSpace",
+ "/Decode": "/Decode",
+ "/DecodeParms": "/DecodeParms",
+ "/Filter": "/Filter",
+ "/Height": "/Height",
+ "/Width": "/Width",
+ "/Interpolate": "/Interpolate",
+ "/ImageMask": "/ImageMask",
+ }[k]
+ )
+ if k not in init:
+ init[k] = v
+ ii["object"] = EncodedStreamObject.initialize_from_dictionary(init)
+ extension, byte_stream, img = _xobj_to_image(ii["object"])
+ files[f"~{num}~"] = ImageFile(
+ name=f"~{num}~{extension}",
+ data=byte_stream,
+ image=img,
+ indirect_reference=None,
+ )
+ return files
+
+ @property
+ def rotation(self) -> int:
+ """
+ The visual rotation of the page.
+
+ This number has to be a multiple of 90 degrees: 0, 90, 180, or 270 are
+ valid values. This property does not affect ``/Contents``.
+ """
+ rotate_obj = self.get(PG.ROTATE, 0)
+ return rotate_obj if isinstance(rotate_obj, int) else rotate_obj.get_object()
+
+ @rotation.setter
+ def rotation(self, r: float) -> None:
+ self[NameObject(PG.ROTATE)] = NumberObject((((int(r) + 45) // 90) * 90) % 360)
+
+ def transfer_rotation_to_content(self) -> None:
+ """
+ Apply the rotation of the page to the content and the media/crop/...
+ boxes.
+
+ It is recommended to apply this function before page merging.
+ """
+ r = -self.rotation # rotation to apply is in the otherway
+ self.rotation = 0
+ mb = RectangleObject(self.mediabox)
+ trsf = (
+ Transformation()
+ .translate(
+ -float(mb.left + mb.width / 2), -float(mb.bottom + mb.height / 2)
+ )
+ .rotate(r)
+ )
+ pt1 = trsf.apply_on(mb.lower_left)
+ pt2 = trsf.apply_on(mb.upper_right)
+ trsf = trsf.translate(-min(pt1[0], pt2[0]), -min(pt1[1], pt2[1]))
+ self.add_transformation(trsf, False)
+ for b in ["/MediaBox", "/CropBox", "/BleedBox", "/TrimBox", "/ArtBox"]:
+ if b in self:
+ rr = RectangleObject(self[b]) # type: ignore
+ pt1 = trsf.apply_on(rr.lower_left)
+ pt2 = trsf.apply_on(rr.upper_right)
+ self[NameObject(b)] = RectangleObject(
+ (
+ min(pt1[0], pt2[0]),
+ min(pt1[1], pt2[1]),
+ max(pt1[0], pt2[0]),
+ max(pt1[1], pt2[1]),
+ )
+ )
+
+ def rotate(self, angle: int) -> "PageObject":
+ """
+ Rotate a page clockwise by increments of 90 degrees.
+
+ Args:
+ angle: Angle to rotate the page. Must be an increment of 90 deg.
+
+ Returns:
+ The rotated PageObject
+ """
+ if angle % 90 != 0:
+ raise ValueError("Rotation angle must be a multiple of 90")
+ self[NameObject(PG.ROTATE)] = NumberObject(self.rotation + angle)
+ return self
+
+ def _merge_resources(
+ self,
+ res1: DictionaryObject,
+ res2: DictionaryObject,
+ resource: Any,
+ new_res1: bool = True,
+ ) -> Tuple[Dict[str, Any], Dict[str, Any]]:
+ try:
+ assert isinstance(self.indirect_reference, IndirectObject)
+ pdf = self.indirect_reference.pdf
+ is_pdf_writer = hasattr(
+ pdf, "_add_object"
+ ) # ---------- expect isinstance(pdf,PdfWriter)
+ except (AssertionError, AttributeError):
+ pdf = None
+ is_pdf_writer = False
+
+ def compute_unique_key(base_key: str) -> Tuple[str, bool]:
+ """
+ Find a key that either doesn't already exist or has the same value
+ (indicated by the bool)
+
+ Args:
+ base_key: An index is added to this to get the computed key
+
+ Returns:
+ A tuple (computed key, bool) where the boolean indicates
+ if there is a resource of the given computed_key with the same
+ value.
+ """
+ value = page2res.raw_get(base_key)
+ # TODO : possible improvement : in case of writer, the indirect_reference
+ # can not be found because translated : this may be improved
+
+ # try the current key first (e.g. "foo"), but otherwise iterate
+ # through "foo-0", "foo-1", etc. new_res can contain only finitely
+ # many keys, thus this'll eventually end, even if it's been crafted
+ # to be maximally annoying.
+ computed_key = base_key
+ idx = 0
+ while computed_key in new_res:
+ if new_res.raw_get(computed_key) == value:
+ # there's already a resource of this name, with the exact
+ # same value
+ return computed_key, True
+ computed_key = f"{base_key}-{idx}"
+ idx += 1
+ return computed_key, False
+
+ if new_res1:
+ new_res = DictionaryObject()
+ new_res.update(res1.get(resource, DictionaryObject()).get_object())
+ else:
+ new_res = cast(DictionaryObject, res1[resource])
+ page2res = cast(
+ DictionaryObject, res2.get(resource, DictionaryObject()).get_object()
+ )
+ rename_res = {}
+ for key in page2res:
+ unique_key, same_value = compute_unique_key(key)
+ newname = NameObject(unique_key)
+ if key != unique_key:
+ # we have to use a different name for this
+ rename_res[key] = newname
+
+ if not same_value:
+ if is_pdf_writer:
+ new_res[newname] = page2res.raw_get(key).clone(pdf)
+ try:
+ new_res[newname] = new_res[newname].indirect_reference
+ except AttributeError:
+ pass
+ else:
+ new_res[newname] = page2res.raw_get(key)
+ lst = sorted(new_res.items())
+ new_res.clear()
+ for el in lst:
+ new_res[el[0]] = el[1]
+ return new_res, rename_res
+
+ @staticmethod
+ def _content_stream_rename(
+ stream: ContentStream,
+ rename: Dict[Any, Any],
+ pdf: Optional[PdfCommonDocProtocol],
+ ) -> ContentStream:
+ if not rename:
+ return stream
+ stream = ContentStream(stream, pdf)
+ for operands, _operator in stream.operations:
+ if isinstance(operands, list):
+ for i, op in enumerate(operands):
+ if isinstance(op, NameObject):
+ operands[i] = rename.get(op, op)
+ elif isinstance(operands, dict):
+ for i, op in operands.items():
+ if isinstance(op, NameObject):
+ operands[i] = rename.get(op, op)
+ else:
+ raise KeyError(f"type of operands is {type(operands)}")
+ return stream
+
+ @staticmethod
+ def _add_transformation_matrix(
+ contents: Any,
+ pdf: Optional[PdfCommonDocProtocol],
+ ctm: CompressedTransformationMatrix,
+ ) -> ContentStream:
+ """Add transformation matrix at the beginning of the given contents stream."""
+ a, b, c, d, e, f = ctm
+ contents = ContentStream(contents, pdf)
+ contents.operations.insert(
+ 0,
+ [
+ [
+ FloatObject(a),
+ FloatObject(b),
+ FloatObject(c),
+ FloatObject(d),
+ FloatObject(e),
+ FloatObject(f),
+ ],
+ " cm",
+ ],
+ )
+ return contents
+
+ def _get_contents_as_bytes(self) -> Optional[bytes]:
+ """
+ Return the page contents as bytes.
+
+ Returns:
+ The ``/Contents`` object as bytes, or ``None`` if it doesn't exist.
+
+ """
+ if PG.CONTENTS in self:
+ obj = self[PG.CONTENTS].get_object()
+ if isinstance(obj, list):
+ return b"".join(x.get_object().get_data() for x in obj)
+ else:
+ return cast(bytes, cast(EncodedStreamObject, obj).get_data())
+ else:
+ return None
+
+ def get_contents(self) -> Optional[ContentStream]:
+ """
+ Access the page contents.
+
+ Returns:
+ The ``/Contents`` object, or ``None`` if it does not exist.
+ ``/Contents`` is optional, as described in §7.7.3.3 of the PDF Reference.
+ """
+ if PG.CONTENTS in self:
+ try:
+ pdf = cast(IndirectObject, self.indirect_reference).pdf
+ except AttributeError:
+ pdf = None
+ obj = self[PG.CONTENTS].get_object()
+ if isinstance(obj, NullObject):
+ return None
+ else:
+ return ContentStream(obj, pdf)
+ else:
+ return None
+
+ def replace_contents(
+ self, content: Union[None, ContentStream, EncodedStreamObject, ArrayObject]
+ ) -> None:
+ """
+ Replace the page contents with the new content and nullify old objects
+ Args:
+ content: new content; if None delete the content field.
+ """
+ if not hasattr(self, "indirect_reference") or self.indirect_reference is None:
+ # the page is not attached : the content is directly attached.
+ self[NameObject(PG.CONTENTS)] = content
+ return
+ if isinstance(self.get(PG.CONTENTS, None), ArrayObject):
+ for o in self[PG.CONTENTS]: # type: ignore[attr-defined]
+ try:
+ self._objects[o.indirect_reference.idnum - 1] = NullObject() # type: ignore
+ except AttributeError:
+ pass
+
+ if isinstance(content, ArrayObject):
+ for i in range(len(content)):
+ content[i] = self.indirect_reference.pdf._add_object(content[i])
+
+ if content is None:
+ if PG.CONTENTS not in self:
+ return
+ else:
+ assert self.indirect_reference is not None
+ assert self[PG.CONTENTS].indirect_reference is not None
+ self.indirect_reference.pdf._objects[
+ self[PG.CONTENTS].indirect_reference.idnum - 1 # type: ignore
+ ] = NullObject()
+ del self[PG.CONTENTS]
+ elif not hasattr(self.get(PG.CONTENTS, None), "indirect_reference"):
+ try:
+ self[NameObject(PG.CONTENTS)] = self.indirect_reference.pdf._add_object(
+ content
+ )
+ except AttributeError:
+ # applies at least for page not in writer
+ # as a backup solution, we put content as an object although not in accordance with pdf ref
+ # this will be fixed with the _add_object
+ self[NameObject(PG.CONTENTS)] = content
+ else:
+ content.indirect_reference = self[
+ PG.CONTENTS
+ ].indirect_reference # TODO: in a future may required generation management
+ try:
+ self.indirect_reference.pdf._objects[
+ content.indirect_reference.idnum - 1 # type: ignore
+ ] = content
+ except AttributeError:
+ # applies at least for page not in writer
+ # as a backup solution, we put content as an object although not in accordance with pdf ref
+ # this will be fixed with the _add_object
+ self[NameObject(PG.CONTENTS)] = content
+ # forces recalculation of inline_images
+ self.inline_images = None
+
+ def merge_page(
+ self, page2: "PageObject", expand: bool = False, over: bool = True
+ ) -> None:
+ """
+ Merge the content streams of two pages into one.
+
+ Resource references
+ (i.e. fonts) are maintained from both pages. The mediabox/cropbox/etc
+ of this page are not altered. The parameter page's content stream will
+ be added to the end of this page's content stream, meaning that it will
+ be drawn after, or "on top" of this page.
+
+ Args:
+ page2: The page to be merged into this one. Should be
+ an instance of :class:`PageObject<PageObject>`.
+ over: set the page2 content over page1 if True (default) else under
+ expand: If True, the current page dimensions will be
+ expanded to accommodate the dimensions of the page to be merged.
+ """
+ self._merge_page(page2, over=over, expand=expand)
+
+ def _merge_page(
+ self,
+ page2: "PageObject",
+ page2transformation: Optional[Callable[[Any], ContentStream]] = None,
+ ctm: Optional[CompressedTransformationMatrix] = None,
+ over: bool = True,
+ expand: bool = False,
+ ) -> None:
+ # First we work on merging the resource dictionaries. This allows us
+ # to find out what symbols in the content streams we might need to
+ # rename.
+ try:
+ assert isinstance(self.indirect_reference, IndirectObject)
+ if hasattr(
+ self.indirect_reference.pdf, "_add_object"
+ ): # ---------- to detect PdfWriter
+ return self._merge_page_writer(
+ page2, page2transformation, ctm, over, expand
+ )
+ except (AssertionError, AttributeError):
+ pass
+
+ new_resources = DictionaryObject()
+ rename = {}
+ try:
+ original_resources = cast(DictionaryObject, self[PG.RESOURCES].get_object())
+ except KeyError:
+ original_resources = DictionaryObject()
+ try:
+ page2resources = cast(DictionaryObject, page2[PG.RESOURCES].get_object())
+ except KeyError:
+ page2resources = DictionaryObject()
+ new_annots = ArrayObject()
+
+ for page in (self, page2):
+ if PG.ANNOTS in page:
+ annots = page[PG.ANNOTS]
+ if isinstance(annots, ArrayObject):
+ new_annots.extend(annots)
+
+ for res in (
+ RES.EXT_G_STATE,
+ RES.FONT,
+ RES.XOBJECT,
+ RES.COLOR_SPACE,
+ RES.PATTERN,
+ RES.SHADING,
+ RES.PROPERTIES,
+ ):
+ new, newrename = self._merge_resources(
+ original_resources, page2resources, res
+ )
+ if new:
+ new_resources[NameObject(res)] = new
+ rename.update(newrename)
+
+ # Combine /ProcSet sets, making sure there's a consistent order
+ new_resources[NameObject(RES.PROC_SET)] = ArrayObject(
+ sorted(
+ set(
+ original_resources.get(RES.PROC_SET, ArrayObject()).get_object()
+ ).union(
+ set(page2resources.get(RES.PROC_SET, ArrayObject()).get_object())
+ )
+ )
+ )
+
+ new_content_array = ArrayObject()
+ original_content = self.get_contents()
+ if original_content is not None:
+ original_content.isolate_graphics_state()
+ new_content_array.append(original_content)
+
+ page2content = page2.get_contents()
+ if page2content is not None:
+ rect = getattr(page2, MERGE_CROP_BOX)
+ page2content.operations.insert(
+ 0,
+ (
+ map(
+ FloatObject,
+ [
+ rect.left,
+ rect.bottom,
+ rect.width,
+ rect.height,
+ ],
+ ),
+ "re",
+ ),
+ )
+ page2content.operations.insert(1, ([], "W"))
+ page2content.operations.insert(2, ([], "n"))
+ if page2transformation is not None:
+ page2content = page2transformation(page2content)
+ page2content = PageObject._content_stream_rename(
+ page2content, rename, self.pdf
+ )
+ page2content.isolate_graphics_state()
+ if over:
+ new_content_array.append(page2content)
+ else:
+ new_content_array.insert(0, page2content)
+
+ # if expanding the page to fit a new page, calculate the new media box size
+ if expand:
+ self._expand_mediabox(page2, ctm)
+
+ self.replace_contents(ContentStream(new_content_array, self.pdf))
+ self[NameObject(PG.RESOURCES)] = new_resources
+ self[NameObject(PG.ANNOTS)] = new_annots
+
+ def _merge_page_writer(
+ self,
+ page2: "PageObject",
+ page2transformation: Optional[Callable[[Any], ContentStream]] = None,
+ ctm: Optional[CompressedTransformationMatrix] = None,
+ over: bool = True,
+ expand: bool = False,
+ ) -> None:
+ # First we work on merging the resource dictionaries. This allows us
+ # to find which symbols in the content streams we might need to
+ # rename.
+ assert isinstance(self.indirect_reference, IndirectObject)
+ pdf = self.indirect_reference.pdf
+
+ rename = {}
+ if PG.RESOURCES not in self:
+ self[NameObject(PG.RESOURCES)] = DictionaryObject()
+ original_resources = cast(DictionaryObject, self[PG.RESOURCES].get_object())
+ if PG.RESOURCES not in page2:
+ page2resources = DictionaryObject()
+ else:
+ page2resources = cast(DictionaryObject, page2[PG.RESOURCES].get_object())
+
+ for res in (
+ RES.EXT_G_STATE,
+ RES.FONT,
+ RES.XOBJECT,
+ RES.COLOR_SPACE,
+ RES.PATTERN,
+ RES.SHADING,
+ RES.PROPERTIES,
+ ):
+ if res in page2resources:
+ if res not in original_resources:
+ original_resources[NameObject(res)] = DictionaryObject()
+ _, newrename = self._merge_resources(
+ original_resources, page2resources, res, False
+ )
+ rename.update(newrename)
+ # Combine /ProcSet sets.
+ if RES.PROC_SET in page2resources:
+ if RES.PROC_SET not in original_resources:
+ original_resources[NameObject(RES.PROC_SET)] = ArrayObject()
+ arr = cast(ArrayObject, original_resources[RES.PROC_SET])
+ for x in cast(ArrayObject, page2resources[RES.PROC_SET]):
+ if x not in arr:
+ arr.append(x)
+ arr.sort()
+
+ if PG.ANNOTS in page2:
+ if PG.ANNOTS not in self:
+ self[NameObject(PG.ANNOTS)] = ArrayObject()
+ annots = cast(ArrayObject, self[PG.ANNOTS].get_object())
+ if ctm is None:
+ trsf = Transformation()
+ else:
+ trsf = Transformation(ctm)
+ for a in cast(ArrayObject, page2[PG.ANNOTS]):
+ a = a.get_object()
+ aa = a.clone(
+ pdf,
+ ignore_fields=("/P", "/StructParent", "/Parent"),
+ force_duplicate=True,
+ )
+ r = cast(ArrayObject, a["/Rect"])
+ pt1 = trsf.apply_on((r[0], r[1]), True)
+ pt2 = trsf.apply_on((r[2], r[3]), True)
+ aa[NameObject("/Rect")] = ArrayObject(
+ (
+ min(pt1[0], pt2[0]),
+ min(pt1[1], pt2[1]),
+ max(pt1[0], pt2[0]),
+ max(pt1[1], pt2[1]),
+ )
+ )
+ if "/QuadPoints" in a:
+ q = cast(ArrayObject, a["/QuadPoints"])
+ aa[NameObject("/QuadPoints")] = ArrayObject(
+ trsf.apply_on((q[0], q[1]), True)
+ + trsf.apply_on((q[2], q[3]), True)
+ + trsf.apply_on((q[4], q[5]), True)
+ + trsf.apply_on((q[6], q[7]), True)
+ )
+ try:
+ aa["/Popup"][NameObject("/Parent")] = aa.indirect_reference
+ except KeyError:
+ pass
+ try:
+ aa[NameObject("/P")] = self.indirect_reference
+ annots.append(aa.indirect_reference)
+ except AttributeError:
+ pass
+
+ new_content_array = ArrayObject()
+ original_content = self.get_contents()
+ if original_content is not None:
+ original_content.isolate_graphics_state()
+ new_content_array.append(original_content)
+
+ page2content = page2.get_contents()
+ if page2content is not None:
+ rect = getattr(page2, MERGE_CROP_BOX)
+ page2content.operations.insert(
+ 0,
+ (
+ map(
+ FloatObject,
+ [
+ rect.left,
+ rect.bottom,
+ rect.width,
+ rect.height,
+ ],
+ ),
+ "re",
+ ),
+ )
+ page2content.operations.insert(1, ([], "W"))
+ page2content.operations.insert(2, ([], "n"))
+ if page2transformation is not None:
+ page2content = page2transformation(page2content)
+ page2content = PageObject._content_stream_rename(
+ page2content, rename, self.pdf
+ )
+ page2content.isolate_graphics_state()
+ if over:
+ new_content_array.append(page2content)
+ else:
+ new_content_array.insert(0, page2content)
+
+ # if expanding the page to fit a new page, calculate the new media box size
+ if expand:
+ self._expand_mediabox(page2, ctm)
+
+ self.replace_contents(new_content_array)
+ # self[NameObject(PG.CONTENTS)] = ContentStream(new_content_array, pdf)
+ # self[NameObject(PG.RESOURCES)] = new_resources
+ # self[NameObject(PG.ANNOTS)] = new_annots
+
+ def _expand_mediabox(
+ self, page2: "PageObject", ctm: Optional[CompressedTransformationMatrix]
+ ) -> None:
+ corners1 = (
+ self.mediabox.left.as_numeric(),
+ self.mediabox.bottom.as_numeric(),
+ self.mediabox.right.as_numeric(),
+ self.mediabox.top.as_numeric(),
+ )
+ corners2 = (
+ page2.mediabox.left.as_numeric(),
+ page2.mediabox.bottom.as_numeric(),
+ page2.mediabox.left.as_numeric(),
+ page2.mediabox.top.as_numeric(),
+ page2.mediabox.right.as_numeric(),
+ page2.mediabox.top.as_numeric(),
+ page2.mediabox.right.as_numeric(),
+ page2.mediabox.bottom.as_numeric(),
+ )
+ if ctm is not None:
+ ctm = tuple(float(x) for x in ctm) # type: ignore[assignment]
+ new_x = tuple(
+ ctm[0] * corners2[i] + ctm[2] * corners2[i + 1] + ctm[4]
+ for i in range(0, 8, 2)
+ )
+ new_y = tuple(
+ ctm[1] * corners2[i] + ctm[3] * corners2[i + 1] + ctm[5]
+ for i in range(0, 8, 2)
+ )
+ else:
+ new_x = corners2[0:8:2]
+ new_y = corners2[1:8:2]
+ lowerleft = (min(new_x), min(new_y))
+ upperright = (max(new_x), max(new_y))
+ lowerleft = (min(corners1[0], lowerleft[0]), min(corners1[1], lowerleft[1]))
+ upperright = (
+ max(corners1[2], upperright[0]),
+ max(corners1[3], upperright[1]),
+ )
+
+ self.mediabox.lower_left = lowerleft
+ self.mediabox.upper_right = upperright
+
+ def merge_transformed_page(
+ self,
+ page2: "PageObject",
+ ctm: Union[CompressedTransformationMatrix, Transformation],
+ over: bool = True,
+ expand: bool = False,
+ ) -> None:
+ """
+ merge_transformed_page is similar to merge_page, but a transformation
+ matrix is applied to the merged stream.
+
+ Args:
+ page2: The page to be merged into this one.
+ ctm: a 6-element tuple containing the operands of the
+ transformation matrix
+ over: set the page2 content over page1 if True (default) else under
+ expand: Whether the page should be expanded to fit the dimensions
+ of the page to be merged.
+ """
+ if isinstance(ctm, Transformation):
+ ctm = ctm.ctm
+ self._merge_page(
+ page2,
+ lambda page2Content: PageObject._add_transformation_matrix(
+ page2Content, page2.pdf, cast(CompressedTransformationMatrix, ctm)
+ ),
+ ctm,
+ over,
+ expand,
+ )
+
+ def merge_scaled_page(
+ self, page2: "PageObject", scale: float, over: bool = True, expand: bool = False
+ ) -> None:
+ """
+ merge_scaled_page is similar to merge_page, but the stream to be merged
+ is scaled by applying a transformation matrix.
+
+ Args:
+ page2: The page to be merged into this one.
+ scale: The scaling factor
+ over: set the page2 content over page1 if True (default) else under
+ expand: Whether the page should be expanded to fit the
+ dimensions of the page to be merged.
+ """
+ op = Transformation().scale(scale, scale)
+ self.merge_transformed_page(page2, op, over, expand)
+
+ def merge_rotated_page(
+ self,
+ page2: "PageObject",
+ rotation: float,
+ over: bool = True,
+ expand: bool = False,
+ ) -> None:
+ """
+ merge_rotated_page is similar to merge_page, but the stream to be merged
+ is rotated by applying a transformation matrix.
+
+ Args:
+ page2: The page to be merged into this one.
+ rotation: The angle of the rotation, in degrees
+ over: set the page2 content over page1 if True (default) else under
+ expand: Whether the page should be expanded to fit the
+ dimensions of the page to be merged.
+ """
+ op = Transformation().rotate(rotation)
+ self.merge_transformed_page(page2, op, over, expand)
+
+ def merge_translated_page(
+ self,
+ page2: "PageObject",
+ tx: float,
+ ty: float,
+ over: bool = True,
+ expand: bool = False,
+ ) -> None:
+ """
+ mergeTranslatedPage is similar to merge_page, but the stream to be
+ merged is translated by applying a transformation matrix.
+
+ Args:
+ page2: the page to be merged into this one.
+ tx: The translation on X axis
+ ty: The translation on Y axis
+ over: set the page2 content over page1 if True (default) else under
+ expand: Whether the page should be expanded to fit the
+ dimensions of the page to be merged.
+ """
+ op = Transformation().translate(tx, ty)
+ self.merge_transformed_page(page2, op, over, expand)
+
+ def add_transformation(
+ self,
+ ctm: Union[Transformation, CompressedTransformationMatrix],
+ expand: bool = False,
+ ) -> None:
+ """
+ Apply a transformation matrix to the page.
+
+ Args:
+ ctm: A 6-element tuple containing the operands of the
+ transformation matrix. Alternatively, a
+ :py:class:`Transformation<pypdf.Transformation>`
+ object can be passed.
+
+ See :doc:`/user/cropping-and-transforming`.
+ """
+ if isinstance(ctm, Transformation):
+ ctm = ctm.ctm
+ content = self.get_contents()
+ if content is not None:
+ content = PageObject._add_transformation_matrix(content, self.pdf, ctm)
+ content.isolate_graphics_state()
+ self.replace_contents(content)
+ # if expanding the page to fit a new page, calculate the new media box size
+ if expand:
+ corners = [
+ self.mediabox.left.as_numeric(),
+ self.mediabox.bottom.as_numeric(),
+ self.mediabox.left.as_numeric(),
+ self.mediabox.top.as_numeric(),
+ self.mediabox.right.as_numeric(),
+ self.mediabox.top.as_numeric(),
+ self.mediabox.right.as_numeric(),
+ self.mediabox.bottom.as_numeric(),
+ ]
+
+ ctm = tuple(float(x) for x in ctm) # type: ignore[assignment]
+ new_x = [
+ ctm[0] * corners[i] + ctm[2] * corners[i + 1] + ctm[4]
+ for i in range(0, 8, 2)
+ ]
+ new_y = [
+ ctm[1] * corners[i] + ctm[3] * corners[i + 1] + ctm[5]
+ for i in range(0, 8, 2)
+ ]
+
+ lowerleft = (min(new_x), min(new_y))
+ upperright = (max(new_x), max(new_y))
+
+ self.mediabox.lower_left = lowerleft
+ self.mediabox.upper_right = upperright
+
+ def scale(self, sx: float, sy: float) -> None:
+ """
+ Scale a page by the given factors by applying a transformation matrix
+ to its content and updating the page size.
+
+ This updates the mediabox, the cropbox, and the contents
+ of the page.
+
+ Args:
+ sx: The scaling factor on horizontal axis.
+ sy: The scaling factor on vertical axis.
+ """
+ self.add_transformation((sx, 0, 0, sy, 0, 0))
+ self.cropbox = self.cropbox.scale(sx, sy)
+ self.artbox = self.artbox.scale(sx, sy)
+ self.bleedbox = self.bleedbox.scale(sx, sy)
+ self.trimbox = self.trimbox.scale(sx, sy)
+ self.mediabox = self.mediabox.scale(sx, sy)
+
+ if PG.ANNOTS in self:
+ annotations = self[PG.ANNOTS]
+ if isinstance(annotations, ArrayObject):
+ for annotation in annotations:
+ annotation_obj = annotation.get_object()
+ if ADA.Rect in annotation_obj:
+ rectangle = annotation_obj[ADA.Rect]
+ if isinstance(rectangle, ArrayObject):
+ rectangle[0] = FloatObject(float(rectangle[0]) * sx)
+ rectangle[1] = FloatObject(float(rectangle[1]) * sy)
+ rectangle[2] = FloatObject(float(rectangle[2]) * sx)
+ rectangle[3] = FloatObject(float(rectangle[3]) * sy)
+
+ if PG.VP in self:
+ viewport = self[PG.VP]
+ if isinstance(viewport, ArrayObject):
+ bbox = viewport[0]["/BBox"]
+ else:
+ bbox = viewport["/BBox"] # type: ignore
+ scaled_bbox = RectangleObject(
+ (
+ float(bbox[0]) * sx,
+ float(bbox[1]) * sy,
+ float(bbox[2]) * sx,
+ float(bbox[3]) * sy,
+ )
+ )
+ if isinstance(viewport, ArrayObject):
+ self[NameObject(PG.VP)][NumberObject(0)][ # type: ignore
+ NameObject("/BBox")
+ ] = scaled_bbox
+ else:
+ self[NameObject(PG.VP)][NameObject("/BBox")] = scaled_bbox # type: ignore
+
+ def scale_by(self, factor: float) -> None:
+ """
+ Scale a page by the given factor by applying a transformation matrix to
+ its content and updating the page size.
+
+ Args:
+ factor: The scaling factor (for both X and Y axis).
+ """
+ self.scale(factor, factor)
+
+ def scale_to(self, width: float, height: float) -> None:
+ """
+ Scale a page to the specified dimensions by applying a transformation
+ matrix to its content and updating the page size.
+
+ Args:
+ width: The new width.
+ height: The new height.
+ """
+ sx = width / float(self.mediabox.width)
+ sy = height / float(self.mediabox.height)
+ self.scale(sx, sy)
+
+ def compress_content_streams(self, level: int = -1) -> None:
+ """
+ Compress the size of this page by joining all content streams and
+ applying a FlateDecode filter.
+
+ However, it is possible that this function will perform no action if
+ content stream compression becomes "automatic".
+ """
+ content = self.get_contents()
+ if content is not None:
+ content_obj = content.flate_encode(level)
+ try:
+ content.indirect_reference.pdf._objects[ # type: ignore
+ content.indirect_reference.idnum - 1 # type: ignore
+ ] = content_obj
+ except AttributeError:
+ if self.indirect_reference is not None and hasattr(
+ self.indirect_reference.pdf, "_add_object"
+ ):
+ self.replace_contents(content_obj)
+ else:
+ raise ValueError("Page must be part of a PdfWriter")
+
+ @property
+ def page_number(self) -> Optional[int]:
+ """
+ Read-only property which returns the page number within the PDF file.
+
+ Returns:
+ int : page number; None if the page is not attached to a PDF.
+ """
+ if self.indirect_reference is None:
+ return None
+ else:
+ try:
+ lst = self.indirect_reference.pdf.pages
+ return lst.index(self)
+ except ValueError:
+ return None
+
+ def _debug_for_extract(self) -> str: # pragma: no cover
+ out = ""
+ for ope, op in ContentStream(
+ self["/Contents"].get_object(), self.pdf, "bytes"
+ ).operations:
+ if op == b"TJ":
+ s = [x for x in ope[0] if isinstance(x, str)]
+ else:
+ s = []
+ out += op.decode("utf-8") + " " + "".join(s) + ope.__repr__() + "\n"
+ out += "\n=============================\n"
+ try:
+ for fo in self[PG.RESOURCES]["/Font"]: # type:ignore
+ out += fo + "\n"
+ out += self[PG.RESOURCES]["/Font"][fo].__repr__() + "\n" # type:ignore
+ try:
+ enc_repr = self[PG.RESOURCES]["/Font"][fo][ # type:ignore
+ "/Encoding"
+ ].__repr__()
+ out += enc_repr + "\n"
+ except Exception:
+ pass
+ try:
+ out += (
+ self[PG.RESOURCES]["/Font"][fo][ # type:ignore
+ "/ToUnicode"
+ ]
+ .get_data()
+ .decode()
+ + "\n"
+ )
+ except Exception:
+ pass
+
+ except KeyError:
+ out += "No Font\n"
+ return out
+
+ def _extract_text(
+ self,
+ obj: Any,
+ pdf: Any,
+ orientations: Tuple[int, ...] = (0, 90, 180, 270),
+ space_width: float = 200.0,
+ content_key: Optional[str] = PG.CONTENTS,
+ visitor_operand_before: Optional[Callable[[Any, Any, Any, Any], None]] = None,
+ visitor_operand_after: Optional[Callable[[Any, Any, Any, Any], None]] = None,
+ visitor_text: Optional[Callable[[Any, Any, Any, Any, Any], None]] = None,
+ ) -> str:
+ """
+ See extract_text for most arguments.
+
+ Args:
+ content_key: indicate the default key where to extract data
+ None = the object; this allow to reuse the function on XObject
+ default = "/Content"
+ """
+ text: str = ""
+ output: str = ""
+ rtl_dir: bool = False # right-to-left
+ cmaps: Dict[
+ str,
+ Tuple[
+ str, float, Union[str, Dict[int, str]], Dict[str, str], DictionaryObject
+ ],
+ ] = {}
+ try:
+ objr = obj
+ while NameObject(PG.RESOURCES) not in objr:
+ # /Resources can be inherited sometimes so we look to parents
+ objr = objr["/Parent"].get_object()
+ # if no parents we will have no /Resources will be available
+ # => an exception will be raised
+ resources_dict = cast(DictionaryObject, objr[PG.RESOURCES])
+ except Exception:
+ # no resources means no text is possible (no font) we consider the
+ # file as not damaged, no need to check for TJ or Tj
+ return ""
+ if "/Font" in resources_dict:
+ for f in cast(DictionaryObject, resources_dict["/Font"]):
+ cmaps[f] = build_char_map(f, space_width, obj)
+ cmap: Tuple[
+ Union[str, Dict[int, str]], Dict[str, str], str, Optional[DictionaryObject]
+ ] = (
+ "charmap",
+ {},
+ "NotInitialized",
+ None,
+ ) # (encoding,CMAP,font resource name,dictionary-object of font)
+ try:
+ content = (
+ obj[content_key].get_object() if isinstance(content_key, str) else obj
+ )
+ if not isinstance(content, ContentStream):
+ content = ContentStream(content, pdf, "bytes")
+ except KeyError: # it means no content can be extracted(certainly empty page)
+ return ""
+ # Note: we check all strings are TextStringObjects. ByteStringObjects
+ # are strings where the byte->string encoding was unknown, so adding
+ # them to the text here would be gibberish.
+
+ cm_matrix: List[float] = [1.0, 0.0, 0.0, 1.0, 0.0, 0.0]
+ cm_stack = []
+ tm_matrix: List[float] = [1.0, 0.0, 0.0, 1.0, 0.0, 0.0]
+
+ # cm/tm_prev stores the last modified matrices can be an intermediate position
+ cm_prev: List[float] = [1.0, 0.0, 0.0, 1.0, 0.0, 0.0]
+ tm_prev: List[float] = [1.0, 0.0, 0.0, 1.0, 0.0, 0.0]
+
+ # memo_cm/tm will be used to store the position at the beginning of building the text
+ memo_cm: List[float] = [1.0, 0.0, 0.0, 1.0, 0.0, 0.0]
+ memo_tm: List[float] = [1.0, 0.0, 0.0, 1.0, 0.0, 0.0]
+ char_scale = 1.0
+ space_scale = 1.0
+ _space_width: float = 500.0 # will be set correctly at first Tf
+ TL = 0.0
+ font_size = 12.0 # init just in case of
+
+ def current_spacewidth() -> float:
+ return _space_width / 1000.0
+
+ def process_operation(operator: bytes, operands: List[Any]) -> None:
+ nonlocal cm_matrix, cm_stack, tm_matrix, cm_prev, tm_prev, memo_cm, memo_tm
+ nonlocal char_scale, space_scale, _space_width, TL, font_size, cmap
+ nonlocal orientations, rtl_dir, visitor_text, output, text
+ global CUSTOM_RTL_MIN, CUSTOM_RTL_MAX, CUSTOM_RTL_SPECIAL_CHARS
+
+ check_crlf_space: bool = False
+ # Table 5.4 page 405
+ if operator == b"BT":
+ tm_matrix = [1.0, 0.0, 0.0, 1.0, 0.0, 0.0]
+ output += text
+ if visitor_text is not None:
+ visitor_text(text, memo_cm, memo_tm, cmap[3], font_size)
+ text = ""
+ memo_cm = cm_matrix.copy()
+ memo_tm = tm_matrix.copy()
+ return None
+ elif operator == b"ET":
+ output += text
+ if visitor_text is not None:
+ visitor_text(text, memo_cm, memo_tm, cmap[3], font_size)
+ text = ""
+ memo_cm = cm_matrix.copy()
+ memo_tm = tm_matrix.copy()
+ # table 4.7 "Graphics state operators", page 219
+ # cm_matrix calculation is a reserved for the moment
+ elif operator == b"q":
+ cm_stack.append(
+ (
+ cm_matrix,
+ cmap,
+ font_size,
+ char_scale,
+ space_scale,
+ _space_width,
+ TL,
+ )
+ )
+ elif operator == b"Q":
+ try:
+ (
+ cm_matrix,
+ cmap,
+ font_size,
+ char_scale,
+ space_scale,
+ _space_width,
+ TL,
+ ) = cm_stack.pop()
+ except Exception:
+ cm_matrix = [1.0, 0.0, 0.0, 1.0, 0.0, 0.0]
+ elif operator == b"cm":
+ output += text
+ if visitor_text is not None:
+ visitor_text(text, memo_cm, memo_tm, cmap[3], font_size)
+ text = ""
+ cm_matrix = mult(
+ [
+ float(operands[0]),
+ float(operands[1]),
+ float(operands[2]),
+ float(operands[3]),
+ float(operands[4]),
+ float(operands[5]),
+ ],
+ cm_matrix,
+ )
+ memo_cm = cm_matrix.copy()
+ memo_tm = tm_matrix.copy()
+ # Table 5.2 page 398
+ elif operator == b"Tz":
+ char_scale = float(operands[0]) / 100.0
+ elif operator == b"Tw":
+ space_scale = 1.0 + float(operands[0])
+ elif operator == b"TL":
+ TL = float(operands[0])
+ elif operator == b"Tf":
+ if text != "":
+ output += text # .translate(cmap)
+ if visitor_text is not None:
+ visitor_text(text, memo_cm, memo_tm, cmap[3], font_size)
+ text = ""
+ memo_cm = cm_matrix.copy()
+ memo_tm = tm_matrix.copy()
+ try:
+ # charMapTuple: font_type, float(sp_width / 2), encoding,
+ # map_dict, font-dictionary
+ charMapTuple = cmaps[operands[0]]
+ _space_width = charMapTuple[1]
+ # current cmap: encoding, map_dict, font resource name
+ # (internal name, not the real font-name),
+ # font-dictionary. The font-dictionary describes the font.
+ cmap = (
+ charMapTuple[2],
+ charMapTuple[3],
+ operands[0],
+ charMapTuple[4],
+ )
+ except KeyError: # font not found
+ _space_width = unknown_char_map[1]
+ cmap = (
+ unknown_char_map[2],
+ unknown_char_map[3],
+ "???" + operands[0],
+ None,
+ )
+ try:
+ font_size = float(operands[1])
+ except Exception:
+ pass # keep previous size
+ # Table 5.5 page 406
+ elif operator == b"Td":
+ check_crlf_space = True
+ # A special case is a translating only tm:
+ # tm[0..5] = 1 0 0 1 e f,
+ # i.e. tm[4] += tx, tm[5] += ty.
+ tx = float(operands[0])
+ ty = float(operands[1])
+ tm_matrix[4] += tx * tm_matrix[0] + ty * tm_matrix[2]
+ tm_matrix[5] += tx * tm_matrix[1] + ty * tm_matrix[3]
+ elif operator == b"Tm":
+ check_crlf_space = True
+ tm_matrix = [
+ float(operands[0]),
+ float(operands[1]),
+ float(operands[2]),
+ float(operands[3]),
+ float(operands[4]),
+ float(operands[5]),
+ ]
+ elif operator == b"T*":
+ check_crlf_space = True
+ tm_matrix[5] -= TL
+
+ elif operator == b"Tj":
+ check_crlf_space = True
+ text, rtl_dir = handle_tj(
+ text,
+ operands,
+ cm_matrix,
+ tm_matrix, # text matrix
+ cmap,
+ orientations,
+ output,
+ font_size,
+ rtl_dir,
+ visitor_text,
+ )
+ else:
+ return None
+ if check_crlf_space:
+ try:
+ text, output, cm_prev, tm_prev = crlf_space_check(
+ text,
+ (cm_prev, tm_prev),
+ (cm_matrix, tm_matrix),
+ (memo_cm, memo_tm),
+ cmap,
+ orientations,
+ output,
+ font_size,
+ visitor_text,
+ current_spacewidth(),
+ )
+ if text == "":
+ memo_cm = cm_matrix.copy()
+ memo_tm = tm_matrix.copy()
+ except OrientationNotFoundError:
+ return None
+
+ for operands, operator in content.operations:
+ if visitor_operand_before is not None:
+ visitor_operand_before(operator, operands, cm_matrix, tm_matrix)
+ # multiple operators are defined in here ####
+ if operator == b"'":
+ process_operation(b"T*", [])
+ process_operation(b"Tj", operands)
+ elif operator == b'"':
+ process_operation(b"Tw", [operands[0]])
+ process_operation(b"Tc", [operands[1]])
+ process_operation(b"T*", [])
+ process_operation(b"Tj", operands[2:])
+ elif operator == b"TD":
+ process_operation(b"TL", [-operands[1]])
+ process_operation(b"Td", operands)
+ elif operator == b"TJ":
+ for op in operands[0]:
+ if isinstance(op, (str, bytes)):
+ process_operation(b"Tj", [op])
+ if isinstance(op, (int, float, NumberObject, FloatObject)) and (
+ (abs(float(op)) >= _space_width)
+ and (len(text) > 0)
+ and (text[-1] != " ")
+ ):
+ process_operation(b"Tj", [" "])
+ elif operator == b"Do":
+ output += text
+ if visitor_text is not None:
+ visitor_text(text, memo_cm, memo_tm, cmap[3], font_size)
+ try:
+ if output[-1] != "\n":
+ output += "\n"
+ if visitor_text is not None:
+ visitor_text(
+ "\n",
+ memo_cm,
+ memo_tm,
+ cmap[3],
+ font_size,
+ )
+ except IndexError:
+ pass
+ try:
+ xobj = resources_dict["/XObject"]
+ if xobj[operands[0]]["/Subtype"] != "/Image": # type: ignore
+ text = self.extract_xform_text(
+ xobj[operands[0]], # type: ignore
+ orientations,
+ space_width,
+ visitor_operand_before,
+ visitor_operand_after,
+ visitor_text,
+ )
+ output += text
+ if visitor_text is not None:
+ visitor_text(
+ text,
+ memo_cm,
+ memo_tm,
+ cmap[3],
+ font_size,
+ )
+ except Exception:
+ logger_warning(
+ f" impossible to decode XFormObject {operands[0]}",
+ __name__,
+ )
+ finally:
+ text = ""
+ memo_cm = cm_matrix.copy()
+ memo_tm = tm_matrix.copy()
+
+ else:
+ process_operation(operator, operands)
+ if visitor_operand_after is not None:
+ visitor_operand_after(operator, operands, cm_matrix, tm_matrix)
+ output += text # just in case of
+ if text != "" and visitor_text is not None:
+ visitor_text(text, memo_cm, memo_tm, cmap[3], font_size)
+ return output
+
+ def _layout_mode_fonts(self) -> Dict[str, _layout_mode.Font]:
+ """
+ Get fonts formatted for "layout" mode text extraction.
+
+ Returns:
+ Dict[str, Font]: dictionary of _layout_mode.Font instances keyed by font name
+ """
+ # Font retrieval logic adapted from pypdf.PageObject._extract_text()
+ objr: Any = self
+ fonts: Dict[str, _layout_mode.Font] = {}
+ while objr is not None:
+ try:
+ resources_dict: Any = objr[PG.RESOURCES]
+ except KeyError:
+ resources_dict = {}
+ if "/Font" in resources_dict and self.pdf is not None:
+ for font_name in resources_dict["/Font"]:
+ *cmap, font_dict_obj = build_char_map(font_name, 200.0, self)
+ font_dict = {
+ k: v.get_object()
+ if isinstance(v, IndirectObject)
+ else [_v.get_object() for _v in v]
+ if isinstance(v, ArrayObject)
+ else v
+ for k, v in font_dict_obj.items()
+ }
+ # mypy really sucks at unpacking
+ fonts[font_name] = _layout_mode.Font(*cmap, font_dict) # type: ignore[call-arg,arg-type]
+ try:
+ objr = objr["/Parent"].get_object()
+ except KeyError:
+ objr = None
+
+ return fonts
+
+ def _layout_mode_text(
+ self,
+ space_vertically: bool = True,
+ scale_weight: float = 1.25,
+ strip_rotated: bool = True,
+ debug_path: Optional[Path] = None,
+ ) -> str:
+ """
+ Get text preserving fidelity to source PDF text layout.
+
+ Args:
+ space_vertically: include blank lines inferred from y distance + font
+ height. Defaults to True.
+ scale_weight: multiplier for string length when calculating weighted
+ average character width. Defaults to 1.25.
+ strip_rotated: Removes text that is rotated w.r.t. to the page from
+ layout mode output. Defaults to True.
+ debug_path (Path | None): if supplied, must target a directory.
+ creates the following files with debug information for layout mode
+ functions if supplied:
+ - fonts.json: output of self._layout_mode_fonts
+ - tjs.json: individual text render ops with corresponding transform matrices
+ - bts.json: text render ops left justified and grouped by BT/ET operators
+ - bt_groups.json: BT/ET operations grouped by rendered y-coord (aka lines)
+ Defaults to None.
+
+ Returns:
+ str: multiline string containing page text in a fixed width format that
+ closely adheres to the rendered layout in the source pdf.
+ """
+ fonts = self._layout_mode_fonts()
+ if debug_path: # pragma: no cover
+ import json
+
+ debug_path.joinpath("fonts.json").write_text(
+ json.dumps(
+ fonts, indent=2, default=lambda x: getattr(x, "to_dict", str)(x)
+ ),
+ "utf-8",
+ )
+
+ ops = iter(
+ ContentStream(self["/Contents"].get_object(), self.pdf, "bytes").operations
+ )
+ bt_groups = _layout_mode.text_show_operations(
+ ops, fonts, strip_rotated, debug_path
+ )
+
+ if not bt_groups:
+ return ""
+
+ ty_groups = _layout_mode.y_coordinate_groups(bt_groups, debug_path)
+
+ char_width = _layout_mode.fixed_char_width(bt_groups, scale_weight)
+
+ return _layout_mode.fixed_width_page(ty_groups, char_width, space_vertically)
+
+ def extract_text(
+ self,
+ *args: Any,
+ orientations: Union[int, Tuple[int, ...]] = (0, 90, 180, 270),
+ space_width: float = 200.0,
+ visitor_operand_before: Optional[Callable[[Any, Any, Any, Any], None]] = None,
+ visitor_operand_after: Optional[Callable[[Any, Any, Any, Any], None]] = None,
+ visitor_text: Optional[Callable[[Any, Any, Any, Any, Any], None]] = None,
+ extraction_mode: Literal["plain", "layout"] = "plain",
+ **kwargs: Any,
+ ) -> str:
+ """
+ Locate all text drawing commands, in the order they are provided in the
+ content stream, and extract the text.
+
+ This works well for some PDF files, but poorly for others, depending on
+ the generator used. This will be refined in the future.
+
+ Do not rely on the order of text coming out of this function, as it
+ will change if this function is made more sophisticated.
+
+ Arabic and Hebrew are extracted in the correct order.
+ If required a custom RTL range of characters can be defined;
+ see function set_custom_rtl.
+
+ Additionally you can provide visitor methods to get informed on all
+ operations and all text objects.
+ For example in some PDF files this can be useful to parse tables.
+
+ Args:
+ orientations: list of orientations extract_text will look for
+ default = (0, 90, 180, 270)
+ note: currently only 0 (up),90 (turned left), 180 (upside down),
+ 270 (turned right)
+ space_width: force default space width
+ if not extracted from font (default: 200)
+ visitor_operand_before: function to be called before processing an operation.
+ It has four arguments: operator, operand-arguments,
+ current transformation matrix and text matrix.
+ visitor_operand_after: function to be called after processing an operation.
+ It has four arguments: operator, operand-arguments,
+ current transformation matrix and text matrix.
+ visitor_text: function to be called when extracting some text at some position.
+ It has five arguments: text, current transformation matrix,
+ text matrix, font-dictionary and font-size.
+ The font-dictionary may be None in case of unknown fonts.
+ If not None it may e.g. contain key "/BaseFont" with value "/Arial,Bold".
+ extraction_mode (Literal["plain", "layout"]): "plain" for legacy functionality,
+ "layout" for experimental layout mode functionality.
+ NOTE: orientations, space_width, and visitor_* parameters are NOT respected
+ in "layout" mode.
+
+ kwargs:
+ layout_mode_space_vertically (bool): include blank lines inferred from
+ y distance + font height. Defaults to True.
+ layout_mode_scale_weight (float): multiplier for string length when calculating
+ weighted average character width. Defaults to 1.25.
+ layout_mode_strip_rotated (bool): layout mode does not support rotated text.
+ Set to False to include rotated text anyway. If rotated text is discovered,
+ layout will be degraded and a warning will result. Defaults to True.
+ layout_mode_debug_path (Path | None): if supplied, must target a directory.
+ creates the following files with debug information for layout mode
+ functions if supplied:
+
+ - fonts.json: output of self._layout_mode_fonts
+ - tjs.json: individual text render ops with corresponding transform matrices
+ - bts.json: text render ops left justified and grouped by BT/ET operators
+ - bt_groups.json: BT/ET operations grouped by rendered y-coord (aka lines)
+
+ Returns:
+ The extracted text
+ """
+ if extraction_mode not in ["plain", "layout"]:
+ raise ValueError(f"Invalid text extraction mode '{extraction_mode}'")
+ if extraction_mode == "layout":
+ return self._layout_mode_text(
+ space_vertically=kwargs.get("layout_mode_space_vertically", True),
+ scale_weight=kwargs.get("layout_mode_scale_weight", 1.25),
+ strip_rotated=kwargs.get("layout_mode_strip_rotated", True),
+ debug_path=kwargs.get("layout_mode_debug_path", None),
+ )
+ if len(args) >= 1:
+ if isinstance(args[0], str):
+ if len(args) >= 3:
+ if isinstance(args[2], (tuple, int)):
+ orientations = args[2]
+ else:
+ raise TypeError(f"Invalid positional parameter {args[2]}")
+ if len(args) >= 4:
+ if isinstance(args[3], (float, int)):
+ space_width = args[3]
+ else:
+ raise TypeError(f"Invalid positional parameter {args[3]}")
+ elif isinstance(args[0], (tuple, int)):
+ orientations = args[0]
+ if len(args) >= 2:
+ if isinstance(args[1], (float, int)):
+ space_width = args[1]
+ else:
+ raise TypeError(f"Invalid positional parameter {args[1]}")
+ else:
+ raise TypeError(f"Invalid positional parameter {args[0]}")
+
+ if isinstance(orientations, int):
+ orientations = (orientations,)
+
+ return self._extract_text(
+ self,
+ self.pdf,
+ orientations,
+ space_width,
+ PG.CONTENTS,
+ visitor_operand_before,
+ visitor_operand_after,
+ visitor_text,
+ )
+
+ def extract_xform_text(
+ self,
+ xform: EncodedStreamObject,
+ orientations: Tuple[int, ...] = (0, 90, 270, 360),
+ space_width: float = 200.0,
+ visitor_operand_before: Optional[Callable[[Any, Any, Any, Any], None]] = None,
+ visitor_operand_after: Optional[Callable[[Any, Any, Any, Any], None]] = None,
+ visitor_text: Optional[Callable[[Any, Any, Any, Any, Any], None]] = None,
+ ) -> str:
+ """
+ Extract text from an XObject.
+
+ Args:
+ xform:
+ orientations:
+ space_width: force default space width (if not extracted from font (default 200)
+ visitor_operand_before:
+ visitor_operand_after:
+ visitor_text:
+
+ Returns:
+ The extracted text
+ """
+ return self._extract_text(
+ xform,
+ self.pdf,
+ orientations,
+ space_width,
+ None,
+ visitor_operand_before,
+ visitor_operand_after,
+ visitor_text,
+ )
+
+ def _get_fonts(self) -> Tuple[Set[str], Set[str]]:
+ """
+ Get the names of embedded fonts and unembedded fonts.
+
+ Returns:
+ A tuple (Set of embedded fonts, set of unembedded fonts)
+ """
+ obj = self.get_object()
+ assert isinstance(obj, DictionaryObject)
+ fonts: Set[str] = set()
+ embedded: Set[str] = set()
+ fonts, embedded = _get_fonts_walk(obj, fonts, embedded)
+ unembedded = fonts - embedded
+ return embedded, unembedded
+
+ mediabox = _create_rectangle_accessor(PG.MEDIABOX, ())
+ """A :class:`RectangleObject<pypdf.generic.RectangleObject>`, expressed in
+ default user space units, defining the boundaries of the physical medium on
+ which the page is intended to be displayed or printed."""
+
+ cropbox = _create_rectangle_accessor("/CropBox", (PG.MEDIABOX,))
+ """
+ A :class:`RectangleObject<pypdf.generic.RectangleObject>`, expressed in
+ default user space units, defining the visible region of default user
+ space.
+
+ When the page is displayed or printed, its contents are to be clipped
+ (cropped) to this rectangle and then imposed on the output medium in some
+ implementation-defined manner. Default value: same as
+ :attr:`mediabox<mediabox>`.
+ """
+
+ bleedbox = _create_rectangle_accessor("/BleedBox", ("/CropBox", PG.MEDIABOX))
+ """A :class:`RectangleObject<pypdf.generic.RectangleObject>`, expressed in
+ default user space units, defining the region to which the contents of the
+ page should be clipped when output in a production environment."""
+
+ trimbox = _create_rectangle_accessor("/TrimBox", ("/CropBox", PG.MEDIABOX))
+ """A :class:`RectangleObject<pypdf.generic.RectangleObject>`, expressed in
+ default user space units, defining the intended dimensions of the finished
+ page after trimming."""
+
+ artbox = _create_rectangle_accessor("/ArtBox", ("/CropBox", PG.MEDIABOX))
+ """A :class:`RectangleObject<pypdf.generic.RectangleObject>`, expressed in
+ default user space units, defining the extent of the page's meaningful
+ content as intended by the page's creator."""
+
+ @property
+ def annotations(self) -> Optional[ArrayObject]:
+ if "/Annots" not in self:
+ return None
+ else:
+ return cast(ArrayObject, self["/Annots"])
+
+ @annotations.setter
+ def annotations(self, value: Optional[ArrayObject]) -> None:
+ """
+ Set the annotations array of the page.
+
+ Typically you do not want to set this value, but append to it.
+ If you append to it, remember to add the object first to the writer
+ and only add the indirect object.
+ """
+ if value is None:
+ del self[NameObject("/Annots")]
+ else:
+ self[NameObject("/Annots")] = value
+
+
+class _VirtualList(Sequence[PageObject]):
+ def __init__(
+ self,
+ length_function: Callable[[], int],
+ get_function: Callable[[int], PageObject],
+ ) -> None:
+ self.length_function = length_function
+ self.get_function = get_function
+ self.current = -1
+
+ def __len__(self) -> int:
+ return self.length_function()
+
+ @overload
+ def __getitem__(self, index: int) -> PageObject:
+ ...
+
+ @overload
+ def __getitem__(self, index: slice) -> Sequence[PageObject]:
+ ...
+
+ def __getitem__(
+ self, index: Union[int, slice]
+ ) -> Union[PageObject, Sequence[PageObject]]:
+ if isinstance(index, slice):
+ indices = range(*index.indices(len(self)))
+ cls = type(self)
+ return cls(indices.__len__, lambda idx: self[indices[idx]])
+ if not isinstance(index, int):
+ raise TypeError("sequence indices must be integers")
+ len_self = len(self)
+ if index < 0:
+ # support negative indexes
+ index = len_self + index
+ if index < 0 or index >= len_self:
+ raise IndexError("sequence index out of range")
+ return self.get_function(index)
+
+ def __delitem__(self, index: Union[int, slice]) -> None:
+ if isinstance(index, slice):
+ r = list(range(*index.indices(len(self))))
+ # pages have to be deleted from last to first
+ r.sort()
+ r.reverse()
+ for p in r:
+ del self[p] # recursive call
+ return
+ if not isinstance(index, int):
+ raise TypeError("index must be integers")
+ len_self = len(self)
+ if index < 0:
+ # support negative indexes
+ index = len_self + index
+ if index < 0 or index >= len_self:
+ raise IndexError("index out of range")
+ ind = self[index].indirect_reference
+ assert ind is not None
+ parent = cast(DictionaryObject, ind.get_object()).get("/Parent", None)
+ while parent is not None:
+ parent = cast(DictionaryObject, parent.get_object())
+ try:
+ i = parent["/Kids"].index(ind)
+ del parent["/Kids"][i]
+ try:
+ assert ind is not None
+ del ind.pdf.flattened_pages[index] # case of page in a Reader
+ except Exception: # pragma: no cover
+ pass
+ if "/Count" in parent:
+ parent[NameObject("/Count")] = NumberObject(parent["/Count"] - 1)
+ if len(parent["/Kids"]) == 0:
+ # No more objects in this part of this sub tree
+ ind = parent.indirect_reference
+ parent = cast(DictionaryObject, parent.get("/Parent", None))
+ else:
+ parent = None
+ except ValueError: # from index
+ raise PdfReadError(f"Page Not Found in Page Tree {ind}")
+
+ def __iter__(self) -> Iterator[PageObject]:
+ for i in range(len(self)):
+ yield self[i]
+
+ def __str__(self) -> str:
+ p = [f"PageObject({i})" for i in range(self.length_function())]
+ return f"[{', '.join(p)}]"
+
+
+def _get_fonts_walk(
+ obj: DictionaryObject,
+ fnt: Set[str],
+ emb: Set[str],
+) -> Tuple[Set[str], Set[str]]:
+ """
+ Get the set of all fonts and all embedded fonts.
+
+ Args:
+ obj: Page resources dictionary
+ fnt: font
+ emb: embedded fonts
+
+ Returns:
+ A tuple (fnt, emb)
+
+ If there is a key called 'BaseFont', that is a font that is used in the document.
+ If there is a key called 'FontName' and another key in the same dictionary object
+ that is called 'FontFilex' (where x is null, 2, or 3), then that fontname is
+ embedded.
+
+ We create and add to two sets, fnt = fonts used and emb = fonts embedded.
+ """
+ fontkeys = ("/FontFile", "/FontFile2", "/FontFile3")
+
+ def process_font(f: DictionaryObject) -> None:
+ nonlocal fnt, emb
+ f = cast(DictionaryObject, f.get_object()) # to be sure
+ if "/BaseFont" in f:
+ fnt.add(cast(str, f["/BaseFont"]))
+
+ if (
+ ("/CharProcs" in f)
+ or (
+ "/FontDescriptor" in f
+ and any(
+ x in cast(DictionaryObject, f["/FontDescriptor"]) for x in fontkeys
+ )
+ )
+ or (
+ "/DescendantFonts" in f
+ and "/FontDescriptor"
+ in cast(
+ DictionaryObject,
+ cast(ArrayObject, f["/DescendantFonts"])[0].get_object(),
+ )
+ and any(
+ x
+ in cast(
+ DictionaryObject,
+ cast(
+ DictionaryObject,
+ cast(ArrayObject, f["/DescendantFonts"])[0].get_object(),
+ )["/FontDescriptor"],
+ )
+ for x in fontkeys
+ )
+ )
+ ):
+ # the list comprehension ensures there is FontFile
+ try:
+ emb.add(cast(str, f["/BaseFont"]))
+ except KeyError:
+ emb.add("(" + cast(str, f["/Subtype"]) + ")")
+
+ if "/DR" in obj and "/Font" in cast(DictionaryObject, obj["/DR"]):
+ for f in cast(DictionaryObject, cast(DictionaryObject, obj["/DR"])["/Font"]):
+ process_font(f)
+ if "/Resources" in obj:
+ if "/Font" in cast(DictionaryObject, obj["/Resources"]):
+ for f in cast(
+ DictionaryObject, cast(DictionaryObject, obj["/Resources"])["/Font"]
+ ).values():
+ process_font(f)
+ if "/XObject" in cast(DictionaryObject, obj["/Resources"]):
+ for x in cast(
+ DictionaryObject, cast(DictionaryObject, obj["/Resources"])["/XObject"]
+ ).values():
+ _get_fonts_walk(cast(DictionaryObject, x.get_object()), fnt, emb)
+ if "/Annots" in obj:
+ for a in cast(ArrayObject, obj["/Annots"]):
+ _get_fonts_walk(cast(DictionaryObject, a.get_object()), fnt, emb)
+ if "/AP" in obj:
+ if (
+ cast(DictionaryObject, cast(DictionaryObject, obj["/AP"])["/N"]).get(
+ "/Type"
+ )
+ == "/XObject"
+ ):
+ _get_fonts_walk(
+ cast(DictionaryObject, cast(DictionaryObject, obj["/AP"])["/N"]),
+ fnt,
+ emb,
+ )
+ else:
+ for a in cast(DictionaryObject, cast(DictionaryObject, obj["/AP"])["/N"]):
+ _get_fonts_walk(cast(DictionaryObject, a), fnt, emb)
+ return fnt, emb # return the sets for each page
+
+
+class _VirtualListImages(Sequence[ImageFile]):
+ def __init__(
+ self,
+ ids_function: Callable[[], List[Union[str, List[str]]]],
+ get_function: Callable[[Union[str, List[str], Tuple[str]]], ImageFile],
+ ) -> None:
+ self.ids_function = ids_function
+ self.get_function = get_function
+ self.current = -1
+
+ def __len__(self) -> int:
+ return len(self.ids_function())
+
+ def keys(self) -> List[Union[str, List[str]]]:
+ return self.ids_function()
+
+ def items(self) -> List[Tuple[Union[str, List[str]], ImageFile]]:
+ return [(x, self[x]) for x in self.ids_function()]
+
+ @overload
+ def __getitem__(self, index: Union[int, str, List[str]]) -> ImageFile:
+ ...
+
+ @overload
+ def __getitem__(self, index: slice) -> Sequence[ImageFile]:
+ ...
+
+ def __getitem__(
+ self, index: Union[int, slice, str, List[str], Tuple[str]]
+ ) -> Union[ImageFile, Sequence[ImageFile]]:
+ lst = self.ids_function()
+ if isinstance(index, slice):
+ indices = range(*index.indices(len(self)))
+ lst = [lst[x] for x in indices]
+ cls = type(self)
+ return cls((lambda: lst), self.get_function)
+ if isinstance(index, (str, list, tuple)):
+ return self.get_function(index)
+ if not isinstance(index, int):
+ raise TypeError("invalid sequence indices type")
+ len_self = len(lst)
+ if index < 0:
+ # support negative indexes
+ index = len_self + index
+ if index < 0 or index >= len_self:
+ raise IndexError("sequence index out of range")
+ return self.get_function(lst[index])
+
+ def __iter__(self) -> Iterator[ImageFile]:
+ for i in range(len(self)):
+ yield self[i]
+
+ def __str__(self) -> str:
+ p = [f"Image_{i}={n}" for i, n in enumerate(self.ids_function())]
+ return f"[{', '.join(p)}]"
diff --git a/.venv/lib/python3.12/site-packages/pypdf/_page_labels.py b/.venv/lib/python3.12/site-packages/pypdf/_page_labels.py
new file mode 100644
index 00000000..b0252795
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/pypdf/_page_labels.py
@@ -0,0 +1,280 @@
+"""
+Page labels are shown by PDF viewers as "the page number".
+
+A page has a numeric index, starting at 0. Additionally, the page
+has a label. In the most simple case:
+
+ label = index + 1
+
+However, the title page and the table of contents might have Roman numerals as
+page labels. This makes things more complicated.
+
+Example 1
+---------
+
+>>> reader.root_object["/PageLabels"]["/Nums"]
+[0, IndirectObject(18, 0, 139929798197504),
+ 8, IndirectObject(19, 0, 139929798197504)]
+>>> reader.get_object(reader.root_object["/PageLabels"]["/Nums"][1])
+{'/S': '/r'}
+>>> reader.get_object(reader.root_object["/PageLabels"]["/Nums"][3])
+{'/S': '/D'}
+
+Example 2
+---------
+The following is a document with pages labeled
+i, ii, iii, iv, 1, 2, 3, A-8, A-9, ...
+
+1 0 obj
+ << /Type /Catalog
+ /PageLabels << /Nums [
+ 0 << /S /r >>
+ 4 << /S /D >>
+ 7 << /S /D
+ /P ( A- )
+ /St 8
+ >>
+ % A number tree containing
+ % three page label dictionaries
+ ]
+ >>
+ ...
+ >>
+endobj
+
+
+§12.4.2 PDF Specification 1.7 and 2.0
+=====================================
+
+Entries in a page label dictionary
+----------------------------------
+The /S key:
+D Decimal Arabic numerals
+R Uppercase Roman numerals
+r Lowercase Roman numerals
+A Uppercase letters (A to Z for the first 26 pages,
+ AA to ZZ for the next 26, and so on)
+a Lowercase letters (a to z for the first 26 pages,
+ aa to zz for the next 26, and so on)
+"""
+
+from typing import Iterator, List, Optional, Tuple, cast
+
+from ._protocols import PdfCommonDocProtocol
+from ._utils import logger_warning
+from .generic import ArrayObject, DictionaryObject, NullObject, NumberObject
+
+
+def number2uppercase_roman_numeral(num: int) -> str:
+ roman = [
+ (1000, "M"),
+ (900, "CM"),
+ (500, "D"),
+ (400, "CD"),
+ (100, "C"),
+ (90, "XC"),
+ (50, "L"),
+ (40, "XL"),
+ (10, "X"),
+ (9, "IX"),
+ (5, "V"),
+ (4, "IV"),
+ (1, "I"),
+ ]
+
+ def roman_num(num: int) -> Iterator[str]:
+ for decimal, roman_repr in roman:
+ x, _ = divmod(num, decimal)
+ yield roman_repr * x
+ num -= decimal * x
+ if num <= 0:
+ break
+
+ return "".join(list(roman_num(num)))
+
+
+def number2lowercase_roman_numeral(number: int) -> str:
+ return number2uppercase_roman_numeral(number).lower()
+
+
+def number2uppercase_letter(number: int) -> str:
+ if number <= 0:
+ raise ValueError("Expecting a positive number")
+ alphabet = [chr(i) for i in range(ord("A"), ord("Z") + 1)]
+ rep = ""
+ while number > 0:
+ remainder = number % 26
+ if remainder == 0:
+ remainder = 26
+ rep = alphabet[remainder - 1] + rep
+ # update
+ number -= remainder
+ number = number // 26
+ return rep
+
+
+def number2lowercase_letter(number: int) -> str:
+ return number2uppercase_letter(number).lower()
+
+
+def get_label_from_nums(dictionary_object: DictionaryObject, index: int) -> str:
+ # [Nums] shall be an array of the form
+ # [ key 1 value 1 key 2 value 2 ... key n value n ]
+ # where each key_i is an integer and the corresponding
+ # value_i shall be the object associated with that key.
+ # The keys shall be sorted in numerical order,
+ # analogously to the arrangement of keys in a name tree
+ # as described in 7.9.6, "Name Trees."
+ nums = cast(ArrayObject, dictionary_object["/Nums"])
+ i = 0
+ value = None
+ start_index = 0
+ while i < len(nums):
+ start_index = nums[i]
+ value = nums[i + 1].get_object()
+ if i + 2 == len(nums):
+ break
+ if nums[i + 2] > index:
+ break
+ i += 2
+ m = {
+ None: lambda n: "",
+ "/D": lambda n: str(n),
+ "/R": number2uppercase_roman_numeral,
+ "/r": number2lowercase_roman_numeral,
+ "/A": number2uppercase_letter,
+ "/a": number2lowercase_letter,
+ }
+ # if /Nums array is not following the specification or if /Nums is empty
+ if not isinstance(value, dict):
+ return str(index + 1) # Fallback
+ start = value.get("/St", 1)
+ prefix = value.get("/P", "")
+ return prefix + m[value.get("/S")](index - start_index + start)
+
+
+def index2label(reader: PdfCommonDocProtocol, index: int) -> str:
+ """
+ See 7.9.7 "Number Trees".
+
+ Args:
+ reader: The PdfReader
+ index: The index of the page
+
+ Returns:
+ The label of the page, e.g. "iv" or "4".
+ """
+ root = cast(DictionaryObject, reader.root_object)
+ if "/PageLabels" not in root:
+ return str(index + 1) # Fallback
+ number_tree = cast(DictionaryObject, root["/PageLabels"].get_object())
+ if "/Nums" in number_tree:
+ return get_label_from_nums(number_tree, index)
+ if "/Kids" in number_tree and not isinstance(number_tree["/Kids"], NullObject):
+ # number_tree = {'/Kids': [IndirectObject(7333, 0, 140132998195856), ...]}
+ # Limit maximum depth.
+ level = 0
+ while level < 100:
+ kids = cast(List[DictionaryObject], number_tree["/Kids"])
+ for kid in kids:
+ # kid = {'/Limits': [0, 63], '/Nums': [0, {'/P': 'C1'}, ...]}
+ limits = cast(List[int], kid["/Limits"])
+ if limits[0] <= index <= limits[1]:
+ if kid.get("/Kids", None) is not None:
+ # Recursive definition.
+ level += 1
+ if level == 100: # pragma: no cover
+ raise NotImplementedError("Too deep nesting is not supported.")
+ number_tree = kid
+ # Exit the inner `for` loop and continue at the next level with the
+ # next iteration of the `while` loop.
+ break
+ return get_label_from_nums(kid, index)
+ else:
+ # When there are no kids, make sure to exit the `while` loop directly
+ # and continue with the fallback.
+ break
+
+ logger_warning(
+ f"Could not reliably determine page label for {index}.",
+ __name__
+ )
+ return str(index + 1) # Fallback if neither /Nums nor /Kids is in the number_tree
+
+
+def nums_insert(
+ key: NumberObject,
+ value: DictionaryObject,
+ nums: ArrayObject,
+) -> None:
+ """
+ Insert a key, value pair in a Nums array.
+
+ See 7.9.7 "Number Trees".
+
+ Args:
+ key: number key of the entry
+ value: value of the entry
+ nums: Nums array to modify
+ """
+ if len(nums) % 2 != 0:
+ raise ValueError("a nums like array must have an even number of elements")
+
+ i = len(nums)
+ while i != 0 and key <= nums[i - 2]:
+ i = i - 2
+
+ if i < len(nums) and key == nums[i]:
+ nums[i + 1] = value
+ else:
+ nums.insert(i, key)
+ nums.insert(i + 1, value)
+
+
+def nums_clear_range(
+ key: NumberObject,
+ page_index_to: int,
+ nums: ArrayObject,
+) -> None:
+ """
+ Remove all entries in a number tree in a range after an entry.
+
+ See 7.9.7 "Number Trees".
+
+ Args:
+ key: number key of the entry before the range
+ page_index_to: The page index of the upper limit of the range
+ nums: Nums array to modify
+ """
+ if len(nums) % 2 != 0:
+ raise ValueError("a nums like array must have an even number of elements")
+ if page_index_to < key:
+ raise ValueError("page_index_to must be greater or equal than key")
+
+ i = nums.index(key) + 2
+ while i < len(nums) and nums[i] <= page_index_to:
+ nums.pop(i)
+ nums.pop(i)
+
+
+def nums_next(
+ key: NumberObject,
+ nums: ArrayObject,
+) -> Tuple[Optional[NumberObject], Optional[DictionaryObject]]:
+ """
+ Return the (key, value) pair of the entry after the given one.
+
+ See 7.9.7 "Number Trees".
+
+ Args:
+ key: number key of the entry
+ nums: Nums array
+ """
+ if len(nums) % 2 != 0:
+ raise ValueError("a nums like array must have an even number of elements")
+
+ i = nums.index(key) + 2
+ if i < len(nums):
+ return (nums[i], nums[i + 1])
+ else:
+ return (None, None)
diff --git a/.venv/lib/python3.12/site-packages/pypdf/_protocols.py b/.venv/lib/python3.12/site-packages/pypdf/_protocols.py
new file mode 100644
index 00000000..9f413660
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/pypdf/_protocols.py
@@ -0,0 +1,89 @@
+"""Helpers for working with PDF types."""
+
+from abc import abstractmethod
+from pathlib import Path
+from typing import IO, Any, Dict, List, Optional, Tuple, Union
+
+try:
+ # Python 3.8+: https://peps.python.org/pep-0586
+ from typing import Protocol
+except ImportError:
+ from typing_extensions import Protocol # type: ignore[assignment]
+
+from ._utils import StrByteType, StreamType
+
+
+class PdfObjectProtocol(Protocol):
+ indirect_reference: Any
+
+ def clone(
+ self,
+ pdf_dest: Any,
+ force_duplicate: bool = False,
+ ignore_fields: Union[Tuple[str, ...], List[str], None] = (),
+ ) -> Any:
+ ... # pragma: no cover
+
+ def _reference_clone(self, clone: Any, pdf_dest: Any) -> Any:
+ ... # pragma: no cover
+
+ def get_object(self) -> Optional["PdfObjectProtocol"]:
+ ... # pragma: no cover
+
+ def hash_value(self) -> bytes:
+ ... # pragma: no cover
+
+ def write_to_stream(
+ self, stream: StreamType, encryption_key: Union[None, str, bytes] = None
+ ) -> None:
+ ... # pragma: no cover
+
+
+class XmpInformationProtocol(PdfObjectProtocol):
+ pass
+
+
+class PdfCommonDocProtocol(Protocol):
+ @property
+ def pdf_header(self) -> str:
+ ... # pragma: no cover
+
+ @property
+ def pages(self) -> List[Any]:
+ ... # pragma: no cover
+
+ @property
+ def root_object(self) -> PdfObjectProtocol:
+ ... # pragma: no cover
+
+ def get_object(self, indirect_reference: Any) -> Optional[PdfObjectProtocol]:
+ ... # pragma: no cover
+
+ @property
+ def strict(self) -> bool:
+ ... # pragma: no cover
+
+
+class PdfReaderProtocol(PdfCommonDocProtocol, Protocol):
+ @property
+ @abstractmethod
+ def xref(self) -> Dict[int, Dict[int, Any]]:
+ ... # pragma: no cover
+
+ @property
+ @abstractmethod
+ def trailer(self) -> Dict[str, Any]:
+ ... # pragma: no cover
+
+
+class PdfWriterProtocol(PdfCommonDocProtocol, Protocol):
+ _objects: List[Any]
+ _id_translated: Dict[int, Dict[int, int]]
+
+ @abstractmethod
+ def write(self, stream: Union[Path, StrByteType]) -> Tuple[bool, IO[Any]]:
+ ... # pragma: no cover
+
+ @abstractmethod
+ def _add_object(self, obj: Any) -> Any:
+ ... # pragma: no cover
diff --git a/.venv/lib/python3.12/site-packages/pypdf/_reader.py b/.venv/lib/python3.12/site-packages/pypdf/_reader.py
new file mode 100644
index 00000000..aeababa7
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/pypdf/_reader.py
@@ -0,0 +1,1159 @@
+# Copyright (c) 2006, Mathieu Fenniak
+# Copyright (c) 2007, Ashish Kulkarni <kulkarni.ashish@gmail.com>
+#
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
+#
+# * Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
+# * Redistributions in binary form must reproduce the above copyright notice,
+# this list of conditions and the following disclaimer in the documentation
+# and/or other materials provided with the distribution.
+# * The name of the author may not be used to endorse or promote products
+# derived from this software without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+# POSSIBILITY OF SUCH DAMAGE.
+
+import os
+import re
+from io import BytesIO, UnsupportedOperation
+from pathlib import Path
+from types import TracebackType
+from typing import (
+ Any,
+ Callable,
+ Dict,
+ Iterable,
+ List,
+ Optional,
+ Tuple,
+ Type,
+ Union,
+ cast,
+)
+
+from ._doc_common import PdfDocCommon, convert_to_int
+from ._encryption import Encryption, PasswordType
+from ._page import PageObject
+from ._utils import (
+ StrByteType,
+ StreamType,
+ b_,
+ logger_warning,
+ read_non_whitespace,
+ read_previous_line,
+ read_until_whitespace,
+ skip_over_comment,
+ skip_over_whitespace,
+)
+from .constants import TrailerKeys as TK
+from .errors import (
+ EmptyFileError,
+ FileNotDecryptedError,
+ PdfReadError,
+ PdfStreamError,
+ WrongPasswordError,
+)
+from .generic import (
+ ArrayObject,
+ ContentStream,
+ DecodedStreamObject,
+ DictionaryObject,
+ EncodedStreamObject,
+ IndirectObject,
+ NameObject,
+ NullObject,
+ NumberObject,
+ PdfObject,
+ TextStringObject,
+ read_object,
+)
+from .xmp import XmpInformation
+
+
+class PdfReader(PdfDocCommon):
+ """
+ Initialize a PdfReader object.
+
+ This operation can take some time, as the PDF stream's cross-reference
+ tables are read into memory.
+
+ Args:
+ stream: A File object or an object that supports the standard read
+ and seek methods similar to a File object. Could also be a
+ string representing a path to a PDF file.
+ strict: Determines whether user should be warned of all
+ problems and also causes some correctable problems to be fatal.
+ Defaults to ``False``.
+ password: Decrypt PDF file at initialization. If the
+ password is None, the file will not be decrypted.
+ Defaults to ``None``.
+ """
+
+ def __init__(
+ self,
+ stream: Union[StrByteType, Path],
+ strict: bool = False,
+ password: Union[None, str, bytes] = None,
+ ) -> None:
+ self.strict = strict
+ self.flattened_pages: Optional[List[PageObject]] = None
+ #: Storage of parsed PDF objects.
+ self.resolved_objects: Dict[Tuple[Any, Any], Optional[PdfObject]] = {}
+
+ self.xref_index = 0
+ self.xref: Dict[int, Dict[Any, Any]] = {}
+ self.xref_free_entry: Dict[int, Dict[Any, Any]] = {}
+ self.xref_objStm: Dict[int, Tuple[Any, Any]] = {}
+ self.trailer = DictionaryObject()
+
+ self._page_id2num: Optional[
+ Dict[Any, Any]
+ ] = None # map page indirect_reference number to Page Number
+ if hasattr(stream, "mode") and "b" not in stream.mode:
+ logger_warning(
+ "PdfReader stream/file object is not in binary mode. "
+ "It may not be read correctly.",
+ __name__,
+ )
+ self._stream_opened = False
+ if isinstance(stream, (str, Path)):
+ with open(stream, "rb") as fh:
+ stream = BytesIO(fh.read())
+ self._stream_opened = True
+ self.read(stream)
+ self.stream = stream
+
+ self._override_encryption = False
+ self._encryption: Optional[Encryption] = None
+ if self.is_encrypted:
+ self._override_encryption = True
+ # Some documents may not have a /ID, use two empty
+ # byte strings instead. Solves
+ # https://github.com/py-pdf/pypdf/issues/608
+ id_entry = self.trailer.get(TK.ID)
+ id1_entry = id_entry[0].get_object().original_bytes if id_entry else b""
+ encrypt_entry = cast(
+ DictionaryObject, self.trailer[TK.ENCRYPT].get_object()
+ )
+ self._encryption = Encryption.read(encrypt_entry, id1_entry)
+
+ # try empty password if no password provided
+ pwd = password if password is not None else b""
+ if (
+ self._encryption.verify(pwd) == PasswordType.NOT_DECRYPTED
+ and password is not None
+ ):
+ # raise if password provided
+ raise WrongPasswordError("Wrong password")
+ self._override_encryption = False
+ elif password is not None:
+ raise PdfReadError("Not encrypted file")
+
+ def __enter__(self) -> "PdfReader":
+ return self
+
+ def __exit__(
+ self,
+ exc_type: Optional[Type[BaseException]],
+ exc_val: Optional[BaseException],
+ exc_tb: Optional[TracebackType],
+ ) -> None:
+ self.close()
+
+ def close(self) -> None:
+ """Close the stream if opened in __init__ and clear memory."""
+ if self._stream_opened:
+ self.stream.close()
+ self.flattened_pages = []
+ self.resolved_objects = {}
+ self.trailer = DictionaryObject()
+ self.xref = {}
+ self.xref_free_entry = {}
+ self.xref_objStm = {}
+
+ @property
+ def root_object(self) -> DictionaryObject:
+ """Provide access to "/Root". Standardized with PdfWriter."""
+ return cast(DictionaryObject, self.trailer[TK.ROOT].get_object())
+
+ @property
+ def _info(self) -> Optional[DictionaryObject]:
+ """
+ Provide access to "/Info". Standardized with PdfWriter.
+
+ Returns:
+ /Info Dictionary; None if the entry does not exist
+ """
+ info = self.trailer.get(TK.INFO, None)
+ if info is None:
+ return None
+ else:
+ info = info.get_object()
+ if info is None:
+ raise PdfReadError(
+ "Trailer not found or does not point to document information directory"
+ )
+ return cast(DictionaryObject, info)
+
+ @property
+ def _ID(self) -> Optional[ArrayObject]:
+ """
+ Provide access to "/ID". Standardized with PdfWriter.
+
+ Returns:
+ /ID array; None if the entry does not exist
+ """
+ id = self.trailer.get(TK.ID, None)
+ return None if id is None else cast(ArrayObject, id.get_object())
+
+ def _repr_mimebundle_(
+ self,
+ include: Union[None, Iterable[str]] = None,
+ exclude: Union[None, Iterable[str]] = None,
+ ) -> Dict[str, Any]:
+ """
+ Integration into Jupyter Notebooks.
+
+ This method returns a dictionary that maps a mime-type to its
+ representation.
+
+ See https://ipython.readthedocs.io/en/stable/config/integrating.html
+ """
+ self.stream.seek(0)
+ pdf_data = self.stream.read()
+ data = {
+ "application/pdf": pdf_data,
+ }
+
+ if include is not None:
+ # Filter representations based on include list
+ data = {k: v for k, v in data.items() if k in include}
+
+ if exclude is not None:
+ # Remove representations based on exclude list
+ data = {k: v for k, v in data.items() if k not in exclude}
+
+ return data
+
+ @property
+ def pdf_header(self) -> str:
+ """
+ The first 8 bytes of the file.
+
+ This is typically something like ``'%PDF-1.6'`` and can be used to
+ detect if the file is actually a PDF file and which version it is.
+ """
+ # TODO: Make this return a bytes object for consistency
+ # but that needs a deprecation
+ loc = self.stream.tell()
+ self.stream.seek(0, 0)
+ pdf_file_version = self.stream.read(8).decode("utf-8", "backslashreplace")
+ self.stream.seek(loc, 0) # return to where it was
+ return pdf_file_version
+
+ @property
+ def xmp_metadata(self) -> Optional[XmpInformation]:
+ """XMP (Extensible Metadata Platform) data."""
+ try:
+ self._override_encryption = True
+ return cast(XmpInformation, self.root_object.xmp_metadata)
+ finally:
+ self._override_encryption = False
+
+ def _get_page(self, page_number: int) -> PageObject:
+ """
+ Retrieve a page by number from this PDF file.
+
+ Args:
+ page_number: The page number to retrieve
+ (pages begin at zero)
+
+ Returns:
+ A :class:`PageObject<pypdf._page.PageObject>` instance.
+ """
+ if self.flattened_pages is None:
+ self._flatten()
+ assert self.flattened_pages is not None, "hint for mypy"
+ return self.flattened_pages[page_number]
+
+ def _get_page_number_by_indirect(
+ self, indirect_reference: Union[None, int, NullObject, IndirectObject]
+ ) -> Optional[int]:
+ """
+ Generate _page_id2num.
+
+ Args:
+ indirect_reference:
+
+ Returns:
+ The page number or None
+ """
+ if self._page_id2num is None:
+ self._page_id2num = {
+ x.indirect_reference.idnum: i for i, x in enumerate(self.pages) # type: ignore
+ }
+
+ if indirect_reference is None or isinstance(indirect_reference, NullObject):
+ return None
+ if isinstance(indirect_reference, int):
+ idnum = indirect_reference
+ else:
+ idnum = indirect_reference.idnum
+ assert self._page_id2num is not None, "hint for mypy"
+ ret = self._page_id2num.get(idnum, None)
+ return ret
+
+ def _get_object_from_stream(
+ self, indirect_reference: IndirectObject
+ ) -> Union[int, PdfObject, str]:
+ # indirect reference to object in object stream
+ # read the entire object stream into memory
+ stmnum, idx = self.xref_objStm[indirect_reference.idnum]
+ obj_stm: EncodedStreamObject = IndirectObject(stmnum, 0, self).get_object() # type: ignore
+ # This is an xref to a stream, so its type better be a stream
+ assert cast(str, obj_stm["/Type"]) == "/ObjStm"
+ # /N is the number of indirect objects in the stream
+ assert idx < obj_stm["/N"]
+ stream_data = BytesIO(b_(obj_stm.get_data()))
+ for i in range(obj_stm["/N"]): # type: ignore
+ read_non_whitespace(stream_data)
+ stream_data.seek(-1, 1)
+ objnum = NumberObject.read_from_stream(stream_data)
+ read_non_whitespace(stream_data)
+ stream_data.seek(-1, 1)
+ offset = NumberObject.read_from_stream(stream_data)
+ read_non_whitespace(stream_data)
+ stream_data.seek(-1, 1)
+ if objnum != indirect_reference.idnum:
+ # We're only interested in one object
+ continue
+ if self.strict and idx != i:
+ raise PdfReadError("Object is in wrong index.")
+ stream_data.seek(int(obj_stm["/First"] + offset), 0) # type: ignore
+
+ # to cope with some case where the 'pointer' is on a white space
+ read_non_whitespace(stream_data)
+ stream_data.seek(-1, 1)
+
+ try:
+ obj = read_object(stream_data, self)
+ except PdfStreamError as exc:
+ # Stream object cannot be read. Normally, a critical error, but
+ # Adobe Reader doesn't complain, so continue (in strict mode?)
+ logger_warning(
+ f"Invalid stream (index {i}) within object "
+ f"{indirect_reference.idnum} {indirect_reference.generation}: "
+ f"{exc}",
+ __name__,
+ )
+
+ if self.strict: # pragma: no cover
+ raise PdfReadError(
+ f"Cannot read object stream: {exc}"
+ ) # pragma: no cover
+ # Replace with null. Hopefully it's nothing important.
+ obj = NullObject() # pragma: no cover
+ return obj
+
+ if self.strict: # pragma: no cover
+ raise PdfReadError(
+ "This is a fatal error in strict mode."
+ ) # pragma: no cover
+ return NullObject() # pragma: no cover
+
+ def get_object(
+ self, indirect_reference: Union[int, IndirectObject]
+ ) -> Optional[PdfObject]:
+ if isinstance(indirect_reference, int):
+ indirect_reference = IndirectObject(indirect_reference, 0, self)
+ retval = self.cache_get_indirect_object(
+ indirect_reference.generation, indirect_reference.idnum
+ )
+ if retval is not None:
+ return retval
+ if (
+ indirect_reference.generation == 0
+ and indirect_reference.idnum in self.xref_objStm
+ ):
+ retval = self._get_object_from_stream(indirect_reference) # type: ignore
+ elif (
+ indirect_reference.generation in self.xref
+ and indirect_reference.idnum in self.xref[indirect_reference.generation]
+ ):
+ if self.xref_free_entry.get(indirect_reference.generation, {}).get(
+ indirect_reference.idnum, False
+ ):
+ return NullObject()
+ start = self.xref[indirect_reference.generation][indirect_reference.idnum]
+ self.stream.seek(start, 0)
+ try:
+ idnum, generation = self.read_object_header(self.stream)
+ if (
+ idnum != indirect_reference.idnum
+ or generation != indirect_reference.generation
+ ):
+ raise PdfReadError("not matching, we parse the file for it")
+ except Exception:
+ if hasattr(self.stream, "getbuffer"):
+ buf = bytes(self.stream.getbuffer())
+ else:
+ p = self.stream.tell()
+ self.stream.seek(0, 0)
+ buf = self.stream.read(-1)
+ self.stream.seek(p, 0)
+ m = re.search(
+ rf"\s{indirect_reference.idnum}\s+{indirect_reference.generation}\s+obj".encode(),
+ buf,
+ )
+ if m is not None:
+ logger_warning(
+ f"Object ID {indirect_reference.idnum},{indirect_reference.generation} ref repaired",
+ __name__,
+ )
+ self.xref[indirect_reference.generation][
+ indirect_reference.idnum
+ ] = (m.start(0) + 1)
+ self.stream.seek(m.start(0) + 1)
+ idnum, generation = self.read_object_header(self.stream)
+ else:
+ idnum = -1
+ generation = -1 # exception will be raised below
+ if idnum != indirect_reference.idnum and self.xref_index:
+ # Xref table probably had bad indexes due to not being zero-indexed
+ if self.strict:
+ raise PdfReadError(
+ f"Expected object ID ({indirect_reference.idnum} {indirect_reference.generation}) "
+ f"does not match actual ({idnum} {generation}); "
+ "xref table not zero-indexed."
+ )
+ # xref table is corrected in non-strict mode
+ elif idnum != indirect_reference.idnum and self.strict:
+ # some other problem
+ raise PdfReadError(
+ f"Expected object ID ({indirect_reference.idnum} "
+ f"{indirect_reference.generation}) does not match actual "
+ f"({idnum} {generation})."
+ )
+ if self.strict:
+ assert generation == indirect_reference.generation
+ retval = read_object(self.stream, self) # type: ignore
+
+ # override encryption is used for the /Encrypt dictionary
+ if not self._override_encryption and self._encryption is not None:
+ # if we don't have the encryption key:
+ if not self._encryption.is_decrypted():
+ raise FileNotDecryptedError("File has not been decrypted")
+ # otherwise, decrypt here...
+ retval = cast(PdfObject, retval)
+ retval = self._encryption.decrypt_object(
+ retval, indirect_reference.idnum, indirect_reference.generation
+ )
+ else:
+ if hasattr(self.stream, "getbuffer"):
+ buf = bytes(self.stream.getbuffer())
+ else:
+ p = self.stream.tell()
+ self.stream.seek(0, 0)
+ buf = self.stream.read(-1)
+ self.stream.seek(p, 0)
+ m = re.search(
+ rf"\s{indirect_reference.idnum}\s+{indirect_reference.generation}\s+obj".encode(),
+ buf,
+ )
+ if m is not None:
+ logger_warning(
+ f"Object {indirect_reference.idnum} {indirect_reference.generation} found",
+ __name__,
+ )
+ if indirect_reference.generation not in self.xref:
+ self.xref[indirect_reference.generation] = {}
+ self.xref[indirect_reference.generation][indirect_reference.idnum] = (
+ m.start(0) + 1
+ )
+ self.stream.seek(m.end(0) + 1)
+ skip_over_whitespace(self.stream)
+ self.stream.seek(-1, 1)
+ retval = read_object(self.stream, self) # type: ignore
+
+ # override encryption is used for the /Encrypt dictionary
+ if not self._override_encryption and self._encryption is not None:
+ # if we don't have the encryption key:
+ if not self._encryption.is_decrypted():
+ raise FileNotDecryptedError("File has not been decrypted")
+ # otherwise, decrypt here...
+ retval = cast(PdfObject, retval)
+ retval = self._encryption.decrypt_object(
+ retval, indirect_reference.idnum, indirect_reference.generation
+ )
+ else:
+ logger_warning(
+ f"Object {indirect_reference.idnum} {indirect_reference.generation} not defined.",
+ __name__,
+ )
+ if self.strict:
+ raise PdfReadError("Could not find object.")
+ self.cache_indirect_object(
+ indirect_reference.generation, indirect_reference.idnum, retval
+ )
+ return retval
+
+ def read_object_header(self, stream: StreamType) -> Tuple[int, int]:
+ # Should never be necessary to read out whitespace, since the
+ # cross-reference table should put us in the right spot to read the
+ # object header. In reality some files have stupid cross reference
+ # tables that are off by whitespace bytes.
+ extra = False
+ skip_over_comment(stream)
+ extra |= skip_over_whitespace(stream)
+ stream.seek(-1, 1)
+ idnum = read_until_whitespace(stream)
+ extra |= skip_over_whitespace(stream)
+ stream.seek(-1, 1)
+ generation = read_until_whitespace(stream)
+ extra |= skip_over_whitespace(stream)
+ stream.seek(-1, 1)
+
+ # although it's not used, it might still be necessary to read
+ _obj = stream.read(3)
+
+ read_non_whitespace(stream)
+ stream.seek(-1, 1)
+ if extra and self.strict:
+ logger_warning(
+ f"Superfluous whitespace found in object header {idnum} {generation}", # type: ignore
+ __name__,
+ )
+ return int(idnum), int(generation)
+
+ def cache_get_indirect_object(
+ self, generation: int, idnum: int
+ ) -> Optional[PdfObject]:
+ return self.resolved_objects.get((generation, idnum))
+
+ def cache_indirect_object(
+ self, generation: int, idnum: int, obj: Optional[PdfObject]
+ ) -> Optional[PdfObject]:
+ if (generation, idnum) in self.resolved_objects:
+ msg = f"Overwriting cache for {generation} {idnum}"
+ if self.strict:
+ raise PdfReadError(msg)
+ logger_warning(msg, __name__)
+ self.resolved_objects[(generation, idnum)] = obj
+ if obj is not None:
+ obj.indirect_reference = IndirectObject(idnum, generation, self)
+ return obj
+
+ def _replace_object(self, indirect: IndirectObject, obj: PdfObject) -> PdfObject:
+ # function reserved for future dev
+ if indirect.pdf != self:
+ raise ValueError("Cannot update PdfReader with external object")
+ if (indirect.generation, indirect.idnum) not in self.resolved_objects:
+ raise ValueError("Cannot find referenced object")
+ self.resolved_objects[(indirect.generation, indirect.idnum)] = obj
+ obj.indirect_reference = indirect
+ return obj
+
+ def read(self, stream: StreamType) -> None:
+ self._basic_validation(stream)
+ self._find_eof_marker(stream)
+ startxref = self._find_startxref_pos(stream)
+
+ # check and eventually correct the startxref only in not strict
+ xref_issue_nr = self._get_xref_issues(stream, startxref)
+ if xref_issue_nr != 0:
+ if self.strict and xref_issue_nr:
+ raise PdfReadError("Broken xref table")
+ logger_warning(f"incorrect startxref pointer({xref_issue_nr})", __name__)
+
+ # read all cross reference tables and their trailers
+ self._read_xref_tables_and_trailers(stream, startxref, xref_issue_nr)
+
+ # if not zero-indexed, verify that the table is correct; change it if necessary
+ if self.xref_index and not self.strict:
+ loc = stream.tell()
+ for gen, xref_entry in self.xref.items():
+ if gen == 65535:
+ continue
+ xref_k = sorted(
+ xref_entry.keys()
+ ) # must ensure ascendant to prevent damage
+ for id in xref_k:
+ stream.seek(xref_entry[id], 0)
+ try:
+ pid, _pgen = self.read_object_header(stream)
+ except ValueError:
+ self._rebuild_xref_table(stream)
+ break
+ if pid == id - self.xref_index:
+ # fixing index item per item is required for revised PDF.
+ self.xref[gen][pid] = self.xref[gen][id]
+ del self.xref[gen][id]
+ # if not, then either it's just plain wrong, or the
+ # non-zero-index is actually correct
+ stream.seek(loc, 0) # return to where it was
+
+ # remove wrong objects (not pointing to correct structures) - cf #2326
+ if not self.strict:
+ loc = stream.tell()
+ for gen, xref_entry in self.xref.items():
+ if gen == 65535:
+ continue
+ ids = list(xref_entry.keys())
+ for id in ids:
+ stream.seek(xref_entry[id], 0)
+ try:
+ self.read_object_header(stream)
+ except ValueError:
+ logger_warning(
+ f"Ignoring wrong pointing object {id} {gen} (offset {xref_entry[id]})",
+ __name__,
+ )
+ del xref_entry[id] # we can delete the id, we are parsing ids
+ stream.seek(loc, 0) # return to where it was
+
+ def _basic_validation(self, stream: StreamType) -> None:
+ """Ensure file is not empty. Read at most 5 bytes."""
+ stream.seek(0, os.SEEK_SET)
+ try:
+ header_byte = stream.read(5)
+ except UnicodeDecodeError:
+ raise UnsupportedOperation("cannot read header")
+ if header_byte == b"":
+ raise EmptyFileError("Cannot read an empty file")
+ elif header_byte != b"%PDF-":
+ if self.strict:
+ raise PdfReadError(
+ f"PDF starts with '{header_byte.decode('utf8')}', "
+ "but '%PDF-' expected"
+ )
+ else:
+ logger_warning(f"invalid pdf header: {header_byte}", __name__)
+ stream.seek(0, os.SEEK_END)
+
+ def _find_eof_marker(self, stream: StreamType) -> None:
+ """
+ Jump to the %%EOF marker.
+
+ According to the specs, the %%EOF marker should be at the very end of
+ the file. Hence for standard-compliant PDF documents this function will
+ read only the last part (DEFAULT_BUFFER_SIZE).
+ """
+ HEADER_SIZE = 8 # to parse whole file, Header is e.g. '%PDF-1.6'
+ line = b""
+ while line[:5] != b"%%EOF":
+ if stream.tell() < HEADER_SIZE:
+ if self.strict:
+ raise PdfReadError("EOF marker not found")
+ else:
+ logger_warning("EOF marker not found", __name__)
+ line = read_previous_line(stream)
+
+ def _find_startxref_pos(self, stream: StreamType) -> int:
+ """
+ Find startxref entry - the location of the xref table.
+
+ Args:
+ stream:
+
+ Returns:
+ The bytes offset
+ """
+ line = read_previous_line(stream)
+ try:
+ startxref = int(line)
+ except ValueError:
+ # 'startxref' may be on the same line as the location
+ if not line.startswith(b"startxref"):
+ raise PdfReadError("startxref not found")
+ startxref = int(line[9:].strip())
+ logger_warning("startxref on same line as offset", __name__)
+ else:
+ line = read_previous_line(stream)
+ if line[:9] != b"startxref":
+ raise PdfReadError("startxref not found")
+ return startxref
+
+ def _read_standard_xref_table(self, stream: StreamType) -> None:
+ # standard cross-reference table
+ ref = stream.read(3)
+ if ref != b"ref":
+ raise PdfReadError("xref table read error")
+ read_non_whitespace(stream)
+ stream.seek(-1, 1)
+ first_time = True # check if the first time looking at the xref table
+ while True:
+ num = cast(int, read_object(stream, self))
+ if first_time and num != 0:
+ self.xref_index = num
+ if self.strict:
+ logger_warning(
+ "Xref table not zero-indexed. ID numbers for objects will be corrected.",
+ __name__,
+ )
+ # if table not zero indexed, could be due to error from when PDF was created
+ # which will lead to mismatched indices later on, only warned and corrected if self.strict==True
+ first_time = False
+ read_non_whitespace(stream)
+ stream.seek(-1, 1)
+ size = cast(int, read_object(stream, self))
+ if not isinstance(size, int):
+ logger_warning(
+ "Invalid/Truncated xref table. Rebuilding it.",
+ __name__,
+ )
+ self._rebuild_xref_table(stream)
+ stream.read()
+ return
+ read_non_whitespace(stream)
+ stream.seek(-1, 1)
+ cnt = 0
+ while cnt < size:
+ line = stream.read(20)
+
+ # It's very clear in section 3.4.3 of the PDF spec
+ # that all cross-reference table lines are a fixed
+ # 20 bytes (as of PDF 1.7). However, some files have
+ # 21-byte entries (or more) due to the use of \r\n
+ # (CRLF) EOL's. Detect that case, and adjust the line
+ # until it does not begin with a \r (CR) or \n (LF).
+ while line[0] in b"\x0D\x0A":
+ stream.seek(-20 + 1, 1)
+ line = stream.read(20)
+
+ # On the other hand, some malformed PDF files
+ # use a single character EOL without a preceding
+ # space. Detect that case, and seek the stream
+ # back one character (0-9 means we've bled into
+ # the next xref entry, t means we've bled into the
+ # text "trailer"):
+ if line[-1] in b"0123456789t":
+ stream.seek(-1, 1)
+
+ try:
+ offset_b, generation_b = line[:16].split(b" ")
+ entry_type_b = line[17:18]
+
+ offset, generation = int(offset_b), int(generation_b)
+ except Exception:
+ # if something wrong occurred
+ if hasattr(stream, "getbuffer"):
+ buf = bytes(stream.getbuffer())
+ else:
+ p = stream.tell()
+ stream.seek(0, 0)
+ buf = stream.read(-1)
+ stream.seek(p)
+
+ f = re.search(f"{num}\\s+(\\d+)\\s+obj".encode(), buf)
+ if f is None:
+ logger_warning(
+ f"entry {num} in Xref table invalid; object not found",
+ __name__,
+ )
+ generation = 65535
+ offset = -1
+ else:
+ logger_warning(
+ f"entry {num} in Xref table invalid but object found",
+ __name__,
+ )
+ generation = int(f.group(1))
+ offset = f.start()
+
+ if generation not in self.xref:
+ self.xref[generation] = {}
+ self.xref_free_entry[generation] = {}
+ if num in self.xref[generation]:
+ # It really seems like we should allow the last
+ # xref table in the file to override previous
+ # ones. Since we read the file backwards, assume
+ # any existing key is already set correctly.
+ pass
+ else:
+ if entry_type_b == b"n":
+ self.xref[generation][num] = offset
+ try:
+ self.xref_free_entry[generation][num] = entry_type_b == b"f"
+ except Exception:
+ pass
+ try:
+ self.xref_free_entry[65535][num] = entry_type_b == b"f"
+ except Exception:
+ pass
+ cnt += 1
+ num += 1
+ read_non_whitespace(stream)
+ stream.seek(-1, 1)
+ trailer_tag = stream.read(7)
+ if trailer_tag != b"trailer":
+ # more xrefs!
+ stream.seek(-7, 1)
+ else:
+ break
+
+ def _read_xref_tables_and_trailers(
+ self, stream: StreamType, startxref: Optional[int], xref_issue_nr: int
+ ) -> None:
+ self.xref = {}
+ self.xref_free_entry = {}
+ self.xref_objStm = {}
+ self.trailer = DictionaryObject()
+ while startxref is not None:
+ # load the xref table
+ stream.seek(startxref, 0)
+ x = stream.read(1)
+ if x in b"\r\n":
+ x = stream.read(1)
+ if x == b"x":
+ startxref = self._read_xref(stream)
+ elif xref_issue_nr:
+ try:
+ self._rebuild_xref_table(stream)
+ break
+ except Exception:
+ xref_issue_nr = 0
+ elif x.isdigit():
+ try:
+ xrefstream = self._read_pdf15_xref_stream(stream)
+ except Exception as e:
+ if TK.ROOT in self.trailer:
+ logger_warning(
+ f"Previous trailer can not be read {e.args}",
+ __name__,
+ )
+ break
+ else:
+ raise PdfReadError(f"trailer can not be read {e.args}")
+ trailer_keys = TK.ROOT, TK.ENCRYPT, TK.INFO, TK.ID, TK.SIZE
+ for key in trailer_keys:
+ if key in xrefstream and key not in self.trailer:
+ self.trailer[NameObject(key)] = xrefstream.raw_get(key)
+ if "/XRefStm" in xrefstream:
+ p = stream.tell()
+ stream.seek(cast(int, xrefstream["/XRefStm"]) + 1, 0)
+ self._read_pdf15_xref_stream(stream)
+ stream.seek(p, 0)
+ if "/Prev" in xrefstream:
+ startxref = cast(int, xrefstream["/Prev"])
+ else:
+ break
+ else:
+ startxref = self._read_xref_other_error(stream, startxref)
+
+ def _read_xref(self, stream: StreamType) -> Optional[int]:
+ self._read_standard_xref_table(stream)
+ if stream.read(1) == b"":
+ return None
+ stream.seek(-1, 1)
+ read_non_whitespace(stream)
+ stream.seek(-1, 1)
+ new_trailer = cast(Dict[str, Any], read_object(stream, self))
+ for key, value in new_trailer.items():
+ if key not in self.trailer:
+ self.trailer[key] = value
+ if "/XRefStm" in new_trailer:
+ p = stream.tell()
+ stream.seek(cast(int, new_trailer["/XRefStm"]) + 1, 0)
+ try:
+ self._read_pdf15_xref_stream(stream)
+ except Exception:
+ logger_warning(
+ f"XRef object at {new_trailer['/XRefStm']} can not be read, some object may be missing",
+ __name__,
+ )
+ stream.seek(p, 0)
+ if "/Prev" in new_trailer:
+ startxref = new_trailer["/Prev"]
+ return startxref
+ else:
+ return None
+
+ def _read_xref_other_error(
+ self, stream: StreamType, startxref: int
+ ) -> Optional[int]:
+ # some PDFs have /Prev=0 in the trailer, instead of no /Prev
+ if startxref == 0:
+ if self.strict:
+ raise PdfReadError(
+ "/Prev=0 in the trailer (try opening with strict=False)"
+ )
+ logger_warning(
+ "/Prev=0 in the trailer - assuming there is no previous xref table",
+ __name__,
+ )
+ return None
+ # bad xref character at startxref. Let's see if we can find
+ # the xref table nearby, as we've observed this error with an
+ # off-by-one before.
+ stream.seek(-11, 1)
+ tmp = stream.read(20)
+ xref_loc = tmp.find(b"xref")
+ if xref_loc != -1:
+ startxref -= 10 - xref_loc
+ return startxref
+ # No explicit xref table, try finding a cross-reference stream.
+ stream.seek(startxref, 0)
+ for look in range(25): # value extended to cope with more linearized files
+ if stream.read(1).isdigit():
+ # This is not a standard PDF, consider adding a warning
+ startxref += look
+ return startxref
+ # no xref table found at specified location
+ if "/Root" in self.trailer and not self.strict:
+ # if Root has been already found, just raise warning
+ logger_warning("Invalid parent xref., rebuild xref", __name__)
+ try:
+ self._rebuild_xref_table(stream)
+ return None
+ except Exception:
+ raise PdfReadError("can not rebuild xref")
+ raise PdfReadError("Could not find xref table at specified location")
+
+ def _read_pdf15_xref_stream(
+ self, stream: StreamType
+ ) -> Union[ContentStream, EncodedStreamObject, DecodedStreamObject]:
+ # PDF 1.5+ Cross-Reference Stream
+ stream.seek(-1, 1)
+ idnum, generation = self.read_object_header(stream)
+ xrefstream = cast(ContentStream, read_object(stream, self))
+ assert cast(str, xrefstream["/Type"]) == "/XRef"
+ self.cache_indirect_object(generation, idnum, xrefstream)
+ stream_data = BytesIO(b_(xrefstream.get_data()))
+ # Index pairs specify the subsections in the dictionary. If
+ # none create one subsection that spans everything.
+ idx_pairs = xrefstream.get("/Index", [0, xrefstream.get("/Size")])
+ entry_sizes = cast(Dict[Any, Any], xrefstream.get("/W"))
+ assert len(entry_sizes) >= 3
+ if self.strict and len(entry_sizes) > 3:
+ raise PdfReadError(f"Too many entry sizes: {entry_sizes}")
+
+ def get_entry(i: int) -> Union[int, Tuple[int, ...]]:
+ # Reads the correct number of bytes for each entry. See the
+ # discussion of the W parameter in PDF spec table 17.
+ if entry_sizes[i] > 0:
+ d = stream_data.read(entry_sizes[i])
+ return convert_to_int(d, entry_sizes[i])
+
+ # PDF Spec Table 17: A value of zero for an element in the
+ # W array indicates...the default value shall be used
+ if i == 0:
+ return 1 # First value defaults to 1
+ else:
+ return 0
+
+ def used_before(num: int, generation: Union[int, Tuple[int, ...]]) -> bool:
+ # We move backwards through the xrefs, don't replace any.
+ return num in self.xref.get(generation, []) or num in self.xref_objStm # type: ignore
+
+ # Iterate through each subsection
+ self._read_xref_subsections(idx_pairs, get_entry, used_before)
+ return xrefstream
+
+ @staticmethod
+ def _get_xref_issues(stream: StreamType, startxref: int) -> int:
+ """
+ Return an int which indicates an issue. 0 means there is no issue.
+
+ Args:
+ stream:
+ startxref:
+
+ Returns:
+ 0 means no issue, other values represent specific issues.
+ """
+ stream.seek(startxref - 1, 0) # -1 to check character before
+ line = stream.read(1)
+ if line == b"j":
+ line = stream.read(1)
+ if line not in b"\r\n \t":
+ return 1
+ line = stream.read(4)
+ if line != b"xref":
+ # not an xref so check if it is an XREF object
+ line = b""
+ while line in b"0123456789 \t":
+ line = stream.read(1)
+ if line == b"":
+ return 2
+ line += stream.read(2) # 1 char already read, +2 to check "obj"
+ if line.lower() != b"obj":
+ return 3
+ return 0
+
+ def _rebuild_xref_table(self, stream: StreamType) -> None:
+ self.xref = {}
+ stream.seek(0, 0)
+ f_ = stream.read(-1)
+
+ for m in re.finditer(rb"[\r\n \t][ \t]*(\d+)[ \t]+(\d+)[ \t]+obj", f_):
+ idnum = int(m.group(1))
+ generation = int(m.group(2))
+ if generation not in self.xref:
+ self.xref[generation] = {}
+ self.xref[generation][idnum] = m.start(1)
+ stream.seek(0, 0)
+ for m in re.finditer(rb"[\r\n \t][ \t]*trailer[\r\n \t]*(<<)", f_):
+ stream.seek(m.start(1), 0)
+ new_trailer = cast(Dict[Any, Any], read_object(stream, self))
+ # Here, we are parsing the file from start to end, the new data have to erase the existing.
+ for key, value in list(new_trailer.items()):
+ self.trailer[key] = value
+
+ def _read_xref_subsections(
+ self,
+ idx_pairs: List[int],
+ get_entry: Callable[[int], Union[int, Tuple[int, ...]]],
+ used_before: Callable[[int, Union[int, Tuple[int, ...]]], bool],
+ ) -> None:
+ for start, size in self._pairs(idx_pairs):
+ # The subsections must increase
+ for num in range(start, start + size):
+ # The first entry is the type
+ xref_type = get_entry(0)
+ # The rest of the elements depend on the xref_type
+ if xref_type == 0:
+ # linked list of free objects
+ next_free_object = get_entry(1) # noqa: F841
+ next_generation = get_entry(2) # noqa: F841
+ elif xref_type == 1:
+ # objects that are in use but are not compressed
+ byte_offset = get_entry(1)
+ generation = get_entry(2)
+ if generation not in self.xref:
+ self.xref[generation] = {} # type: ignore
+ if not used_before(num, generation):
+ self.xref[generation][num] = byte_offset # type: ignore
+ elif xref_type == 2:
+ # compressed objects
+ objstr_num = get_entry(1)
+ obstr_idx = get_entry(2)
+ generation = 0 # PDF spec table 18, generation is 0
+ if not used_before(num, generation):
+ self.xref_objStm[num] = (objstr_num, obstr_idx)
+ elif self.strict:
+ raise PdfReadError(f"Unknown xref type: {xref_type}")
+
+ def _pairs(self, array: List[int]) -> Iterable[Tuple[int, int]]:
+ i = 0
+ while True:
+ yield array[i], array[i + 1]
+ i += 2
+ if (i + 1) >= len(array):
+ break
+
+ def decrypt(self, password: Union[str, bytes]) -> PasswordType:
+ """
+ When using an encrypted / secured PDF file with the PDF Standard
+ encryption handler, this function will allow the file to be decrypted.
+ It checks the given password against the document's user password and
+ owner password, and then stores the resulting decryption key if either
+ password is correct.
+
+ It does not matter which password was matched. Both passwords provide
+ the correct decryption key that will allow the document to be used with
+ this library.
+
+ Args:
+ password: The password to match.
+
+ Returns:
+ An indicator if the document was decrypted and whether it was the
+ owner password or the user password.
+ """
+ if not self._encryption:
+ raise PdfReadError("Not encrypted file")
+ # TODO: raise Exception for wrong password
+ return self._encryption.verify(password)
+
+ @property
+ def is_encrypted(self) -> bool:
+ """
+ Read-only boolean property showing whether this PDF file is encrypted.
+
+ Note that this property, if true, will remain true even after the
+ :meth:`decrypt()<pypdf.PdfReader.decrypt>` method is called.
+ """
+ return TK.ENCRYPT in self.trailer
+
+ def add_form_topname(self, name: str) -> Optional[DictionaryObject]:
+ """
+ Add a top level form that groups all form fields below it.
+
+ Args:
+ name: text string of the "/T" Attribute of the created object
+
+ Returns:
+ The created object. ``None`` means no object was created.
+ """
+ catalog = self.root_object
+
+ if "/AcroForm" not in catalog or not isinstance(
+ catalog["/AcroForm"], DictionaryObject
+ ):
+ return None
+ acroform = cast(DictionaryObject, catalog[NameObject("/AcroForm")])
+ if "/Fields" not in acroform:
+ # TODO: :No error returns but may be extended for XFA Forms
+ return None
+
+ interim = DictionaryObject()
+ interim[NameObject("/T")] = TextStringObject(name)
+ interim[NameObject("/Kids")] = acroform[NameObject("/Fields")]
+ self.cache_indirect_object(
+ 0,
+ max([i for (g, i) in self.resolved_objects if g == 0]) + 1,
+ interim,
+ )
+ arr = ArrayObject()
+ arr.append(interim.indirect_reference)
+ acroform[NameObject("/Fields")] = arr
+ for o in cast(ArrayObject, interim["/Kids"]):
+ obj = o.get_object()
+ if "/Parent" in obj:
+ logger_warning(
+ f"Top Level Form Field {obj.indirect_reference} have a non-expected parent",
+ __name__,
+ )
+ obj[NameObject("/Parent")] = interim.indirect_reference
+ return interim
+
+ def rename_form_topname(self, name: str) -> Optional[DictionaryObject]:
+ """
+ Rename top level form field that all form fields below it.
+
+ Args:
+ name: text string of the "/T" field of the created object
+
+ Returns:
+ The modified object. ``None`` means no object was modified.
+ """
+ catalog = self.root_object
+
+ if "/AcroForm" not in catalog or not isinstance(
+ catalog["/AcroForm"], DictionaryObject
+ ):
+ return None
+ acroform = cast(DictionaryObject, catalog[NameObject("/AcroForm")])
+ if "/Fields" not in acroform:
+ return None
+
+ interim = cast(
+ DictionaryObject,
+ cast(ArrayObject, acroform[NameObject("/Fields")])[0].get_object(),
+ )
+ interim[NameObject("/T")] = TextStringObject(name)
+ return interim
diff --git a/.venv/lib/python3.12/site-packages/pypdf/_text_extraction/__init__.py b/.venv/lib/python3.12/site-packages/pypdf/_text_extraction/__init__.py
new file mode 100644
index 00000000..3b1d687e
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/pypdf/_text_extraction/__init__.py
@@ -0,0 +1,285 @@
+"""
+Code related to text extraction.
+
+Some parts are still in _page.py. In doubt, they will stay there.
+"""
+
+import math
+from typing import Any, Callable, Dict, List, Optional, Tuple, Union
+
+from ..generic import DictionaryObject, TextStringObject, encode_pdfdocencoding
+
+CUSTOM_RTL_MIN: int = -1
+CUSTOM_RTL_MAX: int = -1
+CUSTOM_RTL_SPECIAL_CHARS: List[int] = []
+LAYOUT_NEW_BT_GROUP_SPACE_WIDTHS: int = 5
+
+
+class OrientationNotFoundError(Exception):
+ pass
+
+
+def set_custom_rtl(
+ _min: Union[str, int, None] = None,
+ _max: Union[str, int, None] = None,
+ specials: Union[str, List[int], None] = None,
+) -> Tuple[int, int, List[int]]:
+ """
+ Change the Right-To-Left and special characters custom parameters.
+
+ Args:
+ _min: The new minimum value for the range of custom characters that
+ will be written right to left.
+ If set to ``None``, the value will not be changed.
+ If set to an integer or string, it will be converted to its ASCII code.
+ The default value is -1, which sets no additional range to be converted.
+ _max: The new maximum value for the range of custom characters that will
+ be written right to left.
+ If set to ``None``, the value will not be changed.
+ If set to an integer or string, it will be converted to its ASCII code.
+ The default value is -1, which sets no additional range to be converted.
+ specials: The new list of special characters to be inserted in the
+ current insertion order.
+ If set to ``None``, the current value will not be changed.
+ If set to a string, it will be converted to a list of ASCII codes.
+ The default value is an empty list.
+
+ Returns:
+ A tuple containing the new values for ``CUSTOM_RTL_MIN``,
+ ``CUSTOM_RTL_MAX``, and ``CUSTOM_RTL_SPECIAL_CHARS``.
+ """
+ global CUSTOM_RTL_MIN, CUSTOM_RTL_MAX, CUSTOM_RTL_SPECIAL_CHARS
+ if isinstance(_min, int):
+ CUSTOM_RTL_MIN = _min
+ elif isinstance(_min, str):
+ CUSTOM_RTL_MIN = ord(_min)
+ if isinstance(_max, int):
+ CUSTOM_RTL_MAX = _max
+ elif isinstance(_max, str):
+ CUSTOM_RTL_MAX = ord(_max)
+ if isinstance(specials, str):
+ CUSTOM_RTL_SPECIAL_CHARS = [ord(x) for x in specials]
+ elif isinstance(specials, list):
+ CUSTOM_RTL_SPECIAL_CHARS = specials
+ return CUSTOM_RTL_MIN, CUSTOM_RTL_MAX, CUSTOM_RTL_SPECIAL_CHARS
+
+
+def mult(m: List[float], n: List[float]) -> List[float]:
+ return [
+ m[0] * n[0] + m[1] * n[2],
+ m[0] * n[1] + m[1] * n[3],
+ m[2] * n[0] + m[3] * n[2],
+ m[2] * n[1] + m[3] * n[3],
+ m[4] * n[0] + m[5] * n[2] + n[4],
+ m[4] * n[1] + m[5] * n[3] + n[5],
+ ]
+
+
+def orient(m: List[float]) -> int:
+ if m[3] > 1e-6:
+ return 0
+ elif m[3] < -1e-6:
+ return 180
+ elif m[1] > 0:
+ return 90
+ else:
+ return 270
+
+
+def crlf_space_check(
+ text: str,
+ cmtm_prev: Tuple[List[float], List[float]],
+ cmtm_matrix: Tuple[List[float], List[float]],
+ memo_cmtm: Tuple[List[float], List[float]],
+ cmap: Tuple[
+ Union[str, Dict[int, str]], Dict[str, str], str, Optional[DictionaryObject]
+ ],
+ orientations: Tuple[int, ...],
+ output: str,
+ font_size: float,
+ visitor_text: Optional[Callable[[Any, Any, Any, Any, Any], None]],
+ spacewidth: float,
+) -> Tuple[str, str, List[float], List[float]]:
+ cm_prev = cmtm_prev[0]
+ tm_prev = cmtm_prev[1]
+ cm_matrix = cmtm_matrix[0]
+ tm_matrix = cmtm_matrix[1]
+ memo_cm = memo_cmtm[0]
+ memo_tm = memo_cmtm[1]
+
+ m_prev = mult(tm_prev, cm_prev)
+ m = mult(tm_matrix, cm_matrix)
+ orientation = orient(m)
+ delta_x = m[4] - m_prev[4]
+ delta_y = m[5] - m_prev[5]
+ k = math.sqrt(abs(m[0] * m[3]) + abs(m[1] * m[2]))
+ f = font_size * k
+ cm_prev = m
+ if orientation not in orientations:
+ raise OrientationNotFoundError
+ try:
+ if orientation == 0:
+ if delta_y < -0.8 * f:
+ if (output + text)[-1] != "\n":
+ output += text + "\n"
+ if visitor_text is not None:
+ visitor_text(
+ text + "\n",
+ memo_cm,
+ memo_tm,
+ cmap[3],
+ font_size,
+ )
+ text = ""
+ elif (
+ abs(delta_y) < f * 0.3
+ and abs(delta_x) > spacewidth * f * 15
+ and (output + text)[-1] != " "
+ ):
+ text += " "
+ elif orientation == 180:
+ if delta_y > 0.8 * f:
+ if (output + text)[-1] != "\n":
+ output += text + "\n"
+ if visitor_text is not None:
+ visitor_text(
+ text + "\n",
+ memo_cm,
+ memo_tm,
+ cmap[3],
+ font_size,
+ )
+ text = ""
+ elif (
+ abs(delta_y) < f * 0.3
+ and abs(delta_x) > spacewidth * f * 15
+ and (output + text)[-1] != " "
+ ):
+ text += " "
+ elif orientation == 90:
+ if delta_x > 0.8 * f:
+ if (output + text)[-1] != "\n":
+ output += text + "\n"
+ if visitor_text is not None:
+ visitor_text(
+ text + "\n",
+ memo_cm,
+ memo_tm,
+ cmap[3],
+ font_size,
+ )
+ text = ""
+ elif (
+ abs(delta_x) < f * 0.3
+ and abs(delta_y) > spacewidth * f * 15
+ and (output + text)[-1] != " "
+ ):
+ text += " "
+ elif orientation == 270:
+ if delta_x < -0.8 * f:
+ if (output + text)[-1] != "\n":
+ output += text + "\n"
+ if visitor_text is not None:
+ visitor_text(
+ text + "\n",
+ memo_cm,
+ memo_tm,
+ cmap[3],
+ font_size,
+ )
+ text = ""
+ elif (
+ abs(delta_x) < f * 0.3
+ and abs(delta_y) > spacewidth * f * 15
+ and (output + text)[-1] != " "
+ ):
+ text += " "
+ except Exception:
+ pass
+ tm_prev = tm_matrix.copy()
+ cm_prev = cm_matrix.copy()
+ return text, output, cm_prev, tm_prev
+
+
+def handle_tj(
+ text: str,
+ operands: List[Union[str, TextStringObject]],
+ cm_matrix: List[float],
+ tm_matrix: List[float],
+ cmap: Tuple[
+ Union[str, Dict[int, str]], Dict[str, str], str, Optional[DictionaryObject]
+ ],
+ orientations: Tuple[int, ...],
+ output: str,
+ font_size: float,
+ rtl_dir: bool,
+ visitor_text: Optional[Callable[[Any, Any, Any, Any, Any], None]],
+) -> Tuple[str, bool]:
+ m = mult(tm_matrix, cm_matrix)
+ orientation = orient(m)
+ if orientation in orientations and len(operands) > 0:
+ if isinstance(operands[0], str):
+ text += operands[0]
+ else:
+ t: str = ""
+ tt: bytes = (
+ encode_pdfdocencoding(operands[0])
+ if isinstance(operands[0], str)
+ else operands[0]
+ )
+ if isinstance(cmap[0], str):
+ try:
+ t = tt.decode(cmap[0], "surrogatepass") # apply str encoding
+ except Exception:
+ # the data does not match the expectation,
+ # we use the alternative ;
+ # text extraction may not be good
+ t = tt.decode(
+ "utf-16-be" if cmap[0] == "charmap" else "charmap",
+ "surrogatepass",
+ ) # apply str encoding
+ else: # apply dict encoding
+ t = "".join(
+ [cmap[0][x] if x in cmap[0] else bytes((x,)).decode() for x in tt]
+ )
+ # "\u0590 - \u08FF \uFB50 - \uFDFF"
+ for x in [cmap[1][x] if x in cmap[1] else x for x in t]:
+ # x can be a sequence of bytes ; ex: habibi.pdf
+ if len(x) == 1:
+ xx = ord(x)
+ else:
+ xx = 1
+ # fmt: off
+ if (
+ # cases where the current inserting order is kept
+ (xx <= 0x2F) # punctuations but...
+ or 0x3A <= xx <= 0x40 # numbers (x30-39)
+ or 0x2000 <= xx <= 0x206F # upper punctuations..
+ or 0x20A0 <= xx <= 0x21FF # but (numbers) indices/exponents
+ or xx in CUSTOM_RTL_SPECIAL_CHARS # customized....
+ ):
+ text = x + text if rtl_dir else text + x
+ elif ( # right-to-left characters set
+ 0x0590 <= xx <= 0x08FF
+ or 0xFB1D <= xx <= 0xFDFF
+ or 0xFE70 <= xx <= 0xFEFF
+ or CUSTOM_RTL_MIN <= xx <= CUSTOM_RTL_MAX
+ ):
+ if not rtl_dir:
+ rtl_dir = True
+ output += text
+ if visitor_text is not None:
+ visitor_text(text, cm_matrix, tm_matrix, cmap[3], font_size)
+ text = ""
+ text = x + text
+ else: # left-to-right
+ # print(">",xx,x,end="")
+ if rtl_dir:
+ rtl_dir = False
+ output += text
+ if visitor_text is not None:
+ visitor_text(text, cm_matrix, tm_matrix, cmap[3], font_size)
+ text = ""
+ text = text + x
+ # fmt: on
+ return text, rtl_dir
diff --git a/.venv/lib/python3.12/site-packages/pypdf/_text_extraction/_layout_mode/__init__.py b/.venv/lib/python3.12/site-packages/pypdf/_text_extraction/_layout_mode/__init__.py
new file mode 100644
index 00000000..8f4d5929
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/pypdf/_text_extraction/_layout_mode/__init__.py
@@ -0,0 +1,16 @@
+"""Layout mode text extraction extension for pypdf"""
+from ._fixed_width_page import (
+ fixed_char_width,
+ fixed_width_page,
+ text_show_operations,
+ y_coordinate_groups,
+)
+from ._font import Font
+
+__all__ = [
+ "fixed_char_width",
+ "fixed_width_page",
+ "text_show_operations",
+ "y_coordinate_groups",
+ "Font",
+]
diff --git a/.venv/lib/python3.12/site-packages/pypdf/_text_extraction/_layout_mode/_fixed_width_page.py b/.venv/lib/python3.12/site-packages/pypdf/_text_extraction/_layout_mode/_fixed_width_page.py
new file mode 100644
index 00000000..1be50095
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/pypdf/_text_extraction/_layout_mode/_fixed_width_page.py
@@ -0,0 +1,381 @@
+"""Extract PDF text preserving the layout of the source PDF"""
+
+import sys
+from itertools import groupby
+from math import ceil
+from pathlib import Path
+from typing import Any, Dict, Iterator, List, Optional, Tuple
+
+from ..._utils import logger_warning
+from .. import LAYOUT_NEW_BT_GROUP_SPACE_WIDTHS
+from ._font import Font
+from ._text_state_manager import TextStateManager
+from ._text_state_params import TextStateParams
+
+if sys.version_info >= (3, 8):
+ from typing import Literal, TypedDict
+else:
+ from typing_extensions import Literal, TypedDict
+
+
+class BTGroup(TypedDict):
+ """
+ Dict describing a line of text rendered within a BT/ET operator pair.
+ If multiple text show operations render text on the same line, the text
+ will be combined into a single BTGroup dict.
+
+ Keys:
+ tx: x coordinate of first character in BTGroup
+ ty: y coordinate of first character in BTGroup
+ font_size: nominal font size
+ font_height: effective font height
+ text: rendered text
+ displaced_tx: x coordinate of last character in BTGroup
+ flip_sort: -1 if page is upside down, else 1
+ """
+
+ tx: float
+ ty: float
+ font_size: float
+ font_height: float
+ text: str
+ displaced_tx: float
+ flip_sort: Literal[-1, 1]
+
+
+def bt_group(tj_op: TextStateParams, rendered_text: str, dispaced_tx: float) -> BTGroup:
+ """
+ BTGroup constructed from a TextStateParams instance, rendered text, and
+ displaced tx value.
+
+ Args:
+ tj_op (TextStateParams): TextStateParams instance
+ rendered_text (str): rendered text
+ dispaced_tx (float): x coordinate of last character in BTGroup
+ """
+ return BTGroup(
+ tx=tj_op.tx,
+ ty=tj_op.ty,
+ font_size=tj_op.font_size,
+ font_height=tj_op.font_height,
+ text=rendered_text,
+ displaced_tx=dispaced_tx,
+ flip_sort=-1 if tj_op.flip_vertical else 1,
+ )
+
+
+def recurs_to_target_op(
+ ops: Iterator[Tuple[List[Any], bytes]],
+ text_state_mgr: TextStateManager,
+ end_target: Literal[b"Q", b"ET"],
+ fonts: Dict[str, Font],
+ strip_rotated: bool = True,
+) -> Tuple[List[BTGroup], List[TextStateParams]]:
+ """
+ Recurse operators between BT/ET and/or q/Q operators managing the transform
+ stack and capturing text positioning and rendering data.
+
+ Args:
+ ops: iterator of operators in content stream
+ text_state_mgr: a TextStateManager instance
+ end_target: Either b"Q" (ends b"q" op) or b"ET" (ends b"BT" op)
+ fonts: font dictionary as returned by PageObject._layout_mode_fonts()
+
+ Returns:
+ tuple: list of BTGroup dicts + list of TextStateParams dataclass instances.
+ """
+ # 1 entry per line of text rendered within each BT/ET operation.
+ bt_groups: List[BTGroup] = []
+
+ # 1 entry per text show operator (Tj/TJ/'/")
+ tj_ops: List[TextStateParams] = []
+
+ if end_target == b"Q":
+ # add new q level. cm's added at this level will be popped at next b'Q'
+ text_state_mgr.add_q()
+
+ while True:
+ try:
+ operands, op = next(ops)
+ except StopIteration:
+ return bt_groups, tj_ops
+ if op == end_target:
+ if op == b"Q":
+ text_state_mgr.remove_q()
+ if op == b"ET":
+ if not tj_ops:
+ return bt_groups, tj_ops
+ _text = ""
+ bt_idx = 0 # idx of first tj in this bt group
+ last_displaced_tx = tj_ops[bt_idx].displaced_tx
+ last_ty = tj_ops[bt_idx].ty
+ for _idx, _tj in enumerate(
+ tj_ops
+ ): # ... build text from new Tj operators
+ if strip_rotated and _tj.rotated:
+ continue
+ # if the y position of the text is greater than the font height, assume
+ # the text is on a new line and start a new group
+ if abs(_tj.ty - last_ty) > _tj.font_height:
+ if _text.strip():
+ bt_groups.append(
+ bt_group(tj_ops[bt_idx], _text, last_displaced_tx)
+ )
+ bt_idx = _idx
+ _text = ""
+
+ # if the x position of the text is less than the last x position by
+ # more than 5 spaces widths, assume the text order should be flipped
+ # and start a new group
+ if (
+ last_displaced_tx - _tj.tx
+ > _tj.space_tx * LAYOUT_NEW_BT_GROUP_SPACE_WIDTHS
+ ):
+ if _text.strip():
+ bt_groups.append(
+ bt_group(tj_ops[bt_idx], _text, last_displaced_tx)
+ )
+ bt_idx = _idx
+ last_displaced_tx = _tj.displaced_tx
+ _text = ""
+
+ # calculate excess x translation based on ending tx of previous Tj.
+ # multiply by bool (_idx != bt_idx) to ensure spaces aren't double
+ # applied to the first tj of a BTGroup in fixed_width_page().
+ excess_tx = round(_tj.tx - last_displaced_tx, 3) * (_idx != bt_idx)
+ # space_tx could be 0 if either Tz or font_size was 0 for this _tj.
+ spaces = int(excess_tx // _tj.space_tx) if _tj.space_tx else 0
+ new_text = f'{" " * spaces}{_tj.txt}'
+
+ last_ty = _tj.ty
+ _text = f"{_text}{new_text}"
+ last_displaced_tx = _tj.displaced_tx
+ if _text:
+ bt_groups.append(bt_group(tj_ops[bt_idx], _text, last_displaced_tx))
+ text_state_mgr.reset_tm()
+ return bt_groups, tj_ops
+ if op == b"q":
+ bts, tjs = recurs_to_target_op(
+ ops, text_state_mgr, b"Q", fonts, strip_rotated
+ )
+ bt_groups.extend(bts)
+ tj_ops.extend(tjs)
+ elif op == b"cm":
+ text_state_mgr.add_cm(*operands)
+ elif op == b"BT":
+ bts, tjs = recurs_to_target_op(
+ ops, text_state_mgr, b"ET", fonts, strip_rotated
+ )
+ bt_groups.extend(bts)
+ tj_ops.extend(tjs)
+ elif op == b"Tj":
+ tj_ops.append(text_state_mgr.text_state_params(operands[0]))
+ elif op == b"TJ":
+ _tj = text_state_mgr.text_state_params()
+ for tj_op in operands[0]:
+ if isinstance(tj_op, bytes):
+ _tj = text_state_mgr.text_state_params(tj_op)
+ tj_ops.append(_tj)
+ else:
+ text_state_mgr.add_trm(_tj.displacement_matrix(TD_offset=tj_op))
+ elif op == b"'":
+ text_state_mgr.reset_trm()
+ text_state_mgr.add_tm([0, -text_state_mgr.TL])
+ tj_ops.append(text_state_mgr.text_state_params(operands[0]))
+ elif op == b'"':
+ text_state_mgr.reset_trm()
+ text_state_mgr.set_state_param(b"Tw", operands[0])
+ text_state_mgr.set_state_param(b"Tc", operands[1])
+ text_state_mgr.add_tm([0, -text_state_mgr.TL])
+ tj_ops.append(text_state_mgr.text_state_params(operands[2]))
+ elif op in (b"Td", b"Tm", b"TD", b"T*"):
+ text_state_mgr.reset_trm()
+ if op == b"Tm":
+ text_state_mgr.reset_tm()
+ elif op == b"TD":
+ text_state_mgr.set_state_param(b"TL", -operands[1])
+ elif op == b"T*":
+ operands = [0, -text_state_mgr.TL]
+ text_state_mgr.add_tm(operands)
+ elif op == b"Tf":
+ text_state_mgr.set_font(fonts[operands[0]], operands[1])
+ else: # handle Tc, Tw, Tz, TL, and Ts operators
+ text_state_mgr.set_state_param(op, operands)
+
+
+def y_coordinate_groups(
+ bt_groups: List[BTGroup], debug_path: Optional[Path] = None
+) -> Dict[int, List[BTGroup]]:
+ """
+ Group text operations by rendered y coordinate, i.e. the line number.
+
+ Args:
+ bt_groups: list of dicts as returned by text_show_operations()
+ debug_path (Path, optional): Path to a directory for saving debug output.
+
+ Returns:
+ Dict[int, List[BTGroup]]: dict of lists of text rendered by each BT operator
+ keyed by y coordinate
+ """
+ ty_groups = {
+ ty: sorted(grp, key=lambda x: x["tx"])
+ for ty, grp in groupby(
+ bt_groups, key=lambda bt_grp: int(bt_grp["ty"] * bt_grp["flip_sort"])
+ )
+ }
+ # combine groups whose y coordinates differ by less than the effective font height
+ # (accounts for mixed fonts and other minor oddities)
+ last_ty = next(iter(ty_groups))
+ last_txs = {int(_t["tx"]) for _t in ty_groups[last_ty] if _t["text"].strip()}
+ for ty in list(ty_groups)[1:]:
+ fsz = min(ty_groups[_y][0]["font_height"] for _y in (ty, last_ty))
+ txs = {int(_t["tx"]) for _t in ty_groups[ty] if _t["text"].strip()}
+ # prevent merge if both groups are rendering in the same x position.
+ no_text_overlap = not (txs & last_txs)
+ offset_less_than_font_height = abs(ty - last_ty) < fsz
+ if no_text_overlap and offset_less_than_font_height:
+ ty_groups[last_ty] = sorted(
+ ty_groups.pop(ty) + ty_groups[last_ty], key=lambda x: x["tx"]
+ )
+ last_txs |= txs
+ else:
+ last_ty = ty
+ last_txs = txs
+ if debug_path: # pragma: no cover
+ import json
+
+ debug_path.joinpath("bt_groups.json").write_text(
+ json.dumps(ty_groups, indent=2, default=str), "utf-8"
+ )
+ return ty_groups
+
+
+def text_show_operations(
+ ops: Iterator[Tuple[List[Any], bytes]],
+ fonts: Dict[str, Font],
+ strip_rotated: bool = True,
+ debug_path: Optional[Path] = None,
+) -> List[BTGroup]:
+ """
+ Extract text from BT/ET operator pairs.
+
+ Args:
+ ops (Iterator[Tuple[List, bytes]]): iterator of operators in content stream
+ fonts (Dict[str, Font]): font dictionary
+ strip_rotated: Removes text if rotated w.r.t. to the page. Defaults to True.
+ debug_path (Path, optional): Path to a directory for saving debug output.
+
+ Returns:
+ List[BTGroup]: list of dicts of text rendered by each BT operator
+ """
+ state_mgr = TextStateManager() # transformation stack manager
+ debug = bool(debug_path)
+ bt_groups: List[BTGroup] = [] # BT operator dict
+ tj_debug: List[TextStateParams] = [] # Tj/TJ operator data (debug only)
+ try:
+ warned_rotation = False
+ while True:
+ operands, op = next(ops)
+ if op in (b"BT", b"q"):
+ bts, tjs = recurs_to_target_op(
+ ops, state_mgr, b"ET" if op == b"BT" else b"Q", fonts, strip_rotated
+ )
+ if not warned_rotation and any(tj.rotated for tj in tjs):
+ warned_rotation = True
+ if strip_rotated:
+ logger_warning(
+ "Rotated text discovered. Output will be incomplete.",
+ __name__,
+ )
+ else:
+ logger_warning(
+ "Rotated text discovered. Layout will be degraded.",
+ __name__,
+ )
+ bt_groups.extend(bts)
+ if debug: # pragma: no cover
+ tj_debug.extend(tjs)
+ else: # set Tc, Tw, Tz, TL, and Ts if required. ignores all other ops
+ state_mgr.set_state_param(op, operands)
+ except StopIteration:
+ pass
+
+ # left align the data, i.e. decrement all tx values by min(tx)
+ min_x = min((x["tx"] for x in bt_groups), default=0.0)
+ bt_groups = [
+ dict(ogrp, tx=ogrp["tx"] - min_x, displaced_tx=ogrp["displaced_tx"] - min_x) # type: ignore[misc]
+ for ogrp in sorted(
+ bt_groups, key=lambda x: (x["ty"] * x["flip_sort"], -x["tx"]), reverse=True
+ )
+ ]
+
+ if debug_path: # pragma: no cover
+ import json
+
+ debug_path.joinpath("bts.json").write_text(
+ json.dumps(bt_groups, indent=2, default=str), "utf-8"
+ )
+ debug_path.joinpath("tjs.json").write_text(
+ json.dumps(
+ tj_debug, indent=2, default=lambda x: getattr(x, "to_dict", str)(x)
+ ),
+ "utf-8",
+ )
+ return bt_groups
+
+
+def fixed_char_width(bt_groups: List[BTGroup], scale_weight: float = 1.25) -> float:
+ """
+ Calculate average character width weighted by the length of the rendered
+ text in each sample for conversion to fixed-width layout.
+
+ Args:
+ bt_groups (List[BTGroup]): List of dicts of text rendered by each
+ BT operator
+
+ Returns:
+ float: fixed character width
+ """
+ char_widths = []
+ for _bt in bt_groups:
+ _len = len(_bt["text"]) * scale_weight
+ char_widths.append(((_bt["displaced_tx"] - _bt["tx"]) / _len, _len))
+ return sum(_w * _l for _w, _l in char_widths) / sum(_l for _, _l in char_widths)
+
+
+def fixed_width_page(
+ ty_groups: Dict[int, List[BTGroup]], char_width: float, space_vertically: bool
+) -> str:
+ """
+ Generate page text from text operations grouped by rendered y coordinate.
+
+ Args:
+ ty_groups: dict of text show ops as returned by y_coordinate_groups()
+ char_width: fixed character width
+ space_vertically: include blank lines inferred from y distance + font height.
+
+ Returns:
+ str: page text in a fixed width format that closely adheres to the rendered
+ layout in the source pdf.
+ """
+ lines: List[str] = []
+ last_y_coord = 0
+ for y_coord, line_data in ty_groups.items():
+ if space_vertically and lines:
+ blank_lines = (
+ int(abs(y_coord - last_y_coord) / line_data[0]["font_height"]) - 1
+ )
+ lines.extend([""] * blank_lines)
+ line = ""
+ last_disp = 0.0
+ for bt_op in line_data:
+ offset = int(bt_op["tx"] // char_width)
+ spaces = (offset - len(line)) * (ceil(last_disp) < int(bt_op["tx"]))
+ line = f"{line}{' ' * spaces}{bt_op['text']}"
+ last_disp = bt_op["displaced_tx"]
+ if line.strip() or lines:
+ lines.append(
+ "".join(c if ord(c) < 14 or ord(c) > 31 else " " for c in line)
+ )
+ last_y_coord = y_coord
+ return "\n".join(ln.rstrip() for ln in lines if space_vertically or ln.strip())
diff --git a/.venv/lib/python3.12/site-packages/pypdf/_text_extraction/_layout_mode/_font.py b/.venv/lib/python3.12/site-packages/pypdf/_text_extraction/_layout_mode/_font.py
new file mode 100644
index 00000000..a912fddb
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/pypdf/_text_extraction/_layout_mode/_font.py
@@ -0,0 +1,112 @@
+"""Font constants and classes for "layout" mode text operations"""
+
+from dataclasses import dataclass, field
+from typing import Any, Dict, Sequence, Union
+
+from ...generic import IndirectObject
+from ._font_widths import STANDARD_WIDTHS
+
+
+@dataclass
+class Font:
+ """
+ A font object formatted for use during "layout" mode text extraction
+
+ Attributes:
+ subtype (str): font subtype
+ space_width (int | float): width of a space character
+ encoding (str | Dict[int, str]): font encoding
+ char_map (dict): character map
+ font_dictionary (dict): font dictionary
+ """
+
+ subtype: str
+ space_width: Union[int, float]
+ encoding: Union[str, Dict[int, str]]
+ char_map: Dict[Any, Any]
+ font_dictionary: Dict[Any, Any]
+ width_map: Dict[str, int] = field(default_factory=dict, init=False)
+
+ def __post_init__(self) -> None:
+ # TrueType fonts have a /Widths array mapping character codes to widths
+ if isinstance(self.encoding, dict) and "/Widths" in self.font_dictionary:
+ first_char = self.font_dictionary.get("/FirstChar", 0)
+ self.width_map = {
+ self.encoding.get(idx + first_char, chr(idx + first_char)): width
+ for idx, width in enumerate(self.font_dictionary["/Widths"])
+ }
+
+ # CID fonts have a /W array mapping character codes to widths stashed in /DescendantFonts
+ if "/DescendantFonts" in self.font_dictionary:
+ d_font: Dict[Any, Any]
+ for d_font_idx, d_font in enumerate(
+ self.font_dictionary["/DescendantFonts"]
+ ):
+ while isinstance(d_font, IndirectObject):
+ d_font = d_font.get_object() # type: ignore[assignment]
+ self.font_dictionary["/DescendantFonts"][d_font_idx] = d_font
+ ord_map = {
+ ord(_target): _surrogate
+ for _target, _surrogate in self.char_map.items()
+ if isinstance(_target, str)
+ }
+ # /W width definitions have two valid formats which can be mixed and matched:
+ # (1) A character start index followed by a list of widths, e.g.
+ # `45 [500 600 700]` applies widths 500, 600, 700 to characters 45-47.
+ # (2) A character start index, a character stop index, and a width, e.g.
+ # `45 65 500` applies width 500 to characters 45-65.
+ skip_count = 0
+ _w = d_font.get("/W", [])
+ for idx, w_entry in enumerate(_w):
+ if skip_count:
+ skip_count -= 1
+ continue
+ if not isinstance(w_entry, (int, float)): # pragma: no cover
+ # We should never get here due to skip_count above. Add a
+ # warning and or use reader's "strict" to force an ex???
+ continue
+ # check for format (1): `int [int int int int ...]`
+ if isinstance(_w[idx + 1], Sequence):
+ start_idx, width_list = _w[idx : idx + 2]
+ self.width_map.update(
+ {
+ ord_map[_cidx]: _width
+ for _cidx, _width in zip(
+ range(start_idx, start_idx + len(width_list), 1),
+ width_list,
+ )
+ if _cidx in ord_map
+ }
+ )
+ skip_count = 1
+ # check for format (2): `int int int`
+ if not isinstance(_w[idx + 1], Sequence) and not isinstance(
+ _w[idx + 2], Sequence
+ ):
+ start_idx, stop_idx, const_width = _w[idx : idx + 3]
+ self.width_map.update(
+ {
+ ord_map[_cidx]: const_width
+ for _cidx in range(start_idx, stop_idx + 1, 1)
+ if _cidx in ord_map
+ }
+ )
+ skip_count = 2
+ if not self.width_map and "/BaseFont" in self.font_dictionary:
+ for key in STANDARD_WIDTHS:
+ if self.font_dictionary["/BaseFont"].startswith(f"/{key}"):
+ self.width_map = STANDARD_WIDTHS[key]
+ break
+
+ def word_width(self, word: str) -> float:
+ """Sum of character widths specified in PDF font for the supplied word"""
+ return sum(
+ [self.width_map.get(char, self.space_width * 2) for char in word], 0.0
+ )
+
+ @staticmethod
+ def to_dict(font_instance: "Font") -> Dict[str, Any]:
+ """Dataclass to dict for json.dumps serialization."""
+ return {
+ k: getattr(font_instance, k) for k in font_instance.__dataclass_fields__
+ }
diff --git a/.venv/lib/python3.12/site-packages/pypdf/_text_extraction/_layout_mode/_font_widths.py b/.venv/lib/python3.12/site-packages/pypdf/_text_extraction/_layout_mode/_font_widths.py
new file mode 100644
index 00000000..39092bcd
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/pypdf/_text_extraction/_layout_mode/_font_widths.py
@@ -0,0 +1,208 @@
+# Widths for the standard 14 fonts as described on page 416 of the PDF 1.7 standard
+STANDARD_WIDTHS = {
+ "Helvetica": { # 4 fonts, includes bold, oblique and boldoblique variants
+ " ": 278,
+ "!": 278,
+ '"': 355,
+ "#": 556,
+ "$": 556,
+ "%": 889,
+ "&": 667,
+ "'": 191,
+ "(": 333,
+ ")": 333,
+ "*": 389,
+ "+": 584,
+ ",": 278,
+ "-": 333,
+ ".": 278,
+ "/": 278,
+ "0": 556,
+ "1": 556,
+ "2": 556,
+ "3": 556,
+ "4": 556,
+ "5": 556,
+ "6": 556,
+ "7": 556,
+ "8": 556,
+ "9": 556,
+ ":": 278,
+ ";": 278,
+ "<": 584,
+ "=": 584,
+ ">": 584,
+ "?": 611,
+ "@": 975,
+ "A": 667,
+ "B": 667,
+ "C": 722,
+ "D": 722,
+ "E": 667,
+ "F": 611,
+ "G": 778,
+ "H": 722,
+ "I": 278,
+ "J": 500,
+ "K": 667,
+ "L": 556,
+ "M": 833,
+ "N": 722,
+ "O": 778,
+ "P": 667,
+ "Q": 944,
+ "R": 667,
+ "S": 667,
+ "T": 611,
+ "U": 278,
+ "V": 278,
+ "W": 584,
+ "X": 556,
+ "Y": 556,
+ "Z": 500,
+ "[": 556,
+ "\\": 556,
+ "]": 556,
+ "^": 278,
+ "_": 278,
+ "`": 278,
+ "a": 278,
+ "b": 278,
+ "c": 333,
+ "d": 556,
+ "e": 556,
+ "f": 556,
+ "g": 556,
+ "h": 556,
+ "i": 556,
+ "j": 556,
+ "k": 556,
+ "l": 556,
+ "m": 556,
+ "n": 278,
+ "o": 278,
+ "p": 556,
+ "q": 556,
+ "r": 500,
+ "s": 556,
+ "t": 556,
+ "u": 278,
+ "v": 500,
+ "w": 500,
+ "x": 222,
+ "y": 222,
+ "z": 556,
+ "{": 222,
+ "|": 833,
+ "}": 556,
+ "~": 556,
+ },
+ "Times": { # 4 fonts, includes bold, oblique and boldoblique variants
+ " ": 250,
+ "!": 333,
+ '"': 408,
+ "#": 500,
+ "$": 500,
+ "%": 833,
+ "&": 778,
+ "'": 180,
+ "(": 333,
+ ")": 333,
+ "*": 500,
+ "+": 564,
+ ",": 250,
+ "-": 333,
+ ".": 250,
+ "/": 564,
+ "0": 500,
+ "1": 500,
+ "2": 500,
+ "3": 500,
+ "4": 500,
+ "5": 500,
+ "6": 500,
+ "7": 500,
+ "8": 500,
+ "9": 500,
+ ":": 278,
+ ";": 278,
+ "<": 564,
+ "=": 564,
+ ">": 564,
+ "?": 444,
+ "@": 921,
+ "A": 722,
+ "B": 667,
+ "C": 667,
+ "D": 722,
+ "E": 611,
+ "F": 556,
+ "G": 722,
+ "H": 722,
+ "I": 333,
+ "J": 389,
+ "K": 722,
+ "L": 611,
+ "M": 889,
+ "N": 722,
+ "O": 722,
+ "P": 556,
+ "Q": 722,
+ "R": 667,
+ "S": 556,
+ "T": 611,
+ "U": 722,
+ "V": 722,
+ "W": 944,
+ "X": 722,
+ "Y": 722,
+ "Z": 611,
+ "[": 333,
+ "\\": 278,
+ "]": 333,
+ "^": 469,
+ "_": 500,
+ "`": 333,
+ "a": 444,
+ "b": 500,
+ "c": 444,
+ "d": 500,
+ "e": 444,
+ "f": 333,
+ "g": 500,
+ "h": 500,
+ "i": 278,
+ "j": 278,
+ "k": 500,
+ "l": 278,
+ "m": 722,
+ "n": 500,
+ "o": 500,
+ "p": 500,
+ "q": 500,
+ "r": 333,
+ "s": 389,
+ "t": 278,
+ "u": 500,
+ "v": 444,
+ "w": 722,
+ "x": 500,
+ "y": 444,
+ "z": 389,
+ "{": 348,
+ "|": 220,
+ "}": 348,
+ "~": 469,
+ },
+}
+STANDARD_WIDTHS[
+ "Courier"
+] = { # 4 fonts, includes bold, oblique and boldoblique variants
+ c: 600 for c in STANDARD_WIDTHS["Times"] # fixed width
+}
+STANDARD_WIDTHS["ZapfDingbats"] = {c: 1000 for c in STANDARD_WIDTHS["Times"]} # 1 font
+STANDARD_WIDTHS["Symbol"] = {c: 500 for c in STANDARD_WIDTHS["Times"]} # 1 font
+# add aliases per table H.3 on page 1110 of the PDF 1.7 standard
+STANDARD_WIDTHS["CourierNew"] = STANDARD_WIDTHS["Courier"]
+STANDARD_WIDTHS["Arial"] = STANDARD_WIDTHS["Helvetica"]
+STANDARD_WIDTHS["TimesNewRoman"] = STANDARD_WIDTHS["Times"]
diff --git a/.venv/lib/python3.12/site-packages/pypdf/_text_extraction/_layout_mode/_text_state_manager.py b/.venv/lib/python3.12/site-packages/pypdf/_text_extraction/_layout_mode/_text_state_manager.py
new file mode 100644
index 00000000..3c5d4736
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/pypdf/_text_extraction/_layout_mode/_text_state_manager.py
@@ -0,0 +1,213 @@
+"""manage the PDF transform stack during "layout" mode text extraction"""
+
+from collections import ChainMap, Counter
+from typing import Any, Dict, List, MutableMapping, Union
+from typing import ChainMap as ChainMapType
+from typing import Counter as CounterType
+
+from ...errors import PdfReadError
+from .. import mult
+from ._font import Font
+from ._text_state_params import TextStateParams
+
+TextStateManagerChainMapType = ChainMapType[Union[int, str], Union[float, bool]]
+TextStateManagerDictType = MutableMapping[Union[int, str], Union[float, bool]]
+
+
+class TextStateManager:
+ """
+ Tracks the current text state including cm/tm/trm transformation matrices.
+
+ Attributes:
+ transform_stack (ChainMap): ChainMap of cm/tm transformation matrices
+ q_queue (Counter[int]): Counter of q operators
+ q_depth (List[int]): list of q operator nesting levels
+ Tc (float): character spacing
+ Tw (float): word spacing
+ Tz (int): horizontal scaling
+ TL (float): leading
+ Ts (float): text rise
+ font (Font): font object
+ font_size (int | float): font size
+ """
+
+ def __init__(self) -> None:
+ self.transform_stack: TextStateManagerChainMapType = ChainMap(
+ self.new_transform()
+ )
+ self.q_queue: CounterType[int] = Counter()
+ self.q_depth = [0]
+ self.Tc: float = 0.0
+ self.Tw: float = 0.0
+ self.Tz: float = 100.0
+ self.TL: float = 0.0
+ self.Ts: float = 0.0
+ self.font: Union[Font, None] = None
+ self.font_size: Union[int, float] = 0
+
+ def set_state_param(self, op: bytes, value: Union[float, List[Any]]) -> None:
+ """
+ Set a text state parameter. Supports Tc, Tz, Tw, TL, and Ts operators.
+
+ Args:
+ op: operator read from PDF stream as bytes. No action is taken
+ for unsupported operators (see supported operators above).
+ value (float | List[Any]): new parameter value. If a list,
+ value[0] is used.
+ """
+ if op not in [b"Tc", b"Tz", b"Tw", b"TL", b"Ts"]:
+ return
+ self.__setattr__(op.decode(), value[0] if isinstance(value, list) else value)
+
+ def set_font(self, font: Font, size: float) -> None:
+ """
+ Set the current font and font_size.
+
+ Args:
+ font (Font): a layout mode Font
+ size (float): font size
+ """
+ self.font = font
+ self.font_size = size
+
+ def text_state_params(self, value: Union[bytes, str] = "") -> TextStateParams:
+ """
+ Create a TextStateParams instance to display a text string. Type[bytes] values
+ will be decoded implicitly.
+
+ Args:
+ value (str | bytes): text to associate with the captured state.
+
+ Raises:
+ PdfReadError: if font not set (no Tf operator in incoming pdf content stream)
+
+ Returns:
+ TextStateParams: current text state parameters
+ """
+ if not isinstance(self.font, Font):
+ raise PdfReadError(
+ "font not set: is PDF missing a Tf operator?"
+ ) # pragma: no cover
+ if isinstance(value, bytes):
+ try:
+ if isinstance(self.font.encoding, str):
+ txt = value.decode(self.font.encoding, "surrogatepass")
+ else:
+ txt = "".join(
+ self.font.encoding[x]
+ if x in self.font.encoding
+ else bytes((x,)).decode()
+ for x in value
+ )
+ except (UnicodeEncodeError, UnicodeDecodeError):
+ txt = value.decode("utf-8", "replace")
+ txt = "".join(
+ self.font.char_map[x] if x in self.font.char_map else x for x in txt
+ )
+ else:
+ txt = value
+ return TextStateParams(
+ txt,
+ self.font,
+ self.font_size,
+ self.Tc,
+ self.Tw,
+ self.Tz,
+ self.TL,
+ self.Ts,
+ self.effective_transform,
+ )
+
+ @staticmethod
+ def raw_transform(
+ _a: float = 1.0,
+ _b: float = 0.0,
+ _c: float = 0.0,
+ _d: float = 1.0,
+ _e: float = 0.0,
+ _f: float = 0.0,
+ ) -> Dict[int, float]:
+ """Only a/b/c/d/e/f matrix params"""
+ return dict(zip(range(6), map(float, (_a, _b, _c, _d, _e, _f))))
+
+ @staticmethod
+ def new_transform(
+ _a: float = 1.0,
+ _b: float = 0.0,
+ _c: float = 0.0,
+ _d: float = 1.0,
+ _e: float = 0.0,
+ _f: float = 0.0,
+ is_text: bool = False,
+ is_render: bool = False,
+ ) -> TextStateManagerDictType:
+ """Standard a/b/c/d/e/f matrix params + 'is_text' and 'is_render' keys"""
+ result: Any = TextStateManager.raw_transform(_a, _b, _c, _d, _e, _f)
+ result.update({"is_text": is_text, "is_render": is_render})
+ return result
+
+ def reset_tm(self) -> TextStateManagerChainMapType:
+ """Clear all transforms from chainmap having is_text==True or is_render==True"""
+ while (
+ self.transform_stack.maps[0]["is_text"]
+ or self.transform_stack.maps[0]["is_render"]
+ ):
+ self.transform_stack = self.transform_stack.parents
+ return self.transform_stack
+
+ def reset_trm(self) -> TextStateManagerChainMapType:
+ """Clear all transforms from chainmap having is_render==True"""
+ while self.transform_stack.maps[0]["is_render"]:
+ self.transform_stack = self.transform_stack.parents
+ return self.transform_stack
+
+ def remove_q(self) -> TextStateManagerChainMapType:
+ """Rewind to stack prior state after closing a 'q' with internal 'cm' ops"""
+ self.transform_stack = self.reset_tm()
+ self.transform_stack.maps = self.transform_stack.maps[
+ self.q_queue.pop(self.q_depth.pop(), 0) :
+ ]
+ return self.transform_stack
+
+ def add_q(self) -> None:
+ """Add another level to q_queue"""
+ self.q_depth.append(len(self.q_depth))
+
+ def add_cm(self, *args: Any) -> TextStateManagerChainMapType:
+ """Concatenate an additional transform matrix"""
+ self.transform_stack = self.reset_tm()
+ self.q_queue.update(self.q_depth[-1:])
+ self.transform_stack = self.transform_stack.new_child(self.new_transform(*args))
+ return self.transform_stack
+
+ def _complete_matrix(self, operands: List[float]) -> List[float]:
+ """Adds a, b, c, and d to an "e/f only" operand set (e.g Td)"""
+ if len(operands) == 2: # this is a Td operator or equivalent
+ operands = [1.0, 0.0, 0.0, 1.0, *operands]
+ return operands
+
+ def add_tm(self, operands: List[float]) -> TextStateManagerChainMapType:
+ """Append a text transform matrix"""
+ self.transform_stack = self.transform_stack.new_child(
+ self.new_transform( # type: ignore[misc]
+ *self._complete_matrix(operands), is_text=True # type: ignore[arg-type]
+ )
+ )
+ return self.transform_stack
+
+ def add_trm(self, operands: List[float]) -> TextStateManagerChainMapType:
+ """Append a text rendering transform matrix"""
+ self.transform_stack = self.transform_stack.new_child(
+ self.new_transform( # type: ignore[misc]
+ *self._complete_matrix(operands), is_text=True, is_render=True # type: ignore[arg-type]
+ )
+ )
+ return self.transform_stack
+
+ @property
+ def effective_transform(self) -> List[float]:
+ """Current effective transform accounting for cm, tm, and trm transforms"""
+ eff_transform = [*self.transform_stack.maps[0].values()]
+ for transform in self.transform_stack.maps[1:]:
+ eff_transform = mult(eff_transform, transform) # type: ignore[arg-type] # dict has int keys 0-5
+ return eff_transform
diff --git a/.venv/lib/python3.12/site-packages/pypdf/_text_extraction/_layout_mode/_text_state_params.py b/.venv/lib/python3.12/site-packages/pypdf/_text_extraction/_layout_mode/_text_state_params.py
new file mode 100644
index 00000000..b6e6930c
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/pypdf/_text_extraction/_layout_mode/_text_state_params.py
@@ -0,0 +1,127 @@
+"""A dataclass that captures the CTM and Text State for a tj operation"""
+
+import math
+from dataclasses import dataclass, field
+from typing import Any, Dict, List, Union
+
+from .. import mult, orient
+from ._font import Font
+
+
+@dataclass
+class TextStateParams:
+ """
+ Text state parameters and operator values for a single text value in a
+ TJ or Tj PDF operation.
+
+ Attributes:
+ txt (str): the text to be rendered.
+ font (Font): font object
+ font_size (int | float): font size
+ Tc (float): character spacing. Defaults to 0.0.
+ Tw (float): word spacing. Defaults to 0.0.
+ Tz (float): horizontal scaling. Defaults to 100.0.
+ TL (float): leading, vertical displacement between text lines. Defaults to 0.0.
+ Ts (float): text rise. Used for super/subscripts. Defaults to 0.0.
+ transform (List[float]): effective transformation matrix.
+ tx (float): x cood of rendered text, i.e. self.transform[4]
+ ty (float): y cood of rendered text. May differ from self.transform[5] per self.Ts.
+ displaced_tx (float): x coord immediately following rendered text
+ space_tx (float): tx for a space character
+ font_height (float): effective font height accounting for CTM
+ flip_vertical (bool): True if y axis has been inverted (i.e. if self.transform[3] < 0.)
+ rotated (bool): True if the text orientation is rotated with respect to the page.
+ """
+
+ txt: str
+ font: Font
+ font_size: Union[int, float]
+ Tc: float = 0.0
+ Tw: float = 0.0
+ Tz: float = 100.0
+ TL: float = 0.0
+ Ts: float = 0.0
+ transform: List[float] = field(
+ default_factory=lambda: [1.0, 0.0, 0.0, 1.0, 0.0, 0.0]
+ )
+ tx: float = field(default=0.0, init=False)
+ ty: float = field(default=0.0, init=False)
+ displaced_tx: float = field(default=0.0, init=False)
+ space_tx: float = field(default=0.0, init=False)
+ font_height: float = field(default=0.0, init=False)
+ flip_vertical: bool = field(default=False, init=False)
+ rotated: bool = field(default=False, init=False)
+
+ def __post_init__(self) -> None:
+ if orient(self.transform) in (90, 270):
+ self.transform = mult(
+ [1.0, -self.transform[1], -self.transform[2], 1.0, 0.0, 0.0],
+ self.transform,
+ )
+ self.rotated = True
+ # self.transform[0] AND self.transform[3] < 0 indicates true rotation.
+ # If only self.transform[3] < 0, the y coords are simply inverted.
+ if orient(self.transform) == 180 and self.transform[0] < -1e-6:
+ self.transform = mult([-1.0, 0.0, 0.0, -1.0, 0.0, 0.0], self.transform)
+ self.rotated = True
+ self.displaced_tx = self.displaced_transform()[4]
+ self.tx = self.transform[4]
+ self.ty = self.render_transform()[5]
+ self.space_tx = round(self.word_tx(" "), 3)
+ if self.space_tx < 1e-6:
+ # if the " " char is assigned 0 width (e.g. for fine tuned spacing
+ # with TJ int operators a la crazyones.pdf), calculate space_tx as
+ # a TD_offset of -2 * font.space_width where font.space_width is
+ # the space_width calculated in _cmap.py.
+ self.space_tx = round(self.word_tx("", self.font.space_width * -2), 3)
+ self.font_height = self.font_size * math.sqrt(
+ self.transform[1] ** 2 + self.transform[3] ** 2
+ )
+ # flip_vertical handles PDFs generated by Microsoft Word's "publish" command.
+ self.flip_vertical = self.transform[3] < -1e-6 # inverts y axis
+
+ def font_size_matrix(self) -> List[float]:
+ """Font size matrix"""
+ return [
+ self.font_size * (self.Tz / 100.0),
+ 0.0,
+ 0.0,
+ self.font_size,
+ 0.0,
+ self.Ts,
+ ]
+
+ def displaced_transform(self) -> List[float]:
+ """Effective transform matrix after text has been rendered."""
+ return mult(self.displacement_matrix(), self.transform)
+
+ def render_transform(self) -> List[float]:
+ """Effective transform matrix accounting for font size, Tz, and Ts."""
+ return mult(self.font_size_matrix(), self.transform)
+
+ def displacement_matrix(
+ self, word: Union[str, None] = None, TD_offset: float = 0.0
+ ) -> List[float]:
+ """
+ Text displacement matrix
+
+ Args:
+ word (str, optional): Defaults to None in which case self.txt displacement is
+ returned.
+ TD_offset (float, optional): translation applied by TD operator. Defaults to 0.0.
+ """
+ word = word if word is not None else self.txt
+ return [1.0, 0.0, 0.0, 1.0, self.word_tx(word, TD_offset), 0.0]
+
+ def word_tx(self, word: str, TD_offset: float = 0.0) -> float:
+ """Horizontal text displacement for any word according this text state"""
+ return (
+ (self.font_size * ((self.font.word_width(word) - TD_offset) / 1000.0))
+ + self.Tc
+ + word.count(" ") * self.Tw
+ ) * (self.Tz / 100.0)
+
+ @staticmethod
+ def to_dict(inst: "TextStateParams") -> Dict[str, Any]:
+ """Dataclass to dict for json.dumps serialization"""
+ return {k: getattr(inst, k) for k in inst.__dataclass_fields__ if k != "font"}
diff --git a/.venv/lib/python3.12/site-packages/pypdf/_utils.py b/.venv/lib/python3.12/site-packages/pypdf/_utils.py
new file mode 100644
index 00000000..97565369
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/pypdf/_utils.py
@@ -0,0 +1,683 @@
+# Copyright (c) 2006, Mathieu Fenniak
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
+#
+# * Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
+# * Redistributions in binary form must reproduce the above copyright notice,
+# this list of conditions and the following disclaimer in the documentation
+# and/or other materials provided with the distribution.
+# * The name of the author may not be used to endorse or promote products
+# derived from this software without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+# POSSIBILITY OF SUCH DAMAGE.
+
+"""Utility functions for PDF library."""
+__author__ = "Mathieu Fenniak"
+__author_email__ = "biziqe@mathieu.fenniak.net"
+
+import functools
+import logging
+import re
+import sys
+import warnings
+from dataclasses import dataclass
+from datetime import datetime, timezone
+from io import DEFAULT_BUFFER_SIZE, BytesIO
+from os import SEEK_CUR
+from typing import (
+ IO,
+ Any,
+ Dict,
+ List,
+ Optional,
+ Pattern,
+ Tuple,
+ Union,
+ cast,
+ overload,
+)
+
+if sys.version_info[:2] >= (3, 10):
+ # Python 3.10+: https://www.python.org/dev/peps/pep-0484/
+ from typing import TypeAlias
+else:
+ from typing_extensions import TypeAlias
+
+from .errors import (
+ STREAM_TRUNCATED_PREMATURELY,
+ DeprecationError,
+ PdfStreamError,
+)
+
+TransformationMatrixType: TypeAlias = Tuple[
+ Tuple[float, float, float], Tuple[float, float, float], Tuple[float, float, float]
+]
+CompressedTransformationMatrix: TypeAlias = Tuple[
+ float, float, float, float, float, float
+]
+
+StreamType = IO[Any]
+StrByteType = Union[str, StreamType]
+
+
+def parse_iso8824_date(text: Optional[str]) -> Optional[datetime]:
+ orgtext = text
+ if text is None:
+ return None
+ if text[0].isdigit():
+ text = "D:" + text
+ if text.endswith(("Z", "z")):
+ text += "0000"
+ text = text.replace("z", "+").replace("Z", "+").replace("'", "")
+ i = max(text.find("+"), text.find("-"))
+ if i > 0 and i != len(text) - 5:
+ text += "00"
+ for f in (
+ "D:%Y",
+ "D:%Y%m",
+ "D:%Y%m%d",
+ "D:%Y%m%d%H",
+ "D:%Y%m%d%H%M",
+ "D:%Y%m%d%H%M%S",
+ "D:%Y%m%d%H%M%S%z",
+ ):
+ try:
+ d = datetime.strptime(text, f) # noqa: DTZ007
+ except ValueError:
+ continue
+ else:
+ if text.endswith("+0000"):
+ d = d.replace(tzinfo=timezone.utc)
+ return d
+ raise ValueError(f"Can not convert date: {orgtext}")
+
+
+def _get_max_pdf_version_header(header1: str, header2: str) -> str:
+ versions = (
+ "%PDF-1.3",
+ "%PDF-1.4",
+ "%PDF-1.5",
+ "%PDF-1.6",
+ "%PDF-1.7",
+ "%PDF-2.0",
+ )
+ pdf_header_indices = []
+ if header1 in versions:
+ pdf_header_indices.append(versions.index(header1))
+ if header2 in versions:
+ pdf_header_indices.append(versions.index(header2))
+ if len(pdf_header_indices) == 0:
+ raise ValueError(f"neither {header1!r} nor {header2!r} are proper headers")
+ return versions[max(pdf_header_indices)]
+
+
+def read_until_whitespace(stream: StreamType, maxchars: Optional[int] = None) -> bytes:
+ """
+ Read non-whitespace characters and return them.
+
+ Stops upon encountering whitespace or when maxchars is reached.
+
+ Args:
+ stream: The data stream from which was read.
+ maxchars: The maximum number of bytes returned; by default unlimited.
+
+ Returns:
+ The data which was read.
+ """
+ txt = b""
+ while True:
+ tok = stream.read(1)
+ if tok.isspace() or not tok:
+ break
+ txt += tok
+ if len(txt) == maxchars:
+ break
+ return txt
+
+
+def read_non_whitespace(stream: StreamType) -> bytes:
+ """
+ Find and read the next non-whitespace character (ignores whitespace).
+
+ Args:
+ stream: The data stream from which was read.
+
+ Returns:
+ The data which was read.
+ """
+ tok = stream.read(1)
+ while tok in WHITESPACES:
+ tok = stream.read(1)
+ return tok
+
+
+def skip_over_whitespace(stream: StreamType) -> bool:
+ """
+ Similar to read_non_whitespace, but return a boolean if more than one
+ whitespace character was read.
+
+ Args:
+ stream: The data stream from which was read.
+
+ Returns:
+ True if more than one whitespace was skipped, otherwise return False.
+ """
+ tok = WHITESPACES[0]
+ cnt = 0
+ while tok in WHITESPACES:
+ tok = stream.read(1)
+ cnt += 1
+ return cnt > 1
+
+
+def check_if_whitespace_only(value: bytes) -> bool:
+ """
+ Check if the given value consists of whitespace characters only.
+
+ Args:
+ value: The bytes to check.
+
+ Returns:
+ True if the value only has whitespace characters, otherwise return False.
+ """
+ for index in range(len(value)):
+ current = value[index : index + 1]
+ if current not in WHITESPACES:
+ return False
+ return True
+
+
+def skip_over_comment(stream: StreamType) -> None:
+ tok = stream.read(1)
+ stream.seek(-1, 1)
+ if tok == b"%":
+ while tok not in (b"\n", b"\r"):
+ tok = stream.read(1)
+
+
+def read_until_regex(stream: StreamType, regex: Pattern[bytes]) -> bytes:
+ """
+ Read until the regular expression pattern matched (ignore the match).
+ Treats EOF on the underlying stream as the end of the token to be matched.
+
+ Args:
+ regex: re.Pattern
+
+ Returns:
+ The read bytes.
+ """
+ name = b""
+ while True:
+ tok = stream.read(16)
+ if not tok:
+ return name
+ m = regex.search(name + tok)
+ if m is not None:
+ stream.seek(m.start() - (len(name) + len(tok)), 1)
+ name = (name + tok)[: m.start()]
+ break
+ name += tok
+ return name
+
+
+def read_block_backwards(stream: StreamType, to_read: int) -> bytes:
+ """
+ Given a stream at position X, read a block of size to_read ending at position X.
+
+ This changes the stream's position to the beginning of where the block was
+ read.
+
+ Args:
+ stream:
+ to_read:
+
+ Returns:
+ The data which was read.
+ """
+ if stream.tell() < to_read:
+ raise PdfStreamError("Could not read malformed PDF file")
+ # Seek to the start of the block we want to read.
+ stream.seek(-to_read, SEEK_CUR)
+ read = stream.read(to_read)
+ # Seek to the start of the block we read after reading it.
+ stream.seek(-to_read, SEEK_CUR)
+ return read
+
+
+def read_previous_line(stream: StreamType) -> bytes:
+ """
+ Given a byte stream with current position X, return the previous line.
+
+ All characters between the first CR/LF byte found before X
+ (or, the start of the file, if no such byte is found) and position X
+ After this call, the stream will be positioned one byte after the
+ first non-CRLF character found beyond the first CR/LF byte before X,
+ or, if no such byte is found, at the beginning of the stream.
+
+ Args:
+ stream: StreamType:
+
+ Returns:
+ The data which was read.
+ """
+ line_content = []
+ found_crlf = False
+ if stream.tell() == 0:
+ raise PdfStreamError(STREAM_TRUNCATED_PREMATURELY)
+ while True:
+ to_read = min(DEFAULT_BUFFER_SIZE, stream.tell())
+ if to_read == 0:
+ break
+ # Read the block. After this, our stream will be one
+ # beyond the initial position.
+ block = read_block_backwards(stream, to_read)
+ idx = len(block) - 1
+ if not found_crlf:
+ # We haven't found our first CR/LF yet.
+ # Read off characters until we hit one.
+ while idx >= 0 and block[idx] not in b"\r\n":
+ idx -= 1
+ if idx >= 0:
+ found_crlf = True
+ if found_crlf:
+ # We found our first CR/LF already (on this block or
+ # a previous one).
+ # Our combined line is the remainder of the block
+ # plus any previously read blocks.
+ line_content.append(block[idx + 1 :])
+ # Continue to read off any more CRLF characters.
+ while idx >= 0 and block[idx] in b"\r\n":
+ idx -= 1
+ else:
+ # Didn't find CR/LF yet - add this block to our
+ # previously read blocks and continue.
+ line_content.append(block)
+ if idx >= 0:
+ # We found the next non-CRLF character.
+ # Set the stream position correctly, then break
+ stream.seek(idx + 1, SEEK_CUR)
+ break
+ # Join all the blocks in the line (which are in reverse order)
+ return b"".join(line_content[::-1])
+
+
+def matrix_multiply(
+ a: TransformationMatrixType, b: TransformationMatrixType
+) -> TransformationMatrixType:
+ return tuple( # type: ignore[return-value]
+ tuple(sum(float(i) * float(j) for i, j in zip(row, col)) for col in zip(*b))
+ for row in a
+ )
+
+
+def mark_location(stream: StreamType) -> None:
+ """Create text file showing current location in context."""
+ # Mainly for debugging
+ radius = 5000
+ stream.seek(-radius, 1)
+ with open("pypdf_pdfLocation.txt", "wb") as output_fh:
+ output_fh.write(stream.read(radius))
+ output_fh.write(b"HERE")
+ output_fh.write(stream.read(radius))
+ stream.seek(-radius, 1)
+
+
+B_CACHE: Dict[Union[str, bytes], bytes] = {}
+
+
+def b_(s: Union[str, bytes]) -> bytes:
+ if isinstance(s, bytes):
+ return s
+ bc = B_CACHE
+ if s in bc:
+ return bc[s]
+ try:
+ r = s.encode("latin-1")
+ if len(s) < 2:
+ bc[s] = r
+ return r
+ except Exception:
+ r = s.encode("utf-8")
+ if len(s) < 2:
+ bc[s] = r
+ return r
+
+
+def str_(b: Any) -> str:
+ if isinstance(b, bytes):
+ return b.decode("latin-1")
+ else:
+ return str(b) # will return b.__str__() if defined
+
+
+@overload
+def ord_(b: str) -> int:
+ ...
+
+
+@overload
+def ord_(b: bytes) -> bytes:
+ ...
+
+
+@overload
+def ord_(b: int) -> int:
+ ...
+
+
+def ord_(b: Union[int, str, bytes]) -> Union[int, bytes]:
+ if isinstance(b, str):
+ return ord(b)
+ return b
+
+
+WHITESPACES = (b" ", b"\n", b"\r", b"\t", b"\x00")
+WHITESPACES_AS_BYTES = b"".join(WHITESPACES)
+WHITESPACES_AS_REGEXP = b"[" + WHITESPACES_AS_BYTES + b"]"
+
+
+def paeth_predictor(left: int, up: int, up_left: int) -> int:
+ p = left + up - up_left
+ dist_left = abs(p - left)
+ dist_up = abs(p - up)
+ dist_up_left = abs(p - up_left)
+
+ if dist_left <= dist_up and dist_left <= dist_up_left:
+ return left
+ elif dist_up <= dist_up_left:
+ return up
+ else:
+ return up_left
+
+
+def deprecate(msg: str, stacklevel: int = 3) -> None:
+ warnings.warn(msg, DeprecationWarning, stacklevel=stacklevel)
+
+
+def deprecation(msg: str) -> None:
+ raise DeprecationError(msg)
+
+
+def deprecate_with_replacement(old_name: str, new_name: str, removed_in: str) -> None:
+ """Raise an exception that a feature will be removed, but has a replacement."""
+ deprecate(f"{old_name} is deprecated and will be removed in pypdf {removed_in}. Use {new_name} instead.", 4)
+
+
+def deprecation_with_replacement(old_name: str, new_name: str, removed_in: str) -> None:
+ """Raise an exception that a feature was already removed, but has a replacement."""
+ deprecation(f"{old_name} is deprecated and was removed in pypdf {removed_in}. Use {new_name} instead.")
+
+
+def deprecate_no_replacement(name: str, removed_in: str) -> None:
+ """Raise an exception that a feature will be removed without replacement."""
+ deprecate(f"{name} is deprecated and will be removed in pypdf {removed_in}.", 4)
+
+
+def deprecation_no_replacement(name: str, removed_in: str) -> None:
+ """Raise an exception that a feature was already removed without replacement."""
+ deprecation(f"{name} is deprecated and was removed in pypdf {removed_in}.")
+
+
+def logger_error(msg: str, src: str) -> None:
+ """
+ Use this instead of logger.error directly.
+
+ That allows people to overwrite it more easily.
+
+ See the docs on when to use which:
+ https://pypdf.readthedocs.io/en/latest/user/suppress-warnings.html
+ """
+ logging.getLogger(src).error(msg)
+
+
+def logger_warning(msg: str, src: str) -> None:
+ """
+ Use this instead of logger.warning directly.
+
+ That allows people to overwrite it more easily.
+
+ ## Exception, warnings.warn, logger_warning
+ - Exceptions should be used if the user should write code that deals with
+ an error case, e.g. the PDF being completely broken.
+ - warnings.warn should be used if the user needs to fix their code, e.g.
+ DeprecationWarnings
+ - logger_warning should be used if the user needs to know that an issue was
+ handled by pypdf, e.g. a non-compliant PDF being read in a way that
+ pypdf could apply a robustness fix to still read it. This applies mainly
+ to strict=False mode.
+ """
+ logging.getLogger(src).warning(msg)
+
+
+def rename_kwargs(
+ func_name: str, kwargs: Dict[str, Any], aliases: Dict[str, str], fail: bool = False
+) -> None:
+ """
+ Helper function to deprecate arguments.
+
+ Args:
+ func_name: Name of the function to be deprecated
+ kwargs:
+ aliases:
+ fail:
+ """
+ for old_term, new_term in aliases.items():
+ if old_term in kwargs:
+ if fail:
+ raise DeprecationError(
+ f"{old_term} is deprecated as an argument. Use {new_term} instead"
+ )
+ if new_term in kwargs:
+ raise TypeError(
+ f"{func_name} received both {old_term} and {new_term} as "
+ f"an argument. {old_term} is deprecated. "
+ f"Use {new_term} instead."
+ )
+ kwargs[new_term] = kwargs.pop(old_term)
+ warnings.warn(
+ message=(
+ f"{old_term} is deprecated as an argument. Use {new_term} instead"
+ ),
+ category=DeprecationWarning,
+ )
+
+
+def _human_readable_bytes(bytes: int) -> str:
+ if bytes < 10**3:
+ return f"{bytes} Byte"
+ elif bytes < 10**6:
+ return f"{bytes / 10**3:.1f} kB"
+ elif bytes < 10**9:
+ return f"{bytes / 10**6:.1f} MB"
+ else:
+ return f"{bytes / 10**9:.1f} GB"
+
+
+# The following class has been copied from Django:
+# https://github.com/django/django/blob/adae619426b6f50046b3daaa744db52989c9d6db/django/utils/functional.py#L51-L65
+#
+# Original license:
+#
+# ---------------------------------------------------------------------------------
+# Copyright (c) Django Software Foundation and individual contributors.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without modification,
+# are permitted provided that the following conditions are met:
+#
+# 1. Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
+#
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# 3. Neither the name of Django nor the names of its contributors may be used
+# to endorse or promote products derived from this software without
+# specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
+# ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+# ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+# ---------------------------------------------------------------------------------
+class classproperty: # noqa: N801
+ """
+ Decorator that converts a method with a single cls argument into a property
+ that can be accessed directly from the class.
+ """
+
+ def __init__(self, method=None): # type: ignore # noqa: ANN001
+ self.fget = method
+
+ def __get__(self, instance, cls=None) -> Any: # type: ignore # noqa: ANN001
+ return self.fget(cls)
+
+ def getter(self, method): # type: ignore # noqa: ANN001, ANN202
+ self.fget = method
+ return self
+
+
+@dataclass
+class File:
+ from .generic import IndirectObject
+
+ name: str
+ data: bytes
+ image: Optional[Any] = None # optional ; direct image access
+ indirect_reference: Optional[IndirectObject] = None # optional ; link to PdfObject
+
+ def __str__(self) -> str:
+ return f"{self.__class__.__name__}(name={self.name}, data: {_human_readable_bytes(len(self.data))})"
+
+ def __repr__(self) -> str:
+ return self.__str__()[:-1] + f", hash: {hash(self.data)})"
+
+
+@dataclass
+class ImageFile(File):
+ from .generic import IndirectObject
+
+ image: Optional[Any] = None # optional ; direct PIL image access
+ indirect_reference: Optional[IndirectObject] = None # optional ; link to PdfObject
+
+ def replace(self, new_image: Any, **kwargs: Any) -> None:
+ """
+ Replace the Image with a new PIL image.
+
+ Args:
+ new_image (PIL.Image.Image): The new PIL image to replace the existing image.
+ **kwargs: Additional keyword arguments to pass to `Image.Image.save()`.
+
+ Raises:
+ TypeError: If the image is inline or in a PdfReader.
+ TypeError: If the image does not belong to a PdfWriter.
+ TypeError: If `new_image` is not a PIL Image.
+
+ Note:
+ This method replaces the existing image with a new image.
+ It is not allowed for inline images or images within a PdfReader.
+ The `kwargs` parameter allows passing additional parameters
+ to `Image.Image.save()`, such as quality.
+ """
+ from PIL import Image
+
+ from ._reader import PdfReader
+
+ # to prevent circular import
+ from .filters import _xobj_to_image
+ from .generic import DictionaryObject, PdfObject
+
+ if self.indirect_reference is None:
+ raise TypeError("Can not update an inline image")
+ if not hasattr(self.indirect_reference.pdf, "_id_translated"):
+ raise TypeError("Can not update an image not belonging to a PdfWriter")
+ if not isinstance(new_image, Image.Image):
+ raise TypeError("new_image shall be a PIL Image")
+ b = BytesIO()
+ new_image.save(b, "PDF", **kwargs)
+ reader = PdfReader(b)
+ assert reader.pages[0].images[0].indirect_reference is not None
+ self.indirect_reference.pdf._objects[self.indirect_reference.idnum - 1] = (
+ reader.pages[0].images[0].indirect_reference.get_object()
+ )
+ cast(
+ PdfObject, self.indirect_reference.get_object()
+ ).indirect_reference = self.indirect_reference
+ # change the object attributes
+ extension, byte_stream, img = _xobj_to_image(
+ cast(DictionaryObject, self.indirect_reference.get_object())
+ )
+ assert extension is not None
+ self.name = self.name[: self.name.rfind(".")] + extension
+ self.data = byte_stream
+ self.image = img
+
+
+@functools.total_ordering
+class Version:
+ COMPONENT_PATTERN = re.compile(r"^(\d+)(.*)$")
+
+ def __init__(self, version_str: str) -> None:
+ self.version_str = version_str
+ self.components = self._parse_version(version_str)
+
+ def _parse_version(self, version_str: str) -> List[Tuple[int, str]]:
+ components = version_str.split(".")
+ parsed_components = []
+ for component in components:
+ match = Version.COMPONENT_PATTERN.match(component)
+ if not match:
+ parsed_components.append((0, component))
+ continue
+ integer_prefix = match.group(1)
+ suffix = match.group(2)
+ if integer_prefix is None:
+ integer_prefix = 0
+ parsed_components.append((int(integer_prefix), suffix))
+ return parsed_components
+
+ def __eq__(self, other: object) -> bool:
+ if not isinstance(other, Version):
+ return False
+ return self.components == other.components
+
+ def __lt__(self, other: Any) -> bool:
+ if not isinstance(other, Version):
+ raise ValueError(f"Version cannot be compared against {type(other)}")
+ min_len = min(len(self.components), len(other.components))
+ for i in range(min_len):
+ self_value, self_suffix = self.components[i]
+ other_value, other_suffix = other.components[i]
+
+ if self_value < other_value:
+ return True
+ elif self_value > other_value:
+ return False
+
+ if self_suffix < other_suffix:
+ return True
+ elif self_suffix > other_suffix:
+ return False
+
+ return len(self.components) < len(other.components)
diff --git a/.venv/lib/python3.12/site-packages/pypdf/_version.py b/.venv/lib/python3.12/site-packages/pypdf/_version.py
new file mode 100644
index 00000000..ed48cdab
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/pypdf/_version.py
@@ -0,0 +1 @@
+__version__ = "4.3.1"
diff --git a/.venv/lib/python3.12/site-packages/pypdf/_writer.py b/.venv/lib/python3.12/site-packages/pypdf/_writer.py
new file mode 100644
index 00000000..00b9d498
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/pypdf/_writer.py
@@ -0,0 +1,3047 @@
+# Copyright (c) 2006, Mathieu Fenniak
+# Copyright (c) 2007, Ashish Kulkarni <kulkarni.ashish@gmail.com>
+#
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
+#
+# * Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
+# * Redistributions in binary form must reproduce the above copyright notice,
+# this list of conditions and the following disclaimer in the documentation
+# and/or other materials provided with the distribution.
+# * The name of the author may not be used to endorse or promote products
+# derived from this software without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+# POSSIBILITY OF SUCH DAMAGE.
+
+import collections
+import decimal
+import enum
+import hashlib
+import re
+import uuid
+from io import BytesIO, FileIO, IOBase
+from pathlib import Path
+from types import TracebackType
+from typing import (
+ IO,
+ Any,
+ Callable,
+ Deque,
+ Dict,
+ Iterable,
+ List,
+ Optional,
+ Pattern,
+ Tuple,
+ Type,
+ Union,
+ cast,
+)
+
+from ._cmap import _default_fonts_space_width, build_char_map_from_dict
+from ._doc_common import PdfDocCommon
+from ._encryption import EncryptAlgorithm, Encryption
+from ._page import PageObject
+from ._page_labels import nums_clear_range, nums_insert, nums_next
+from ._reader import PdfReader
+from ._utils import (
+ StrByteType,
+ StreamType,
+ _get_max_pdf_version_header,
+ b_,
+ deprecate_with_replacement,
+ logger_warning,
+)
+from .constants import AnnotationDictionaryAttributes as AA
+from .constants import CatalogAttributes as CA
+from .constants import (
+ CatalogDictionary,
+ FileSpecificationDictionaryEntries,
+ GoToActionArguments,
+ ImageType,
+ InteractiveFormDictEntries,
+ PageLabelStyle,
+ TypFitArguments,
+ UserAccessPermissions,
+)
+from .constants import Core as CO
+from .constants import FieldDictionaryAttributes as FA
+from .constants import PageAttributes as PG
+from .constants import PagesAttributes as PA
+from .constants import TrailerKeys as TK
+from .errors import PyPdfError
+from .generic import (
+ PAGE_FIT,
+ ArrayObject,
+ BooleanObject,
+ ByteStringObject,
+ ContentStream,
+ DecodedStreamObject,
+ Destination,
+ DictionaryObject,
+ Fit,
+ FloatObject,
+ IndirectObject,
+ NameObject,
+ NullObject,
+ NumberObject,
+ PdfObject,
+ RectangleObject,
+ StreamObject,
+ TextStringObject,
+ TreeObject,
+ ViewerPreferences,
+ create_string_object,
+ hex_to_rgb,
+)
+from .pagerange import PageRange, PageRangeSpec
+from .types import (
+ AnnotationSubtype,
+ BorderArrayType,
+ LayoutType,
+ OutlineItemType,
+ OutlineType,
+ PagemodeType,
+)
+from .xmp import XmpInformation
+
+ALL_DOCUMENT_PERMISSIONS = UserAccessPermissions.all()
+DEFAULT_FONT_HEIGHT_IN_MULTILINE = 12
+
+
+class ObjectDeletionFlag(enum.IntFlag):
+ NONE = 0
+ TEXT = enum.auto()
+ LINKS = enum.auto()
+ ATTACHMENTS = enum.auto()
+ OBJECTS_3D = enum.auto()
+ ALL_ANNOTATIONS = enum.auto()
+ XOBJECT_IMAGES = enum.auto()
+ INLINE_IMAGES = enum.auto()
+ DRAWING_IMAGES = enum.auto()
+ IMAGES = XOBJECT_IMAGES | INLINE_IMAGES | DRAWING_IMAGES
+
+
+def _rolling_checksum(stream: BytesIO, blocksize: int = 65536) -> str:
+ hash = hashlib.md5()
+ for block in iter(lambda: stream.read(blocksize), b""):
+ hash.update(block)
+ return hash.hexdigest()
+
+
+class PdfWriter(PdfDocCommon):
+ """
+ Write a PDF file out, given pages produced by another class or through
+ cloning a PDF file during initialization.
+
+ Typically data is added from a :class:`PdfReader<pypdf.PdfReader>`.
+ """
+
+ def __init__(
+ self,
+ fileobj: Union[None, PdfReader, StrByteType, Path] = "",
+ clone_from: Union[None, PdfReader, StrByteType, Path] = None,
+ ) -> None:
+ self._header = b"%PDF-1.3"
+ self._objects: List[PdfObject] = []
+ """The indirect objects in the PDF."""
+
+ self._idnum_hash: Dict[bytes, IndirectObject] = {}
+ """Maps hash values of indirect objects to their IndirectObject instances."""
+
+ self._id_translated: Dict[int, Dict[int, int]] = {}
+
+ # The root of our page tree node.
+ pages = DictionaryObject()
+ pages.update(
+ {
+ NameObject(PA.TYPE): NameObject("/Pages"),
+ NameObject(PA.COUNT): NumberObject(0),
+ NameObject(PA.KIDS): ArrayObject(),
+ }
+ )
+ self._pages = self._add_object(pages)
+ self.flattened_pages = []
+
+ # info object
+ info = DictionaryObject()
+ info.update({NameObject("/Producer"): create_string_object("pypdf")})
+ self._info_obj: PdfObject = self._add_object(info)
+
+ # root object
+ self._root_object = DictionaryObject()
+ self._root_object.update(
+ {
+ NameObject(PA.TYPE): NameObject(CO.CATALOG),
+ NameObject(CO.PAGES): self._pages,
+ }
+ )
+ self._root = self._add_object(self._root_object)
+
+ def _get_clone_from(
+ fileobj: Union[None, PdfReader, str, Path, IO[Any], BytesIO],
+ clone_from: Union[None, PdfReader, str, Path, IO[Any], BytesIO],
+ ) -> Union[None, PdfReader, str, Path, IO[Any], BytesIO]:
+ if not isinstance(fileobj, (str, Path, IO, BytesIO)) or (
+ fileobj != "" and clone_from is None
+ ):
+ cloning = True
+ if not (
+ not isinstance(fileobj, (str, Path))
+ or (
+ Path(str(fileobj)).exists()
+ and Path(str(fileobj)).stat().st_size > 0
+ )
+ ):
+ cloning = False
+ if isinstance(fileobj, (IO, BytesIO)):
+ t = fileobj.tell()
+ fileobj.seek(-1, 2)
+ if fileobj.tell() == 0:
+ cloning = False
+ fileobj.seek(t, 0)
+ if cloning:
+ clone_from = fileobj
+ return clone_from
+
+ clone_from = _get_clone_from(fileobj, clone_from)
+ # to prevent overwriting
+ self.temp_fileobj = fileobj
+ self.fileobj = ""
+ self.with_as_usage = False
+ if clone_from is not None:
+ if not isinstance(clone_from, PdfReader):
+ clone_from = PdfReader(clone_from)
+ self.clone_document_from_reader(clone_from)
+
+ self._encryption: Optional[Encryption] = None
+ self._encrypt_entry: Optional[DictionaryObject] = None
+ self._ID: Union[ArrayObject, None] = None
+
+ # for commonality
+ @property
+ def is_encrypted(self) -> bool:
+ """
+ Read-only boolean property showing whether this PDF file is encrypted.
+
+ Note that this property, if true, will remain true even after the
+ :meth:`decrypt()<pypdf.PdfReader.decrypt>` method is called.
+ """
+ return False
+
+ @property
+ def root_object(self) -> DictionaryObject:
+ """
+ Provide direct access to PDF Structure.
+
+ Note:
+ Recommended only for read access.
+ """
+ return self._root_object
+
+ @property
+ def _info(self) -> Optional[DictionaryObject]:
+ """
+ Provide access to "/Info". Standardized with PdfReader.
+
+ Returns:
+ /Info Dictionary; None if the entry does not exist
+ """
+ return cast(DictionaryObject, self._info_obj.get_object())
+
+ @_info.setter
+ def _info(self, value: Union[IndirectObject, DictionaryObject]) -> None:
+ obj = cast(DictionaryObject, self._info_obj.get_object())
+ obj.clear()
+ obj.update(cast(DictionaryObject, value.get_object()))
+
+ @property
+ def xmp_metadata(self) -> Optional[XmpInformation]:
+ """XMP (Extensible Metadata Platform) data."""
+ return cast(XmpInformation, self.root_object.xmp_metadata)
+
+ @xmp_metadata.setter
+ def xmp_metadata(self, value: Optional[XmpInformation]) -> None:
+ """XMP (Extensible Metadata Platform) data."""
+ if value is None:
+ if "/Metadata" in self.root_object:
+ del self.root_object["/Metadata"]
+ else:
+ self.root_object[NameObject("/Metadata")] = value
+
+ return self.root_object.xmp_metadata # type: ignore
+
+ def __enter__(self) -> "PdfWriter":
+ """Store that writer is initialized by 'with'."""
+ t = self.temp_fileobj
+ self.__init__() # type: ignore
+ self.with_as_usage = True
+ self.fileobj = t # type: ignore
+ return self
+
+ def __exit__(
+ self,
+ exc_type: Optional[Type[BaseException]],
+ exc: Optional[BaseException],
+ traceback: Optional[TracebackType],
+ ) -> None:
+ """Write data to the fileobj."""
+ if self.fileobj:
+ self.write(self.fileobj)
+
+ def _repr_mimebundle_(
+ self,
+ include: Union[None, Iterable[str]] = None,
+ exclude: Union[None, Iterable[str]] = None,
+ ) -> Dict[str, Any]:
+ """
+ Integration into Jupyter Notebooks.
+
+ This method returns a dictionary that maps a mime-type to its
+ representation.
+
+ See https://ipython.readthedocs.io/en/stable/config/integrating.html
+ """
+ pdf_data = BytesIO()
+ self.write(pdf_data)
+ data = {
+ "application/pdf": pdf_data,
+ }
+
+ if include is not None:
+ # Filter representations based on include list
+ data = {k: v for k, v in data.items() if k in include}
+
+ if exclude is not None:
+ # Remove representations based on exclude list
+ data = {k: v for k, v in data.items() if k not in exclude}
+
+ return data
+
+ @property
+ def pdf_header(self) -> str:
+ """
+ Read/Write property of the PDF header that is written.
+
+ This should be something like ``'%PDF-1.5'``. It is recommended to set
+ the lowest version that supports all features which are used within the
+ PDF file.
+
+ Note: `pdf_header` returns a string but accepts bytes or str for writing
+ """
+ return self._header.decode()
+
+ @pdf_header.setter
+ def pdf_header(self, new_header: Union[str, bytes]) -> None:
+ if isinstance(new_header, str):
+ new_header = new_header.encode()
+ self._header = new_header
+
+ def _add_object(self, obj: PdfObject) -> IndirectObject:
+ if (
+ getattr(obj, "indirect_reference", None) is not None
+ and obj.indirect_reference.pdf == self # type: ignore
+ ):
+ return obj.indirect_reference # type: ignore
+ # check for /Contents in Pages (/Contents in annotation are strings)
+ if isinstance(obj, DictionaryObject) and isinstance(
+ obj.get(PG.CONTENTS, None), (ArrayObject, DictionaryObject)
+ ):
+ obj[NameObject(PG.CONTENTS)] = self._add_object(obj[PG.CONTENTS])
+ self._objects.append(obj)
+ obj.indirect_reference = IndirectObject(len(self._objects), 0, self)
+ return obj.indirect_reference
+
+ def get_object(
+ self,
+ indirect_reference: Union[int, IndirectObject],
+ ) -> PdfObject:
+ if isinstance(indirect_reference, int):
+ return self._objects[indirect_reference - 1]
+ if indirect_reference.pdf != self:
+ raise ValueError("pdf must be self")
+ return self._objects[indirect_reference.idnum - 1]
+
+ def _replace_object(
+ self,
+ indirect_reference: Union[int, IndirectObject],
+ obj: PdfObject,
+ ) -> PdfObject:
+ if isinstance(indirect_reference, IndirectObject):
+ if indirect_reference.pdf != self:
+ raise ValueError("pdf must be self")
+ indirect_reference = indirect_reference.idnum
+ gen = self._objects[indirect_reference - 1].indirect_reference.generation # type: ignore
+ if (
+ getattr(obj, "indirect_reference", None) is not None
+ and obj.indirect_reference.pdf != self # type: ignore
+ ):
+ obj = obj.clone(self)
+ self._objects[indirect_reference - 1] = obj
+ obj.indirect_reference = IndirectObject(indirect_reference, gen, self)
+ return self._objects[indirect_reference - 1]
+
+ def _add_page(
+ self,
+ page: PageObject,
+ action: Callable[[Any, Union[PageObject, IndirectObject]], None],
+ excluded_keys: Iterable[str] = (),
+ ) -> PageObject:
+ assert cast(str, page[PA.TYPE]) == CO.PAGE
+ page_org = page
+ excluded_keys = list(excluded_keys)
+ excluded_keys += [PA.PARENT, "/StructParents"]
+ # acrobat does not accept to have two indirect ref pointing on the same
+ # page; therefore in order to add easily multiple copies of the same
+ # page, we need to create a new dictionary for the page, however the
+ # objects below (including content) are not duplicated:
+ try: # delete an already existing page
+ del self._id_translated[id(page_org.indirect_reference.pdf)][ # type: ignore
+ page_org.indirect_reference.idnum # type: ignore
+ ]
+ except Exception:
+ pass
+ page = cast("PageObject", page_org.clone(self, False, excluded_keys))
+ if page_org.pdf is not None:
+ other = page_org.pdf.pdf_header
+ self.pdf_header = _get_max_pdf_version_header(self.pdf_header, other)
+ page[NameObject(PA.PARENT)] = self._pages
+ pages = cast(DictionaryObject, self.get_object(self._pages))
+ assert page.indirect_reference is not None
+ action(pages[PA.KIDS], page.indirect_reference)
+ action(self.flattened_pages, page)
+ page_count = cast(int, pages[PA.COUNT])
+ pages[NameObject(PA.COUNT)] = NumberObject(page_count + 1)
+ return page
+
+ def set_need_appearances_writer(self, state: bool = True) -> None:
+ """
+ Sets the "NeedAppearances" flag in the PDF writer.
+
+ The "NeedAppearances" flag indicates whether the appearance dictionary
+ for form fields should be automatically generated by the PDF viewer or
+ if the embedded appearance should be used.
+
+ Args:
+ state: The actual value of the NeedAppearances flag.
+
+ Returns:
+ None
+ """
+ # See 12.7.2 and 7.7.2 for more information:
+ # https://opensource.adobe.com/dc-acrobat-sdk-docs/pdfstandards/PDF32000_2008.pdf
+ try:
+ # get the AcroForm tree
+ if CatalogDictionary.ACRO_FORM not in self._root_object:
+ self._root_object[
+ NameObject(CatalogDictionary.ACRO_FORM)
+ ] = self._add_object(DictionaryObject())
+
+ need_appearances = NameObject(InteractiveFormDictEntries.NeedAppearances)
+ cast(DictionaryObject, self._root_object[CatalogDictionary.ACRO_FORM])[
+ need_appearances
+ ] = BooleanObject(state)
+ except Exception as exc: # pragma: no cover
+ logger_warning(
+ f"set_need_appearances_writer({state}) catch : {exc}", __name__
+ )
+
+ def create_viewer_preferences(self) -> ViewerPreferences:
+ o = ViewerPreferences()
+ self._root_object[
+ NameObject(CatalogDictionary.VIEWER_PREFERENCES)
+ ] = self._add_object(o)
+ return o
+
+ def add_page(
+ self,
+ page: PageObject,
+ excluded_keys: Iterable[str] = (),
+ ) -> PageObject:
+ """
+ Add a page to this PDF file.
+
+ Recommended for advanced usage including the adequate excluded_keys.
+
+ The page is usually acquired from a :class:`PdfReader<pypdf.PdfReader>`
+ instance.
+
+ Args:
+ page: The page to add to the document. Should be
+ an instance of :class:`PageObject<pypdf._page.PageObject>`
+ excluded_keys:
+
+ Returns:
+ The added PageObject.
+ """
+ return self._add_page(page, list.append, excluded_keys)
+
+ def insert_page(
+ self,
+ page: PageObject,
+ index: int = 0,
+ excluded_keys: Iterable[str] = (),
+ ) -> PageObject:
+ """
+ Insert a page in this PDF file. The page is usually acquired from a
+ :class:`PdfReader<pypdf.PdfReader>` instance.
+
+ Args:
+ page: The page to add to the document.
+ index: Position at which the page will be inserted.
+ excluded_keys:
+
+ Returns:
+ The added PageObject.
+ """
+ return self._add_page(page, lambda kids, p: kids.insert(index, p))
+
+ def _get_page_number_by_indirect(
+ self, indirect_reference: Union[None, int, NullObject, IndirectObject]
+ ) -> Optional[int]:
+ """
+ Generate _page_id2num.
+
+ Args:
+ indirect_reference:
+
+ Returns:
+ The page number or None
+ """
+ # to provide same function as in PdfReader
+ if indirect_reference is None or isinstance(indirect_reference, NullObject):
+ return None
+ if isinstance(indirect_reference, int):
+ indirect_reference = IndirectObject(indirect_reference, 0, self)
+ obj = indirect_reference.get_object()
+ if isinstance(obj, PageObject):
+ return obj.page_number
+ return None
+
+ def add_blank_page(
+ self, width: Optional[float] = None, height: Optional[float] = None
+ ) -> PageObject:
+ """
+ Append a blank page to this PDF file and return it.
+
+ If no page size is specified, use the size of the last page.
+
+ Args:
+ width: The width of the new page expressed in default user
+ space units.
+ height: The height of the new page expressed in default
+ user space units.
+
+ Returns:
+ The newly appended page.
+
+ Raises:
+ PageSizeNotDefinedError: if width and height are not defined
+ and previous page does not exist.
+ """
+ page = PageObject.create_blank_page(self, width, height)
+ return self.add_page(page)
+
+ def insert_blank_page(
+ self,
+ width: Optional[Union[float, decimal.Decimal]] = None,
+ height: Optional[Union[float, decimal.Decimal]] = None,
+ index: int = 0,
+ ) -> PageObject:
+ """
+ Insert a blank page to this PDF file and return it.
+
+ If no page size is specified, use the size of the last page.
+
+ Args:
+ width: The width of the new page expressed in default user
+ space units.
+ height: The height of the new page expressed in default
+ user space units.
+ index: Position to add the page.
+
+ Returns:
+ The newly inserted page.
+
+ Raises:
+ PageSizeNotDefinedError: if width and height are not defined
+ and previous page does not exist.
+ """
+ if width is None or height is None and (self.get_num_pages() - 1) >= index:
+ oldpage = self.pages[index]
+ width = oldpage.mediabox.width
+ height = oldpage.mediabox.height
+ page = PageObject.create_blank_page(self, width, height)
+ self.insert_page(page, index)
+ return page
+
+ @property
+ def open_destination(
+ self,
+ ) -> Union[None, Destination, TextStringObject, ByteStringObject]:
+ return super().open_destination
+
+ @open_destination.setter
+ def open_destination(self, dest: Union[None, str, Destination, PageObject]) -> None:
+ if dest is None:
+ try:
+ del self._root_object["/OpenAction"]
+ except KeyError:
+ pass
+ elif isinstance(dest, str):
+ self._root_object[NameObject("/OpenAction")] = TextStringObject(dest)
+ elif isinstance(dest, Destination):
+ self._root_object[NameObject("/OpenAction")] = dest.dest_array
+ elif isinstance(dest, PageObject):
+ self._root_object[NameObject("/OpenAction")] = Destination(
+ "Opening",
+ dest.indirect_reference
+ if dest.indirect_reference is not None
+ else NullObject(),
+ PAGE_FIT,
+ ).dest_array
+
+ def add_js(self, javascript: str) -> None:
+ """
+ Add JavaScript which will launch upon opening this PDF.
+
+ Args:
+ javascript: Your Javascript.
+
+ >>> output.add_js("this.print({bUI:true,bSilent:false,bShrinkToFit:true});")
+ # Example: This will launch the print window when the PDF is opened.
+ """
+ # Names / JavaScript preferred to be able to add multiple scripts
+ if "/Names" not in self._root_object:
+ self._root_object[NameObject(CA.NAMES)] = DictionaryObject()
+ names = cast(DictionaryObject, self._root_object[CA.NAMES])
+ if "/JavaScript" not in names:
+ names[NameObject("/JavaScript")] = DictionaryObject(
+ {NameObject("/Names"): ArrayObject()}
+ )
+ js_list = cast(
+ ArrayObject, cast(DictionaryObject, names["/JavaScript"])["/Names"]
+ )
+
+ js = DictionaryObject()
+ js.update(
+ {
+ NameObject(PA.TYPE): NameObject("/Action"),
+ NameObject("/S"): NameObject("/JavaScript"),
+ NameObject("/JS"): TextStringObject(f"{javascript}"),
+ }
+ )
+ # We need a name for parameterized javascript in the pdf file,
+ # but it can be anything.
+ js_list.append(create_string_object(str(uuid.uuid4())))
+ js_list.append(self._add_object(js))
+
+ def add_attachment(self, filename: str, data: Union[str, bytes]) -> None:
+ """
+ Embed a file inside the PDF.
+
+ Reference:
+ https://opensource.adobe.com/dc-acrobat-sdk-docs/pdfstandards/PDF32000_2008.pdf
+ Section 7.11.3
+
+ Args:
+ filename: The filename to display.
+ data: The data in the file.
+ """
+ # We need three entries:
+ # * The file's data
+ # * The /Filespec entry
+ # * The file's name, which goes in the Catalog
+
+ # The entry for the file
+ # Sample:
+ # 8 0 obj
+ # <<
+ # /Length 12
+ # /Type /EmbeddedFile
+ # >>
+ # stream
+ # Hello world!
+ # endstream
+ # endobj
+
+ file_entry = DecodedStreamObject()
+ file_entry.set_data(b_(data))
+ file_entry.update({NameObject(PA.TYPE): NameObject("/EmbeddedFile")})
+
+ # The Filespec entry
+ # Sample:
+ # 7 0 obj
+ # <<
+ # /Type /Filespec
+ # /F (hello.txt)
+ # /EF << /F 8 0 R >>
+ # >>
+ # endobj
+
+ ef_entry = DictionaryObject()
+ ef_entry.update({NameObject("/F"): self._add_object(file_entry)})
+
+ filespec = DictionaryObject()
+ filespec.update(
+ {
+ NameObject(PA.TYPE): NameObject("/Filespec"),
+ NameObject(FileSpecificationDictionaryEntries.F): create_string_object(
+ filename
+ ), # Perhaps also try TextStringObject
+ NameObject(FileSpecificationDictionaryEntries.EF): ef_entry,
+ }
+ )
+
+ # Then create the entry for the root, as it needs
+ # a reference to the Filespec
+ # Sample:
+ # 1 0 obj
+ # <<
+ # /Type /Catalog
+ # /Outlines 2 0 R
+ # /Pages 3 0 R
+ # /Names << /EmbeddedFiles << /Names [(hello.txt) 7 0 R] >> >>
+ # >>
+ # endobj
+
+ if CA.NAMES not in self._root_object:
+ self._root_object[NameObject(CA.NAMES)] = self._add_object(
+ DictionaryObject()
+ )
+ if "/EmbeddedFiles" not in cast(DictionaryObject, self._root_object[CA.NAMES]):
+ embedded_files_names_dictionary = DictionaryObject(
+ {NameObject(CA.NAMES): ArrayObject()}
+ )
+ cast(DictionaryObject, self._root_object[CA.NAMES])[
+ NameObject("/EmbeddedFiles")
+ ] = self._add_object(embedded_files_names_dictionary)
+ else:
+ embedded_files_names_dictionary = cast(
+ DictionaryObject,
+ cast(DictionaryObject, self._root_object[CA.NAMES])["/EmbeddedFiles"],
+ )
+ cast(ArrayObject, embedded_files_names_dictionary[CA.NAMES]).extend(
+ [create_string_object(filename), filespec]
+ )
+
+ def append_pages_from_reader(
+ self,
+ reader: PdfReader,
+ after_page_append: Optional[Callable[[PageObject], None]] = None,
+ ) -> None:
+ """
+ Copy pages from reader to writer. Includes an optional callback
+ parameter which is invoked after pages are appended to the writer.
+
+ ``append`` should be preferred.
+
+ Args:
+ reader: a PdfReader object from which to copy page
+ annotations to this writer object. The writer's annots
+ will then be updated.
+ after_page_append:
+ Callback function that is invoked after each page is appended to
+ the writer. Signature includes a reference to the appended page
+ (delegates to append_pages_from_reader). The single parameter of
+ the callback is a reference to the page just appended to the
+ document.
+ """
+ # Get page count from writer and reader
+ reader_num_pages = len(reader.pages)
+ # Copy pages from reader to writer
+ for reader_page_number in range(reader_num_pages):
+ reader_page = reader.pages[reader_page_number]
+ writer_page = self.add_page(reader_page)
+ # Trigger callback, pass writer page as parameter
+ if callable(after_page_append):
+ after_page_append(writer_page)
+
+ def _update_field_annotation(
+ self,
+ field: DictionaryObject,
+ anno: DictionaryObject,
+ font_name: str = "",
+ font_size: float = -1,
+ ) -> None:
+ # Calculate rectangle dimensions
+ _rct = cast(RectangleObject, anno[AA.Rect])
+ rct = RectangleObject((0, 0, abs(_rct[2] - _rct[0]), abs(_rct[3] - _rct[1])))
+
+ # Extract font information
+ da = anno.get_inherited(
+ AA.DA,
+ cast(DictionaryObject, self.root_object[CatalogDictionary.ACRO_FORM]).get(
+ AA.DA, None
+ ),
+ )
+ if da is None:
+ da = TextStringObject("/Helv 0 Tf 0 g")
+ else:
+ da = da.get_object()
+ font_properties = da.replace("\n", " ").replace("\r", " ").split(" ")
+ font_properties = [x for x in font_properties if x != ""]
+ if font_name:
+ font_properties[font_properties.index("Tf") - 2] = font_name
+ else:
+ font_name = font_properties[font_properties.index("Tf") - 2]
+ font_height = (
+ font_size
+ if font_size >= 0
+ else float(font_properties[font_properties.index("Tf") - 1])
+ )
+ if font_height == 0:
+ if field.get(FA.Ff, 0) & FA.FfBits.Multiline:
+ font_height = DEFAULT_FONT_HEIGHT_IN_MULTILINE
+ else:
+ font_height = rct.height - 2
+ font_properties[font_properties.index("Tf") - 1] = str(font_height)
+ da = " ".join(font_properties)
+ y_offset = rct.height - 1 - font_height
+
+ # Retrieve font information from local DR ...
+ dr: Any = cast(
+ DictionaryObject,
+ cast(
+ DictionaryObject,
+ anno.get_inherited(
+ "/DR",
+ cast(
+ DictionaryObject, self.root_object[CatalogDictionary.ACRO_FORM]
+ ).get("/DR", DictionaryObject()),
+ ),
+ ).get_object(),
+ )
+ dr = dr.get("/Font", DictionaryObject()).get_object()
+ # _default_fonts_space_width keys is the list of Standard fonts
+ if font_name not in dr and font_name not in _default_fonts_space_width:
+ # ...or AcroForm dictionary
+ dr = cast(
+ Dict[Any, Any],
+ cast(
+ DictionaryObject, self.root_object[CatalogDictionary.ACRO_FORM]
+ ).get("/DR", {}),
+ )
+ dr = dr.get_object().get("/Font", DictionaryObject()).get_object()
+ font_res = dr.get(font_name, None)
+ if font_res is not None:
+ font_res = cast(DictionaryObject, font_res.get_object())
+ font_subtype, _, font_encoding, font_map = build_char_map_from_dict(
+ 200, font_res
+ )
+ try: # get rid of width stored in -1 key
+ del font_map[-1]
+ except KeyError:
+ pass
+ font_full_rev: Dict[str, bytes]
+ if isinstance(font_encoding, str):
+ font_full_rev = {
+ v: k.encode(font_encoding) for k, v in font_map.items()
+ }
+ else:
+ font_full_rev = {v: bytes((k,)) for k, v in font_encoding.items()}
+ font_encoding_rev = {v: bytes((k,)) for k, v in font_encoding.items()}
+ for kk, v in font_map.items():
+ font_full_rev[v] = font_encoding_rev.get(kk, kk)
+ else:
+ logger_warning(f"Font dictionary for {font_name} not found.", __name__)
+ font_full_rev = {}
+
+ # Retrieve field text and selected values
+ field_flags = field.get(FA.Ff, 0)
+ if field.get(FA.FT, "/Tx") == "/Ch" and field_flags & FA.FfBits.Combo == 0:
+ txt = "\n".join(anno.get_inherited(FA.Opt, []))
+ sel = field.get("/V", [])
+ if not isinstance(sel, list):
+ sel = [sel]
+ else: # /Tx
+ txt = field.get("/V", "")
+ sel = []
+ # Escape parentheses (pdf 1.7 reference, table 3.2 Literal Strings)
+ txt = txt.replace("\\", "\\\\").replace("(", r"\(").replace(")", r"\)")
+ # Generate appearance stream
+ ap_stream = f"q\n/Tx BMC \nq\n1 1 {rct.width - 1} {rct.height - 1} re\nW\nBT\n{da}\n".encode()
+ for line_number, line in enumerate(txt.replace("\n", "\r").split("\r")):
+ if line in sel:
+ # may be improved but cannot find how to get fill working => replaced with lined box
+ ap_stream += (
+ f"1 {y_offset - (line_number * font_height * 1.4) - 1} {rct.width - 2} {font_height + 2} re\n"
+ f"0.5 0.5 0.5 rg s\n{da}\n"
+ ).encode()
+ if line_number == 0:
+ ap_stream += f"2 {y_offset} Td\n".encode()
+ else:
+ # Td is a relative translation
+ ap_stream += f"0 {- font_height * 1.4} Td\n".encode()
+ enc_line: List[bytes] = [
+ font_full_rev.get(c, c.encode("utf-16-be")) for c in line
+ ]
+ if any(len(c) >= 2 for c in enc_line):
+ ap_stream += b"<" + (b"".join(enc_line)).hex().encode() + b"> Tj\n"
+ else:
+ ap_stream += b"(" + b"".join(enc_line) + b") Tj\n"
+ ap_stream += b"ET\nQ\nEMC\nQ\n"
+
+ # Create appearance dictionary
+ dct = DecodedStreamObject.initialize_from_dictionary(
+ {
+ NameObject("/Type"): NameObject("/XObject"),
+ NameObject("/Subtype"): NameObject("/Form"),
+ NameObject("/BBox"): rct,
+ "__streamdata__": ByteStringObject(ap_stream),
+ "/Length": 0,
+ }
+ )
+ if AA.AP in anno:
+ for k, v in cast(DictionaryObject, anno[AA.AP]).get("/N", {}).items():
+ if k not in {"/BBox", "/Length", "/Subtype", "/Type", "/Filter"}:
+ dct[k] = v
+
+ # Update Resources with font information if necessary
+ if font_res is not None:
+ dct[NameObject("/Resources")] = DictionaryObject(
+ {
+ NameObject("/Font"): DictionaryObject(
+ {
+ NameObject(font_name): getattr(
+ font_res, "indirect_reference", font_res
+ )
+ }
+ )
+ }
+ )
+ if AA.AP not in anno:
+ anno[NameObject(AA.AP)] = DictionaryObject(
+ {NameObject("/N"): self._add_object(dct)}
+ )
+ elif "/N" not in cast(DictionaryObject, anno[AA.AP]):
+ cast(DictionaryObject, anno[NameObject(AA.AP)])[
+ NameObject("/N")
+ ] = self._add_object(dct)
+ else: # [/AP][/N] exists
+ n = anno[AA.AP]["/N"].indirect_reference.idnum # type: ignore
+ self._objects[n - 1] = dct
+ dct.indirect_reference = IndirectObject(n, 0, self)
+
+ FFBITS_NUL = FA.FfBits(0)
+
+ def update_page_form_field_values(
+ self,
+ page: Union[PageObject, List[PageObject], None],
+ fields: Dict[str, Any],
+ flags: FA.FfBits = FFBITS_NUL,
+ auto_regenerate: Optional[bool] = True,
+ ) -> None:
+ """
+ Update the form field values for a given page from a fields dictionary.
+
+ Copy field texts and values from fields to page.
+ If the field links to a parent object, add the information to the parent.
+
+ Args:
+ page: `PageObject` - references **PDF writer's page** where the
+ annotations and field data will be updated.
+ `List[Pageobject]` - provides list of pages to be processed.
+ `None` - all pages.
+ fields: a Python dictionary of:
+
+ * field names (/T) as keys and text values (/V) as value
+ * field names (/T) as keys and list of text values (/V) for multiple choice list
+ * field names (/T) as keys and tuple of:
+ * text values (/V)
+ * font id (e.g. /F1, the font id must exist)
+ * font size (0 for autosize)
+
+ flags: A set of flags from :class:`~pypdf.constants.FieldDictionaryAttributes.FfBits`.
+
+ auto_regenerate: Set/unset the need_appearances flag;
+ the flag is unchanged if auto_regenerate is None.
+ """
+ if CatalogDictionary.ACRO_FORM not in self._root_object:
+ raise PyPdfError("No /AcroForm dictionary in PdfWriter Object")
+ af = cast(DictionaryObject, self._root_object[CatalogDictionary.ACRO_FORM])
+ if InteractiveFormDictEntries.Fields not in af:
+ raise PyPdfError("No /Fields dictionary in Pdf in PdfWriter Object")
+ if isinstance(auto_regenerate, bool):
+ self.set_need_appearances_writer(auto_regenerate)
+ # Iterate through pages, update field values
+ if page is None:
+ page = list(self.pages)
+ if isinstance(page, list):
+ for p in page:
+ if PG.ANNOTS in p: # just to prevent warnings
+ self.update_page_form_field_values(p, fields, flags, None)
+ return None
+ if PG.ANNOTS not in page:
+ logger_warning("No fields to update on this page", __name__)
+ return
+ for writer_annot in page[PG.ANNOTS]: # type: ignore
+ writer_annot = cast(DictionaryObject, writer_annot.get_object())
+ if writer_annot.get("/Subtype", "") != "/Widget":
+ continue
+ if "/FT" in writer_annot and "/T" in writer_annot:
+ writer_parent_annot = writer_annot
+ else:
+ writer_parent_annot = writer_annot.get(
+ PG.PARENT, DictionaryObject()
+ ).get_object()
+
+ for field, value in fields.items():
+ if not (
+ self._get_qualified_field_name(writer_parent_annot) == field
+ or writer_parent_annot.get("/T", None) == field
+ ):
+ continue
+ if (
+ writer_parent_annot.get("/FT", None) == "/Ch"
+ and "/I" in writer_parent_annot
+ ):
+ del writer_parent_annot["/I"]
+ if flags:
+ writer_annot[NameObject(FA.Ff)] = NumberObject(flags)
+ if isinstance(value, list):
+ lst = ArrayObject(TextStringObject(v) for v in value)
+ writer_parent_annot[NameObject(FA.V)] = lst
+ elif isinstance(value, tuple):
+ writer_annot[NameObject(FA.V)] = TextStringObject(
+ value[0],
+ )
+ else:
+ writer_parent_annot[NameObject(FA.V)] = TextStringObject(value)
+ if writer_parent_annot.get(FA.FT) in ("/Btn"):
+ # case of Checkbox button (no /FT found in Radio widgets
+ v = NameObject(value)
+ if v not in writer_annot[NameObject(AA.AP)][NameObject("/N")]:
+ v = NameObject("/Off")
+ # other cases will be updated through the for loop
+ writer_annot[NameObject(AA.AS)] = v
+ elif (
+ writer_parent_annot.get(FA.FT) == "/Tx"
+ or writer_parent_annot.get(FA.FT) == "/Ch"
+ ):
+ # textbox
+ if isinstance(value, tuple):
+ self._update_field_annotation(
+ writer_parent_annot, writer_annot, value[1], value[2]
+ )
+ else:
+ self._update_field_annotation(writer_parent_annot, writer_annot)
+ elif (
+ writer_annot.get(FA.FT) == "/Sig"
+ ): # deprecated # not implemented yet
+ # signature
+ logger_warning("Signature forms not implemented yet", __name__)
+
+ def reattach_fields(
+ self, page: Optional[PageObject] = None
+ ) -> List[DictionaryObject]:
+ """
+ Parse annotations within the page looking for orphan fields and
+ reattach then into the Fields Structure.
+
+ Args:
+ page: page to analyze.
+ If none is provided, all pages will be analyzed.
+
+ Returns:
+ list of reattached fields.
+ """
+ lst = []
+ if page is None:
+ for p in self.pages:
+ lst += self.reattach_fields(p)
+ return lst
+
+ try:
+ af = cast(DictionaryObject, self._root_object[CatalogDictionary.ACRO_FORM])
+ except KeyError:
+ af = DictionaryObject()
+ self._root_object[NameObject(CatalogDictionary.ACRO_FORM)] = af
+ try:
+ fields = cast(ArrayObject, af[InteractiveFormDictEntries.Fields])
+ except KeyError:
+ fields = ArrayObject()
+ af[NameObject(InteractiveFormDictEntries.Fields)] = fields
+
+ if "/Annots" not in page:
+ return lst
+ annots = cast(ArrayObject, page["/Annots"])
+ for idx in range(len(annots)):
+ ano = annots[idx]
+ indirect = isinstance(ano, IndirectObject)
+ ano = cast(DictionaryObject, ano.get_object())
+ if ano.get("/Subtype", "") == "/Widget" and "/FT" in ano:
+ if (
+ "indirect_reference" in ano.__dict__
+ and ano.indirect_reference in fields
+ ):
+ continue
+ if not indirect:
+ annots[idx] = self._add_object(ano)
+ fields.append(ano.indirect_reference)
+ lst.append(ano)
+ return lst
+
+ def clone_reader_document_root(self, reader: PdfReader) -> None:
+ """
+ Copy the reader document root to the writer and all sub-elements,
+ including pages, threads, outlines,... For partial insertion, ``append``
+ should be considered.
+
+ Args:
+ reader: PdfReader from which the document root should be copied.
+ """
+ self._objects.clear()
+ self._root_object = reader.root_object.clone(self)
+ self._root = self._root_object.indirect_reference # type: ignore[assignment]
+ self._pages = self._root_object.raw_get("/Pages")
+ self._flatten()
+ assert self.flattened_pages is not None
+ for p in self.flattened_pages:
+ p[NameObject("/Parent")] = self._pages
+ self._objects[cast(IndirectObject, p.indirect_reference).idnum - 1] = p
+ cast(DictionaryObject, self._pages.get_object())[
+ NameObject("/Kids")
+ ] = ArrayObject([p.indirect_reference for p in self.flattened_pages])
+
+ def clone_document_from_reader(
+ self,
+ reader: PdfReader,
+ after_page_append: Optional[Callable[[PageObject], None]] = None,
+ ) -> None:
+ """
+ Create a copy (clone) of a document from a PDF file reader cloning
+ section '/Root' and '/Info' and '/ID' of the pdf.
+
+ Args:
+ reader: PDF file reader instance from which the clone
+ should be created.
+ after_page_append:
+ Callback function that is invoked after each page is appended to
+ the writer. Signature includes a reference to the appended page
+ (delegates to append_pages_from_reader). The single parameter of
+ the callback is a reference to the page just appended to the
+ document.
+ """
+ self.clone_reader_document_root(reader)
+ self._info_obj = self._add_object(DictionaryObject())
+ if TK.INFO in reader.trailer:
+ self._info = reader._info # actually copy fields
+ try:
+ self._ID = cast(ArrayObject, reader._ID).clone(self)
+ except AttributeError:
+ pass
+ if callable(after_page_append):
+ for page in cast(
+ ArrayObject, cast(DictionaryObject, self._pages.get_object())["/Kids"]
+ ):
+ after_page_append(page.get_object())
+
+ def _compute_document_identifier(self) -> ByteStringObject:
+ stream = BytesIO()
+ self._write_pdf_structure(stream)
+ stream.seek(0)
+ return ByteStringObject(_rolling_checksum(stream).encode("utf8"))
+
+ def generate_file_identifiers(self) -> None:
+ """
+ Generate an identifier for the PDF that will be written.
+
+ The only point of this is ensuring uniqueness. Reproducibility is not
+ required.
+ When a file is first written, both identifiers shall be set to the same value.
+ If both identifiers match when a file reference is resolved, it is very
+ likely that the correct and unchanged file has been found. If only the first
+ identifier matches, a different version of the correct file has been found.
+ see 14.4 "File Identifiers".
+ """
+ if self._ID:
+ id1 = self._ID[0]
+ id2 = self._compute_document_identifier()
+ else:
+ id1 = self._compute_document_identifier()
+ id2 = id1
+ self._ID = ArrayObject((id1, id2))
+
+ def encrypt(
+ self,
+ user_password: str,
+ owner_password: Optional[str] = None,
+ use_128bit: bool = True,
+ permissions_flag: UserAccessPermissions = ALL_DOCUMENT_PERMISSIONS,
+ *,
+ algorithm: Optional[str] = None,
+ ) -> None:
+ """
+ Encrypt this PDF file with the PDF Standard encryption handler.
+
+ Args:
+ user_password: The password which allows for opening
+ and reading the PDF file with the restrictions provided.
+ owner_password: The password which allows for
+ opening the PDF files without any restrictions. By default,
+ the owner password is the same as the user password.
+ use_128bit: flag as to whether to use 128bit
+ encryption. When false, 40bit encryption will be used.
+ By default, this flag is on.
+ permissions_flag: permissions as described in
+ Table 3.20 of the PDF 1.7 specification. A bit value of 1 means
+ the permission is granted.
+ Hence an integer value of -1 will set all flags.
+ Bit position 3 is for printing, 4 is for modifying content,
+ 5 and 6 control annotations, 9 for form fields,
+ 10 for extraction of text and graphics.
+ algorithm: encrypt algorithm. Values may be one of "RC4-40", "RC4-128",
+ "AES-128", "AES-256-R5", "AES-256". If it is valid,
+ `use_128bit` will be ignored.
+ """
+ if owner_password is None:
+ owner_password = user_password
+
+ if algorithm is not None:
+ try:
+ alg = getattr(EncryptAlgorithm, algorithm.replace("-", "_"))
+ except AttributeError:
+ raise ValueError(f"algorithm '{algorithm}' NOT supported")
+ else:
+ alg = EncryptAlgorithm.RC4_128
+ if not use_128bit:
+ alg = EncryptAlgorithm.RC4_40
+ self.generate_file_identifiers()
+ assert self._ID
+ self._encryption = Encryption.make(alg, permissions_flag, self._ID[0])
+ # in case call `encrypt` again
+ entry = self._encryption.write_entry(user_password, owner_password)
+ if self._encrypt_entry:
+ # replace old encrypt_entry
+ assert self._encrypt_entry.indirect_reference is not None
+ entry.indirect_reference = self._encrypt_entry.indirect_reference
+ self._objects[entry.indirect_reference.idnum - 1] = entry
+ else:
+ self._add_object(entry)
+ self._encrypt_entry = entry
+
+ def write_stream(self, stream: StreamType) -> None:
+ if hasattr(stream, "mode") and "b" not in stream.mode:
+ logger_warning(
+ f"File <{stream.name}> to write to is not in binary mode. "
+ "It may not be written to correctly.",
+ __name__,
+ )
+
+ if not self._root:
+ self._root = self._add_object(self._root_object)
+
+ self._sweep_indirect_references(self._root)
+
+ object_positions = self._write_pdf_structure(stream)
+ xref_location = self._write_xref_table(stream, object_positions)
+ self._write_trailer(stream, xref_location)
+
+ def write(self, stream: Union[Path, StrByteType]) -> Tuple[bool, IO[Any]]:
+ """
+ Write the collection of pages added to this object out as a PDF file.
+
+ Args:
+ stream: An object to write the file to. The object can support
+ the write method and the tell method, similar to a file object, or
+ be a file path, just like the fileobj, just named it stream to keep
+ existing workflow.
+
+ Returns:
+ A tuple (bool, IO).
+ """
+ my_file = False
+
+ if stream == "":
+ raise ValueError(f"Output(stream={stream}) is empty.")
+
+ if isinstance(stream, (str, Path)):
+ stream = FileIO(stream, "wb")
+ self.with_as_usage = True #
+ my_file = True
+
+ self.write_stream(stream)
+
+ if self.with_as_usage:
+ stream.close()
+
+ return my_file, stream
+
+ def _write_pdf_structure(self, stream: StreamType) -> List[int]:
+ object_positions = []
+ stream.write(self.pdf_header.encode() + b"\n")
+ stream.write(b"%\xE2\xE3\xCF\xD3\n")
+
+ for i, obj in enumerate(self._objects):
+ if obj is not None:
+ idnum = i + 1
+ object_positions.append(stream.tell())
+ stream.write(f"{idnum} 0 obj\n".encode())
+ if self._encryption and obj != self._encrypt_entry:
+ obj = self._encryption.encrypt_object(obj, idnum, 0)
+ obj.write_to_stream(stream)
+ stream.write(b"\nendobj\n")
+ return object_positions
+
+ def _write_xref_table(self, stream: StreamType, object_positions: List[int]) -> int:
+ xref_location = stream.tell()
+ stream.write(b"xref\n")
+ stream.write(f"0 {len(self._objects) + 1}\n".encode())
+ stream.write(f"{0:0>10} {65535:0>5} f \n".encode())
+ for offset in object_positions:
+ stream.write(f"{offset:0>10} {0:0>5} n \n".encode())
+ return xref_location
+
+ def _write_trailer(self, stream: StreamType, xref_location: int) -> None:
+ """
+ Write the PDF trailer to the stream.
+
+ To quote the PDF specification:
+ [The] trailer [gives] the location of the cross-reference table and
+ of certain special objects within the body of the file.
+ """
+ stream.write(b"trailer\n")
+ trailer = DictionaryObject()
+ trailer.update(
+ {
+ NameObject(TK.SIZE): NumberObject(len(self._objects) + 1),
+ NameObject(TK.ROOT): self._root,
+ NameObject(TK.INFO): self._info_obj,
+ }
+ )
+ if self._ID:
+ trailer[NameObject(TK.ID)] = self._ID
+ if self._encrypt_entry:
+ trailer[NameObject(TK.ENCRYPT)] = self._encrypt_entry.indirect_reference
+ trailer.write_to_stream(stream)
+ stream.write(f"\nstartxref\n{xref_location}\n%%EOF\n".encode()) # eof
+
+ def add_metadata(self, infos: Dict[str, Any]) -> None:
+ """
+ Add custom metadata to the output.
+
+ Args:
+ infos: a Python dictionary where each key is a field
+ and each value is your new metadata.
+ """
+ args = {}
+ if isinstance(infos, PdfObject):
+ infos = cast(DictionaryObject, infos.get_object())
+ for key, value in list(infos.items()):
+ if isinstance(value, PdfObject):
+ value = value.get_object()
+ args[NameObject(key)] = create_string_object(str(value))
+ assert isinstance(self._info, DictionaryObject)
+ self._info.update(args)
+
+ def _sweep_indirect_references(
+ self,
+ root: Union[
+ ArrayObject,
+ BooleanObject,
+ DictionaryObject,
+ FloatObject,
+ IndirectObject,
+ NameObject,
+ PdfObject,
+ NumberObject,
+ TextStringObject,
+ NullObject,
+ ],
+ ) -> None:
+ """
+ Resolving any circular references to Page objects.
+
+ Circular references to Page objects can arise when objects such as
+ annotations refer to their associated page. If these references are not
+ properly handled, the PDF file will contain multiple copies of the same
+ Page object. To address this problem, Page objects store their original
+ object reference number. This method adds the reference number of any
+ circularly referenced Page objects to an external reference map. This
+ ensures that self-referencing trees reference the correct new object
+ location, rather than copying in a new copy of the Page object.
+
+ Args:
+ root: The root of the PDF object tree to sweep.
+ """
+ stack: Deque[
+ Tuple[
+ Any,
+ Optional[Any],
+ Any,
+ List[PdfObject],
+ ]
+ ] = collections.deque()
+ discovered = []
+ parent = None
+ grant_parents: List[PdfObject] = []
+ key_or_id = None
+
+ # Start from root
+ stack.append((root, parent, key_or_id, grant_parents))
+
+ while len(stack):
+ data, parent, key_or_id, grant_parents = stack.pop()
+
+ # Build stack for a processing depth-first
+ if isinstance(data, (ArrayObject, DictionaryObject)):
+ for key, value in data.items():
+ stack.append(
+ (
+ value,
+ data,
+ key,
+ grant_parents + [parent] if parent is not None else [],
+ )
+ )
+ elif isinstance(data, IndirectObject) and data.pdf != self:
+ data = self._resolve_indirect_object(data)
+
+ if str(data) not in discovered:
+ discovered.append(str(data))
+ stack.append((data.get_object(), None, None, []))
+
+ # Check if data has a parent and if it is a dict or
+ # an array update the value
+ if isinstance(parent, (DictionaryObject, ArrayObject)):
+ if isinstance(data, StreamObject):
+ # a dictionary value is a stream; streams must be indirect
+ # objects, so we need to change this value.
+ data = self._resolve_indirect_object(self._add_object(data))
+
+ update_hashes = []
+
+ # Data changed and thus the hash value changed
+ if parent[key_or_id] != data:
+ update_hashes = [parent.hash_value()] + [
+ grant_parent.hash_value() for grant_parent in grant_parents
+ ]
+ parent[key_or_id] = data
+
+ # Update old hash value to new hash value
+ for old_hash in update_hashes:
+ indirect_reference = self._idnum_hash.pop(old_hash, None)
+
+ if indirect_reference is not None:
+ indirect_reference_obj = indirect_reference.get_object()
+
+ if indirect_reference_obj is not None:
+ self._idnum_hash[
+ indirect_reference_obj.hash_value()
+ ] = indirect_reference
+
+ def _resolve_indirect_object(self, data: IndirectObject) -> IndirectObject:
+ """
+ Resolves an indirect object to an indirect object in this PDF file.
+
+ If the input indirect object already belongs to this PDF file, it is
+ returned directly. Otherwise, the object is retrieved from the input
+ object's PDF file using the object's ID number and generation number. If
+ the object cannot be found, a warning is logged and a `NullObject` is
+ returned.
+
+ If the object is not already in this PDF file, it is added to the file's
+ list of objects and assigned a new ID number and generation number of 0.
+ The hash value of the object is then added to the `_idnum_hash`
+ dictionary, with the corresponding `IndirectObject` reference as the
+ value.
+
+ Args:
+ data: The `IndirectObject` to resolve.
+
+ Returns:
+ The resolved `IndirectObject` in this PDF file.
+
+ Raises:
+ ValueError: If the input stream is closed.
+ """
+ if hasattr(data.pdf, "stream") and data.pdf.stream.closed:
+ raise ValueError(f"I/O operation on closed file: {data.pdf.stream.name}")
+
+ if data.pdf == self:
+ return data
+
+ # Get real object indirect object
+ real_obj = data.pdf.get_object(data)
+
+ if real_obj is None:
+ logger_warning(
+ f"Unable to resolve [{data.__class__.__name__}: {data}], "
+ "returning NullObject instead",
+ __name__,
+ )
+ real_obj = NullObject()
+
+ hash_value = real_obj.hash_value()
+
+ # Check if object is handled
+ if hash_value in self._idnum_hash:
+ return self._idnum_hash[hash_value]
+
+ if data.pdf == self:
+ self._idnum_hash[hash_value] = IndirectObject(data.idnum, 0, self)
+ # This is new object in this pdf
+ else:
+ self._idnum_hash[hash_value] = self._add_object(real_obj)
+
+ return self._idnum_hash[hash_value]
+
+ def get_reference(self, obj: PdfObject) -> IndirectObject:
+ idnum = self._objects.index(obj) + 1
+ ref = IndirectObject(idnum, 0, self)
+ assert ref.get_object() == obj
+ return ref
+
+ def get_outline_root(self) -> TreeObject:
+ if CO.OUTLINES in self._root_object:
+ # Table 3.25 Entries in the catalog dictionary
+ outline = cast(TreeObject, self._root_object[CO.OUTLINES])
+ if not isinstance(outline, TreeObject):
+ t = TreeObject(outline)
+ self._replace_object(outline.indirect_reference.idnum, t)
+ outline = t
+ idnum = self._objects.index(outline) + 1
+ outline_ref = IndirectObject(idnum, 0, self)
+ assert outline_ref.get_object() == outline
+ else:
+ outline = TreeObject()
+ outline.update({})
+ outline_ref = self._add_object(outline)
+ self._root_object[NameObject(CO.OUTLINES)] = outline_ref
+
+ return outline
+
+ def get_threads_root(self) -> ArrayObject:
+ """
+ The list of threads.
+
+ See §12.4.3 of the PDF 1.7 or PDF 2.0 specification.
+
+ Returns:
+ An array (possibly empty) of Dictionaries with ``/F`` and
+ ``/I`` properties.
+ """
+ if CO.THREADS in self._root_object:
+ # Table 3.25 Entries in the catalog dictionary
+ threads = cast(ArrayObject, self._root_object[CO.THREADS])
+ else:
+ threads = ArrayObject()
+ self._root_object[NameObject(CO.THREADS)] = threads
+ return threads
+
+ @property
+ def threads(self) -> ArrayObject:
+ """
+ Read-only property for the list of threads.
+
+ See §8.3.2 from PDF 1.7 spec.
+
+ Each element is a dictionaries with ``/F`` and ``/I`` keys.
+ """
+ return self.get_threads_root()
+
+ def add_outline_item_destination(
+ self,
+ page_destination: Union[IndirectObject, PageObject, TreeObject],
+ parent: Union[None, TreeObject, IndirectObject] = None,
+ before: Union[None, TreeObject, IndirectObject] = None,
+ is_open: bool = True,
+ ) -> IndirectObject:
+ page_destination = cast(PageObject, page_destination.get_object())
+ if isinstance(page_destination, PageObject):
+ return self.add_outline_item_destination(
+ Destination(
+ f"page #{page_destination.page_number}",
+ cast(IndirectObject, page_destination.indirect_reference),
+ Fit.fit(),
+ )
+ )
+
+ if parent is None:
+ parent = self.get_outline_root()
+
+ page_destination[NameObject("/%is_open%")] = BooleanObject(is_open)
+ parent = cast(TreeObject, parent.get_object())
+ page_destination_ref = self._add_object(page_destination)
+ if before is not None:
+ before = before.indirect_reference
+ parent.insert_child(
+ page_destination_ref,
+ before,
+ self,
+ page_destination.inc_parent_counter_outline
+ if is_open
+ else (lambda x, y: 0),
+ )
+ if "/Count" not in page_destination:
+ page_destination[NameObject("/Count")] = NumberObject(0)
+
+ return page_destination_ref
+
+ def add_outline_item_dict(
+ self,
+ outline_item: OutlineItemType,
+ parent: Union[None, TreeObject, IndirectObject] = None,
+ before: Union[None, TreeObject, IndirectObject] = None,
+ is_open: bool = True,
+ ) -> IndirectObject:
+ outline_item_object = TreeObject()
+ outline_item_object.update(outline_item)
+
+ if "/A" in outline_item:
+ action = DictionaryObject()
+ a_dict = cast(DictionaryObject, outline_item["/A"])
+ for k, v in list(a_dict.items()):
+ action[NameObject(str(k))] = v
+ action_ref = self._add_object(action)
+ outline_item_object[NameObject("/A")] = action_ref
+
+ return self.add_outline_item_destination(
+ outline_item_object, parent, before, is_open
+ )
+
+ def add_outline_item(
+ self,
+ title: str,
+ page_number: Union[None, PageObject, IndirectObject, int],
+ parent: Union[None, TreeObject, IndirectObject] = None,
+ before: Union[None, TreeObject, IndirectObject] = None,
+ color: Optional[Union[Tuple[float, float, float], str]] = None,
+ bold: bool = False,
+ italic: bool = False,
+ fit: Fit = PAGE_FIT,
+ is_open: bool = True,
+ ) -> IndirectObject:
+ """
+ Add an outline item (commonly referred to as a "Bookmark") to the PDF file.
+
+ Args:
+ title: Title to use for this outline item.
+ page_number: Page number this outline item will point to.
+ parent: A reference to a parent outline item to create nested
+ outline items.
+ before:
+ color: Color of the outline item's font as a red, green, blue tuple
+ from 0.0 to 1.0 or as a Hex String (#RRGGBB)
+ bold: Outline item font is bold
+ italic: Outline item font is italic
+ fit: The fit of the destination page.
+
+ Returns:
+ The added outline item as an indirect object.
+ """
+ page_ref: Union[None, NullObject, IndirectObject, NumberObject]
+ if isinstance(italic, Fit): # it means that we are on the old params
+ if fit is not None and page_number is None:
+ page_number = fit # type: ignore
+ return self.add_outline_item(
+ title, page_number, parent, None, before, color, bold, italic, is_open=is_open # type: ignore
+ )
+ if page_number is None:
+ action_ref = None
+ else:
+ if isinstance(page_number, IndirectObject):
+ page_ref = page_number
+ elif isinstance(page_number, PageObject):
+ page_ref = page_number.indirect_reference
+ elif isinstance(page_number, int):
+ try:
+ page_ref = self.pages[page_number].indirect_reference
+ except IndexError:
+ page_ref = NumberObject(page_number)
+ if page_ref is None:
+ logger_warning(
+ f"can not find reference of page {page_number}",
+ __name__,
+ )
+ page_ref = NullObject()
+ dest = Destination(
+ NameObject("/" + title + " outline item"),
+ page_ref,
+ fit,
+ )
+
+ action_ref = self._add_object(
+ DictionaryObject(
+ {
+ NameObject(GoToActionArguments.D): dest.dest_array,
+ NameObject(GoToActionArguments.S): NameObject("/GoTo"),
+ }
+ )
+ )
+ outline_item = self._add_object(
+ _create_outline_item(action_ref, title, color, italic, bold)
+ )
+
+ if parent is None:
+ parent = self.get_outline_root()
+ return self.add_outline_item_destination(outline_item, parent, before, is_open)
+
+ def add_outline(self) -> None:
+ raise NotImplementedError(
+ "This method is not yet implemented. Use :meth:`add_outline_item` instead."
+ )
+
+ def add_named_destination_array(
+ self, title: TextStringObject, destination: Union[IndirectObject, ArrayObject]
+ ) -> None:
+ named_dest = self.get_named_dest_root()
+ i = 0
+ while i < len(named_dest):
+ if title < named_dest[i]:
+ named_dest.insert(i, destination)
+ named_dest.insert(i, TextStringObject(title))
+ return
+ else:
+ i += 2
+ named_dest.extend([TextStringObject(title), destination])
+ return
+
+ def add_named_destination_object(
+ self,
+ page_destination: PdfObject,
+ ) -> IndirectObject:
+ page_destination_ref = self._add_object(page_destination.dest_array) # type: ignore
+ self.add_named_destination_array(
+ cast("TextStringObject", page_destination["/Title"]), page_destination_ref # type: ignore
+ )
+
+ return page_destination_ref
+
+ def add_named_destination(
+ self,
+ title: str,
+ page_number: int,
+ ) -> IndirectObject:
+ page_ref = self.get_object(self._pages)[PA.KIDS][page_number] # type: ignore
+ dest = DictionaryObject()
+ dest.update(
+ {
+ NameObject(GoToActionArguments.D): ArrayObject(
+ [page_ref, NameObject(TypFitArguments.FIT_H), NumberObject(826)]
+ ),
+ NameObject(GoToActionArguments.S): NameObject("/GoTo"),
+ }
+ )
+
+ dest_ref = self._add_object(dest)
+ if not isinstance(title, TextStringObject):
+ title = TextStringObject(str(title))
+
+ self.add_named_destination_array(title, dest_ref)
+ return dest_ref
+
+ def remove_links(self) -> None:
+ """Remove links and annotations from this output."""
+ for page in self.pages:
+ self.remove_objects_from_page(page, ObjectDeletionFlag.ALL_ANNOTATIONS)
+
+ def remove_annotations(
+ self, subtypes: Optional[Union[AnnotationSubtype, Iterable[AnnotationSubtype]]]
+ ) -> None:
+ """
+ Remove annotations by annotation subtype.
+
+ Args:
+ subtypes: subtype or list of subtypes to be removed.
+ Examples are: "/Link", "/FileAttachment", "/Sound",
+ "/Movie", "/Screen", ...
+ If you want to remove all annotations, use subtypes=None.
+ """
+ for page in self.pages:
+ self._remove_annots_from_page(page, subtypes)
+
+ def _remove_annots_from_page(
+ self,
+ page: Union[IndirectObject, PageObject, DictionaryObject],
+ subtypes: Optional[Iterable[str]],
+ ) -> None:
+ page = cast(DictionaryObject, page.get_object())
+ if PG.ANNOTS in page:
+ i = 0
+ while i < len(cast(ArrayObject, page[PG.ANNOTS])):
+ an = cast(ArrayObject, page[PG.ANNOTS])[i]
+ obj = cast(DictionaryObject, an.get_object())
+ if subtypes is None or cast(str, obj["/Subtype"]) in subtypes:
+ if isinstance(an, IndirectObject):
+ self._objects[an.idnum - 1] = NullObject() # to reduce PDF size
+ del page[PG.ANNOTS][i] # type:ignore
+ else:
+ i += 1
+
+ def remove_objects_from_page(
+ self,
+ page: Union[PageObject, DictionaryObject],
+ to_delete: Union[ObjectDeletionFlag, Iterable[ObjectDeletionFlag]],
+ ) -> None:
+ """
+ Remove objects specified by ``to_delete`` from the given page.
+
+ Args:
+ page: Page object to clean up.
+ to_delete: Objects to be deleted; can be a ``ObjectDeletionFlag``
+ or a list of ObjectDeletionFlag
+ """
+ if isinstance(to_delete, (list, tuple)):
+ for to_d in to_delete:
+ self.remove_objects_from_page(page, to_d)
+ return
+ assert isinstance(to_delete, ObjectDeletionFlag)
+
+ if to_delete & ObjectDeletionFlag.LINKS:
+ return self._remove_annots_from_page(page, ("/Link",))
+ if to_delete & ObjectDeletionFlag.ATTACHMENTS:
+ return self._remove_annots_from_page(
+ page, ("/FileAttachment", "/Sound", "/Movie", "/Screen")
+ )
+ if to_delete & ObjectDeletionFlag.OBJECTS_3D:
+ return self._remove_annots_from_page(page, ("/3D",))
+ if to_delete & ObjectDeletionFlag.ALL_ANNOTATIONS:
+ return self._remove_annots_from_page(page, None)
+
+ jump_operators = []
+ if to_delete & ObjectDeletionFlag.DRAWING_IMAGES:
+ jump_operators = (
+ [b"w", b"J", b"j", b"M", b"d", b"i"]
+ + [b"W", b"W*"]
+ + [b"b", b"b*", b"B", b"B*", b"S", b"s", b"f", b"f*", b"F", b"n"]
+ + [b"m", b"l", b"c", b"v", b"y", b"h", b"re"]
+ + [b"sh"]
+ )
+ if to_delete & ObjectDeletionFlag.TEXT:
+ jump_operators = [b"Tj", b"TJ", b"'", b'"']
+
+ def clean(content: ContentStream, images: List[str], forms: List[str]) -> None:
+ nonlocal jump_operators, to_delete
+ i = 0
+ while i < len(content.operations):
+ operands, operator = content.operations[i]
+ if (
+ (
+ operator == b"INLINE IMAGE"
+ and (to_delete & ObjectDeletionFlag.INLINE_IMAGES)
+ )
+ or (operator in jump_operators)
+ or (
+ operator == b"Do"
+ and (to_delete & ObjectDeletionFlag.XOBJECT_IMAGES)
+ and (operands[0] in images)
+ )
+ ):
+ del content.operations[i]
+ else:
+ i += 1
+ content.get_data() # this ensures ._data is rebuilt from the .operations
+
+ def clean_forms(
+ elt: DictionaryObject, stack: List[DictionaryObject]
+ ) -> Tuple[List[str], List[str]]:
+ nonlocal to_delete
+ # elt in recursive call is a new ContentStream object, so we have to check the indirect_reference
+ if (elt in stack) or (
+ hasattr(elt, "indirect_reference")
+ and any(
+ elt.indirect_reference == getattr(x, "indirect_reference", -1)
+ for x in stack
+ )
+ ):
+ # to prevent infinite looping
+ return [], [] # pragma: no cover
+ try:
+ d = cast(
+ Dict[Any, Any],
+ cast(DictionaryObject, elt["/Resources"])["/XObject"],
+ )
+ except KeyError:
+ d = {}
+ images = []
+ forms = []
+ for k, v in d.items():
+ o = v.get_object()
+ try:
+ content: Any = None
+ if (
+ to_delete & ObjectDeletionFlag.XOBJECT_IMAGES
+ and o["/Subtype"] == "/Image"
+ ):
+ content = NullObject() # to delete the image keeping the entry
+ images.append(k)
+ if o["/Subtype"] == "/Form":
+ forms.append(k)
+ if isinstance(o, ContentStream):
+ content = o
+ else:
+ content = ContentStream(o, self)
+ content.update(
+ {
+ k1: v1
+ for k1, v1 in o.items()
+ if k1 not in ["/Length", "/Filter", "/DecodeParms"]
+ }
+ )
+ try:
+ content.indirect_reference = o.indirect_reference
+ except AttributeError: # pragma: no cover
+ pass
+ stack.append(elt)
+ clean_forms(content, stack) # clean subforms
+ if content is not None:
+ if isinstance(v, IndirectObject):
+ self._objects[v.idnum - 1] = content
+ else:
+ # should only occur with pdf not respecting pdf spec
+ # where streams must be indirected.
+ d[k] = self._add_object(content) # pragma: no cover
+ except (TypeError, KeyError):
+ pass
+ for im in images:
+ del d[im] # for clean-up
+ if isinstance(elt, StreamObject): # for /Form
+ if not isinstance(elt, ContentStream): # pragma: no cover
+ e = ContentStream(elt, self)
+ e.update(elt.items())
+ elt = e
+ clean(elt, images, forms) # clean the content
+ return images, forms
+
+ if not isinstance(page, PageObject):
+ page = PageObject(self, page.indirect_reference) # pragma: no cover
+ if "/Contents" in page:
+ content = cast(ContentStream, page.get_contents())
+
+ images, forms = clean_forms(page, [])
+
+ clean(content, images, forms)
+ page.replace_contents(content)
+
+ def remove_images(
+ self,
+ to_delete: ImageType = ImageType.ALL,
+ ) -> None:
+ """
+ Remove images from this output.
+
+ Args:
+ to_delete : The type of images to be deleted
+ (default = all images types)
+ """
+ if isinstance(to_delete, bool):
+ to_delete = ImageType.ALL
+ i = (
+ (
+ ObjectDeletionFlag.XOBJECT_IMAGES
+ if to_delete & ImageType.XOBJECT_IMAGES
+ else ObjectDeletionFlag.NONE
+ )
+ | (
+ ObjectDeletionFlag.INLINE_IMAGES
+ if to_delete & ImageType.INLINE_IMAGES
+ else ObjectDeletionFlag.NONE
+ )
+ | (
+ ObjectDeletionFlag.DRAWING_IMAGES
+ if to_delete & ImageType.DRAWING_IMAGES
+ else ObjectDeletionFlag.NONE
+ )
+ )
+ for page in self.pages:
+ self.remove_objects_from_page(page, i)
+
+ def remove_text(self) -> None:
+ """Remove text from this output."""
+ for page in self.pages:
+ self.remove_objects_from_page(page, ObjectDeletionFlag.TEXT)
+
+ def add_uri(
+ self,
+ page_number: int,
+ uri: str,
+ rect: RectangleObject,
+ border: Optional[ArrayObject] = None,
+ ) -> None:
+ """
+ Add an URI from a rectangular area to the specified page.
+
+ Args:
+ page_number: index of the page on which to place the URI action.
+ uri: URI of resource to link to.
+ rect: :class:`RectangleObject<pypdf.generic.RectangleObject>` or
+ array of four integers specifying the clickable rectangular area
+ ``[xLL, yLL, xUR, yUR]``, or string in the form
+ ``"[ xLL yLL xUR yUR ]"``.
+ border: if provided, an array describing border-drawing
+ properties. See the PDF spec for details. No border will be
+ drawn if this argument is omitted.
+ """
+ page_link = self.get_object(self._pages)[PA.KIDS][page_number] # type: ignore
+ page_ref = cast(Dict[str, Any], self.get_object(page_link))
+
+ border_arr: BorderArrayType
+ if border is not None:
+ border_arr = [NumberObject(n) for n in border[:3]]
+ if len(border) == 4:
+ dash_pattern = ArrayObject([NumberObject(n) for n in border[3]])
+ border_arr.append(dash_pattern)
+ else:
+ border_arr = [NumberObject(2), NumberObject(2), NumberObject(2)]
+
+ if isinstance(rect, str):
+ rect = NumberObject(rect)
+ elif isinstance(rect, RectangleObject):
+ pass
+ else:
+ rect = RectangleObject(rect)
+
+ lnk2 = DictionaryObject()
+ lnk2.update(
+ {
+ NameObject("/S"): NameObject("/URI"),
+ NameObject("/URI"): TextStringObject(uri),
+ }
+ )
+ lnk = DictionaryObject()
+ lnk.update(
+ {
+ NameObject(AA.Type): NameObject("/Annot"),
+ NameObject(AA.Subtype): NameObject("/Link"),
+ NameObject(AA.P): page_link,
+ NameObject(AA.Rect): rect,
+ NameObject("/H"): NameObject("/I"),
+ NameObject(AA.Border): ArrayObject(border_arr),
+ NameObject("/A"): lnk2,
+ }
+ )
+ lnk_ref = self._add_object(lnk)
+
+ if PG.ANNOTS in page_ref:
+ page_ref[PG.ANNOTS].append(lnk_ref)
+ else:
+ page_ref[NameObject(PG.ANNOTS)] = ArrayObject([lnk_ref])
+
+ _valid_layouts = (
+ "/NoLayout",
+ "/SinglePage",
+ "/OneColumn",
+ "/TwoColumnLeft",
+ "/TwoColumnRight",
+ "/TwoPageLeft",
+ "/TwoPageRight",
+ )
+
+ def _get_page_layout(self) -> Optional[LayoutType]:
+ try:
+ return cast(LayoutType, self._root_object["/PageLayout"])
+ except KeyError:
+ return None
+
+ def _set_page_layout(self, layout: Union[NameObject, LayoutType]) -> None:
+ """
+ Set the page layout.
+
+ Args:
+ layout: The page layout to be used.
+
+ .. list-table:: Valid ``layout`` arguments
+ :widths: 50 200
+
+ * - /NoLayout
+ - Layout explicitly not specified
+ * - /SinglePage
+ - Show one page at a time
+ * - /OneColumn
+ - Show one column at a time
+ * - /TwoColumnLeft
+ - Show pages in two columns, odd-numbered pages on the left
+ * - /TwoColumnRight
+ - Show pages in two columns, odd-numbered pages on the right
+ * - /TwoPageLeft
+ - Show two pages at a time, odd-numbered pages on the left
+ * - /TwoPageRight
+ - Show two pages at a time, odd-numbered pages on the right
+ """
+ if not isinstance(layout, NameObject):
+ if layout not in self._valid_layouts:
+ logger_warning(
+ f"Layout should be one of: {'', ''.join(self._valid_layouts)}",
+ __name__,
+ )
+ layout = NameObject(layout)
+ self._root_object.update({NameObject("/PageLayout"): layout})
+
+ def set_page_layout(self, layout: LayoutType) -> None:
+ """
+ Set the page layout.
+
+ Args:
+ layout: The page layout to be used
+
+ .. list-table:: Valid ``layout`` arguments
+ :widths: 50 200
+
+ * - /NoLayout
+ - Layout explicitly not specified
+ * - /SinglePage
+ - Show one page at a time
+ * - /OneColumn
+ - Show one column at a time
+ * - /TwoColumnLeft
+ - Show pages in two columns, odd-numbered pages on the left
+ * - /TwoColumnRight
+ - Show pages in two columns, odd-numbered pages on the right
+ * - /TwoPageLeft
+ - Show two pages at a time, odd-numbered pages on the left
+ * - /TwoPageRight
+ - Show two pages at a time, odd-numbered pages on the right
+ """
+ self._set_page_layout(layout)
+
+ @property
+ def page_layout(self) -> Optional[LayoutType]:
+ """
+ Page layout property.
+
+ .. list-table:: Valid ``layout`` values
+ :widths: 50 200
+
+ * - /NoLayout
+ - Layout explicitly not specified
+ * - /SinglePage
+ - Show one page at a time
+ * - /OneColumn
+ - Show one column at a time
+ * - /TwoColumnLeft
+ - Show pages in two columns, odd-numbered pages on the left
+ * - /TwoColumnRight
+ - Show pages in two columns, odd-numbered pages on the right
+ * - /TwoPageLeft
+ - Show two pages at a time, odd-numbered pages on the left
+ * - /TwoPageRight
+ - Show two pages at a time, odd-numbered pages on the right
+ """
+ return self._get_page_layout()
+
+ @page_layout.setter
+ def page_layout(self, layout: LayoutType) -> None:
+ self._set_page_layout(layout)
+
+ _valid_modes = (
+ "/UseNone",
+ "/UseOutlines",
+ "/UseThumbs",
+ "/FullScreen",
+ "/UseOC",
+ "/UseAttachments",
+ )
+
+ def _get_page_mode(self) -> Optional[PagemodeType]:
+ try:
+ return cast(PagemodeType, self._root_object["/PageMode"])
+ except KeyError:
+ return None
+
+ @property
+ def page_mode(self) -> Optional[PagemodeType]:
+ """
+ Page mode property.
+
+ .. list-table:: Valid ``mode`` values
+ :widths: 50 200
+
+ * - /UseNone
+ - Do not show outline or thumbnails panels
+ * - /UseOutlines
+ - Show outline (aka bookmarks) panel
+ * - /UseThumbs
+ - Show page thumbnails panel
+ * - /FullScreen
+ - Fullscreen view
+ * - /UseOC
+ - Show Optional Content Group (OCG) panel
+ * - /UseAttachments
+ - Show attachments panel
+ """
+ return self._get_page_mode()
+
+ @page_mode.setter
+ def page_mode(self, mode: PagemodeType) -> None:
+ if isinstance(mode, NameObject):
+ mode_name: NameObject = mode
+ else:
+ if mode not in self._valid_modes:
+ logger_warning(
+ f"Mode should be one of: {', '.join(self._valid_modes)}", __name__
+ )
+ mode_name = NameObject(mode)
+ self._root_object.update({NameObject("/PageMode"): mode_name})
+
+ def add_annotation(
+ self,
+ page_number: Union[int, PageObject],
+ annotation: Dict[str, Any],
+ ) -> DictionaryObject:
+ """
+ Add a single annotation to the page.
+ The added annotation must be a new annotation.
+ It cannot be recycled.
+
+ Args:
+ page_number: PageObject or page index.
+ annotation: Annotation to be added (created with annotation).
+
+ Returns:
+ The inserted object.
+ This can be used for popup creation, for example.
+ """
+ page = page_number
+ if isinstance(page, int):
+ page = self.pages[page]
+ elif not isinstance(page, PageObject):
+ raise TypeError("page: invalid type")
+
+ to_add = cast(DictionaryObject, _pdf_objectify(annotation))
+ to_add[NameObject("/P")] = page.indirect_reference
+
+ if page.annotations is None:
+ page[NameObject("/Annots")] = ArrayObject()
+ assert page.annotations is not None
+
+ # Internal link annotations need the correct object type for the
+ # destination
+ if to_add.get("/Subtype") == "/Link" and "/Dest" in to_add:
+ tmp = cast(Dict[Any, Any], to_add[NameObject("/Dest")])
+ dest = Destination(
+ NameObject("/LinkName"),
+ tmp["target_page_index"],
+ Fit(
+ fit_type=tmp["fit"], fit_args=dict(tmp)["fit_args"]
+ ), # I have no clue why this dict-hack is necessary
+ )
+ to_add[NameObject("/Dest")] = dest.dest_array
+
+ page.annotations.append(self._add_object(to_add))
+
+ if to_add.get("/Subtype") == "/Popup" and NameObject("/Parent") in to_add:
+ cast(DictionaryObject, to_add["/Parent"].get_object())[
+ NameObject("/Popup")
+ ] = to_add.indirect_reference
+
+ return to_add
+
+ def clean_page(self, page: Union[PageObject, IndirectObject]) -> PageObject:
+ """
+ Perform some clean up in the page.
+ Currently: convert NameObject named destination to TextStringObject
+ (required for names/dests list)
+
+ Args:
+ page:
+
+ Returns:
+ The cleaned PageObject
+ """
+ page = cast("PageObject", page.get_object())
+ for a in page.get("/Annots", []):
+ a_obj = a.get_object()
+ d = a_obj.get("/Dest", None)
+ act = a_obj.get("/A", None)
+ if isinstance(d, NameObject):
+ a_obj[NameObject("/Dest")] = TextStringObject(d)
+ elif act is not None:
+ act = act.get_object()
+ d = act.get("/D", None)
+ if isinstance(d, NameObject):
+ act[NameObject("/D")] = TextStringObject(d)
+ return page
+
+ def _create_stream(
+ self, fileobj: Union[Path, StrByteType, PdfReader]
+ ) -> Tuple[IOBase, Optional[Encryption]]:
+ # If the fileobj parameter is a string, assume it is a path
+ # and create a file object at that location. If it is a file,
+ # copy the file's contents into a BytesIO stream object; if
+ # it is a PdfReader, copy that reader's stream into a
+ # BytesIO stream.
+ # If fileobj is none of the above types, it is not modified
+ encryption_obj = None
+ stream: IOBase
+ if isinstance(fileobj, (str, Path)):
+ with FileIO(fileobj, "rb") as f:
+ stream = BytesIO(f.read())
+ elif isinstance(fileobj, PdfReader):
+ if fileobj._encryption:
+ encryption_obj = fileobj._encryption
+ orig_tell = fileobj.stream.tell()
+ fileobj.stream.seek(0)
+ stream = BytesIO(fileobj.stream.read())
+
+ # reset the stream to its original location
+ fileobj.stream.seek(orig_tell)
+ elif hasattr(fileobj, "seek") and hasattr(fileobj, "read"):
+ fileobj.seek(0)
+ filecontent = fileobj.read()
+ stream = BytesIO(filecontent)
+ else:
+ raise NotImplementedError(
+ "PdfMerger.merge requires an object that PdfReader can parse. "
+ "Typically, that is a Path or a string representing a Path, "
+ "a file object, or an object implementing .seek and .read. "
+ "Passing a PdfReader directly works as well."
+ )
+ return stream, encryption_obj
+
+ def append(
+ self,
+ fileobj: Union[StrByteType, PdfReader, Path],
+ outline_item: Union[
+ str, None, PageRange, Tuple[int, int], Tuple[int, int, int], List[int]
+ ] = None,
+ pages: Union[
+ None,
+ PageRange,
+ Tuple[int, int],
+ Tuple[int, int, int],
+ List[int],
+ List[PageObject],
+ ] = None,
+ import_outline: bool = True,
+ excluded_fields: Optional[Union[List[str], Tuple[str, ...]]] = None,
+ ) -> None:
+ """
+ Identical to the :meth:`merge()<merge>` method, but assumes you want to
+ concatenate all pages onto the end of the file instead of specifying a
+ position.
+
+ Args:
+ fileobj: A File Object or an object that supports the standard
+ read and seek methods similar to a File Object. Could also be a
+ string representing a path to a PDF file.
+ outline_item: Optionally, you may specify a string to build an
+ outline (aka 'bookmark') to identify the beginning of the
+ included file.
+ pages: Can be a :class:`PageRange<pypdf.pagerange.PageRange>`
+ or a ``(start, stop[, step])`` tuple
+ or a list of pages to be processed
+ to merge only the specified range of pages from the source
+ document into the output document.
+ import_outline: You may prevent the source document's
+ outline (collection of outline items, previously referred to as
+ 'bookmarks') from being imported by specifying this as ``False``.
+ excluded_fields: Provide the list of fields/keys to be ignored
+ if ``/Annots`` is part of the list, the annotation will be ignored
+ if ``/B`` is part of the list, the articles will be ignored
+ """
+ if excluded_fields is None:
+ excluded_fields = ()
+ if isinstance(outline_item, (tuple, list, PageRange)):
+ if isinstance(pages, bool):
+ if not isinstance(import_outline, bool):
+ excluded_fields = import_outline
+ import_outline = pages
+ pages = outline_item
+ self.merge(
+ None,
+ fileobj,
+ None,
+ pages,
+ import_outline,
+ excluded_fields,
+ )
+ else: # if isinstance(outline_item,str):
+ self.merge(
+ None,
+ fileobj,
+ outline_item,
+ pages,
+ import_outline,
+ excluded_fields,
+ )
+
+ def merge(
+ self,
+ position: Optional[int],
+ fileobj: Union[Path, StrByteType, PdfReader],
+ outline_item: Optional[str] = None,
+ pages: Optional[Union[PageRangeSpec, List[PageObject]]] = None,
+ import_outline: bool = True,
+ excluded_fields: Optional[Union[List[str], Tuple[str, ...]]] = (),
+ ) -> None:
+ """
+ Merge the pages from the given file into the output file at the
+ specified page number.
+
+ Args:
+ position: The *page number* to insert this file. File will
+ be inserted after the given number.
+ fileobj: A File Object or an object that supports the standard
+ read and seek methods similar to a File Object. Could also be a
+ string representing a path to a PDF file.
+ outline_item: Optionally, you may specify a string to build an outline
+ (aka 'bookmark') to identify the
+ beginning of the included file.
+ pages: can be a :class:`PageRange<pypdf.pagerange.PageRange>`
+ or a ``(start, stop[, step])`` tuple
+ or a list of pages to be processed
+ to merge only the specified range of pages from the source
+ document into the output document.
+ import_outline: You may prevent the source document's
+ outline (collection of outline items, previously referred to as
+ 'bookmarks') from being imported by specifying this as ``False``.
+ excluded_fields: provide the list of fields/keys to be ignored
+ if ``/Annots`` is part of the list, the annotation will be ignored
+ if ``/B`` is part of the list, the articles will be ignored
+
+ Raises:
+ TypeError: The pages attribute is not configured properly
+ """
+ if isinstance(fileobj, PdfDocCommon):
+ reader = fileobj
+ else:
+ stream, encryption_obj = self._create_stream(fileobj)
+ # Create a new PdfReader instance using the stream
+ # (either file or BytesIO or StringIO) created above
+ reader = PdfReader(stream, strict=False) # type: ignore[arg-type]
+
+ if excluded_fields is None:
+ excluded_fields = ()
+ # Find the range of pages to merge.
+ if pages is None:
+ pages = list(range(len(reader.pages)))
+ elif isinstance(pages, PageRange):
+ pages = list(range(*pages.indices(len(reader.pages))))
+ elif isinstance(pages, list):
+ pass # keep unchanged
+ elif isinstance(pages, tuple) and len(pages) <= 3:
+ pages = list(range(*pages))
+ elif not isinstance(pages, tuple):
+ raise TypeError(
+ '"pages" must be a tuple of (start, stop[, step]) or a list'
+ )
+
+ srcpages = {}
+ for page in pages:
+ if isinstance(page, PageObject):
+ pg = page
+ else:
+ pg = reader.pages[page]
+ assert pg.indirect_reference is not None
+ if position is None:
+ # numbers in the exclude list identifies that the exclusion is
+ # only applicable to 1st level of cloning
+ srcpages[pg.indirect_reference.idnum] = self.add_page(
+ pg, list(excluded_fields) + [1, "/B", 1, "/Annots"] # type: ignore
+ )
+ else:
+ srcpages[pg.indirect_reference.idnum] = self.insert_page(
+ pg, position, list(excluded_fields) + [1, "/B", 1, "/Annots"] # type: ignore
+ )
+ position += 1
+ srcpages[pg.indirect_reference.idnum].original_page = pg
+
+ reader._namedDests = (
+ reader.named_destinations
+ ) # need for the outline processing below
+ for dest in reader._namedDests.values():
+ arr = dest.dest_array
+ if "/Names" in self._root_object and dest["/Title"] in cast(
+ List[Any],
+ cast(
+ DictionaryObject,
+ cast(DictionaryObject, self._root_object["/Names"])["/Dests"],
+ )["/Names"],
+ ):
+ # already exists : should not duplicate it
+ pass
+ elif isinstance(dest["/Page"], NullObject):
+ pass
+ elif isinstance(dest["/Page"], int):
+ # the page reference is a page number normally not a PDF Reference
+ # page numbers as int are normally accepted only in external goto
+ p = reader.pages[dest["/Page"]]
+ assert p.indirect_reference is not None
+ try:
+ arr[NumberObject(0)] = NumberObject(
+ srcpages[p.indirect_reference.idnum].page_number
+ )
+ self.add_named_destination_array(dest["/Title"], arr)
+ except KeyError:
+ pass
+ elif dest["/Page"].indirect_reference.idnum in srcpages:
+ arr[NumberObject(0)] = srcpages[
+ dest["/Page"].indirect_reference.idnum
+ ].indirect_reference
+ self.add_named_destination_array(dest["/Title"], arr)
+
+ outline_item_typ: TreeObject
+ if outline_item is not None:
+ outline_item_typ = cast(
+ "TreeObject",
+ self.add_outline_item(
+ TextStringObject(outline_item),
+ next(iter(srcpages.values())).indirect_reference,
+ fit=PAGE_FIT,
+ ).get_object(),
+ )
+ else:
+ outline_item_typ = self.get_outline_root()
+
+ _ro = reader.root_object
+ if import_outline and CO.OUTLINES in _ro:
+ outline = self._get_filtered_outline(
+ _ro.get(CO.OUTLINES, None), srcpages, reader
+ )
+ self._insert_filtered_outline(
+ outline, outline_item_typ, None
+ ) # TODO : use before parameter
+
+ if "/Annots" not in excluded_fields:
+ for pag in srcpages.values():
+ lst = self._insert_filtered_annotations(
+ pag.original_page.get("/Annots", ()), pag, srcpages, reader
+ )
+ if len(lst) > 0:
+ pag[NameObject("/Annots")] = lst
+ self.clean_page(pag)
+
+ if "/AcroForm" in _ro and _ro["/AcroForm"] is not None:
+ if "/AcroForm" not in self._root_object:
+ self._root_object[NameObject("/AcroForm")] = self._add_object(
+ cast(
+ DictionaryObject,
+ reader.root_object["/AcroForm"],
+ ).clone(self, False, ("/Fields",))
+ )
+ arr = ArrayObject()
+ else:
+ arr = cast(
+ ArrayObject,
+ cast(DictionaryObject, self._root_object["/AcroForm"])["/Fields"],
+ )
+ trslat = self._id_translated[id(reader)]
+ try:
+ for f in reader.root_object["/AcroForm"]["/Fields"]: # type: ignore
+ try:
+ ind = IndirectObject(trslat[f.idnum], 0, self)
+ if ind not in arr:
+ arr.append(ind)
+ except KeyError:
+ # for trslat[] which mean the field has not be copied
+ # through the page
+ pass
+ except KeyError: # for /Acroform or /Fields are not existing
+ arr = self._add_object(ArrayObject())
+ cast(DictionaryObject, self._root_object["/AcroForm"])[
+ NameObject("/Fields")
+ ] = arr
+
+ if "/B" not in excluded_fields:
+ self.add_filtered_articles("", srcpages, reader)
+
+ def _add_articles_thread(
+ self,
+ thread: DictionaryObject, # thread entry from the reader's array of threads
+ pages: Dict[int, PageObject],
+ reader: PdfReader,
+ ) -> IndirectObject:
+ """
+ Clone the thread with only the applicable articles.
+
+ Args:
+ thread:
+ pages:
+ reader:
+
+ Returns:
+ The added thread as an indirect reference
+ """
+ nthread = thread.clone(
+ self, force_duplicate=True, ignore_fields=("/F",)
+ ) # use of clone to keep link between reader and writer
+ self.threads.append(nthread.indirect_reference)
+ first_article = cast("DictionaryObject", thread["/F"])
+ current_article: Optional[DictionaryObject] = first_article
+ new_article: Optional[DictionaryObject] = None
+ while current_article is not None:
+ pag = self._get_cloned_page(
+ cast("PageObject", current_article["/P"]), pages, reader
+ )
+ if pag is not None:
+ if new_article is None:
+ new_article = cast(
+ "DictionaryObject",
+ self._add_object(DictionaryObject()).get_object(),
+ )
+ new_first = new_article
+ nthread[NameObject("/F")] = new_article.indirect_reference
+ else:
+ new_article2 = cast(
+ "DictionaryObject",
+ self._add_object(
+ DictionaryObject(
+ {NameObject("/V"): new_article.indirect_reference}
+ )
+ ).get_object(),
+ )
+ new_article[NameObject("/N")] = new_article2.indirect_reference
+ new_article = new_article2
+ new_article[NameObject("/P")] = pag
+ new_article[NameObject("/T")] = nthread.indirect_reference
+ new_article[NameObject("/R")] = current_article["/R"]
+ pag_obj = cast("PageObject", pag.get_object())
+ if "/B" not in pag_obj:
+ pag_obj[NameObject("/B")] = ArrayObject()
+ cast("ArrayObject", pag_obj["/B"]).append(
+ new_article.indirect_reference
+ )
+ current_article = cast("DictionaryObject", current_article["/N"])
+ if current_article == first_article:
+ new_article[NameObject("/N")] = new_first.indirect_reference # type: ignore
+ new_first[NameObject("/V")] = new_article.indirect_reference # type: ignore
+ current_article = None
+ assert nthread.indirect_reference is not None
+ return nthread.indirect_reference
+
+ def add_filtered_articles(
+ self,
+ fltr: Union[
+ Pattern[Any], str
+ ], # thread entry from the reader's array of threads
+ pages: Dict[int, PageObject],
+ reader: PdfReader,
+ ) -> None:
+ """
+ Add articles matching the defined criteria.
+
+ Args:
+ fltr:
+ pages:
+ reader:
+ """
+ if isinstance(fltr, str):
+ fltr = re.compile(fltr)
+ elif not isinstance(fltr, Pattern):
+ fltr = re.compile("")
+ for p in pages.values():
+ pp = p.original_page
+ for a in pp.get("/B", ()):
+ thr = a.get_object().get("/T")
+ if thr is None:
+ continue
+ else:
+ thr = thr.get_object()
+ if thr.indirect_reference.idnum not in self._id_translated[
+ id(reader)
+ ] and fltr.search((thr["/I"] if "/I" in thr else {}).get("/Title", "")):
+ self._add_articles_thread(thr, pages, reader)
+
+ def _get_cloned_page(
+ self,
+ page: Union[None, int, IndirectObject, PageObject, NullObject],
+ pages: Dict[int, PageObject],
+ reader: PdfReader,
+ ) -> Optional[IndirectObject]:
+ if isinstance(page, NullObject):
+ return None
+ if isinstance(page, int):
+ _i = reader.pages[page].indirect_reference
+ elif isinstance(page, DictionaryObject) and page.get("/Type", "") == "/Page":
+ _i = page.indirect_reference
+ elif isinstance(page, IndirectObject):
+ _i = page
+ try:
+ return pages[_i.idnum].indirect_reference # type: ignore
+ except Exception:
+ return None
+
+ def _insert_filtered_annotations(
+ self,
+ annots: Union[IndirectObject, List[DictionaryObject]],
+ page: PageObject,
+ pages: Dict[int, PageObject],
+ reader: PdfReader,
+ ) -> List[Destination]:
+ outlist = ArrayObject()
+ if isinstance(annots, IndirectObject):
+ annots = cast("List[Any]", annots.get_object())
+ for an in annots:
+ ano = cast("DictionaryObject", an.get_object())
+ if (
+ ano["/Subtype"] != "/Link"
+ or "/A" not in ano
+ or cast("DictionaryObject", ano["/A"])["/S"] != "/GoTo"
+ or "/Dest" in ano
+ ):
+ if "/Dest" not in ano:
+ outlist.append(self._add_object(ano.clone(self)))
+ else:
+ d = ano["/Dest"]
+ if isinstance(d, str):
+ # it is a named dest
+ if str(d) in self.get_named_dest_root():
+ outlist.append(ano.clone(self).indirect_reference)
+ else:
+ d = cast("ArrayObject", d)
+ p = self._get_cloned_page(d[0], pages, reader)
+ if p is not None:
+ anc = ano.clone(self, ignore_fields=("/Dest",))
+ anc[NameObject("/Dest")] = ArrayObject([p] + d[1:])
+ outlist.append(self._add_object(anc))
+ else:
+ d = cast("DictionaryObject", ano["/A"])["/D"]
+ if isinstance(d, str):
+ # it is a named dest
+ if str(d) in self.get_named_dest_root():
+ outlist.append(ano.clone(self).indirect_reference)
+ else:
+ d = cast("ArrayObject", d)
+ p = self._get_cloned_page(d[0], pages, reader)
+ if p is not None:
+ anc = ano.clone(self, ignore_fields=("/D",))
+ cast("DictionaryObject", anc["/A"])[
+ NameObject("/D")
+ ] = ArrayObject([p] + d[1:])
+ outlist.append(self._add_object(anc))
+ return outlist
+
+ def _get_filtered_outline(
+ self,
+ node: Any,
+ pages: Dict[int, PageObject],
+ reader: PdfReader,
+ ) -> List[Destination]:
+ """
+ Extract outline item entries that are part of the specified page set.
+
+ Args:
+ node:
+ pages:
+ reader:
+
+ Returns:
+ A list of destination objects.
+ """
+ new_outline = []
+ if node is None:
+ node = NullObject()
+ node = node.get_object()
+ if node is None or isinstance(node, NullObject):
+ node = DictionaryObject()
+ if node.get("/Type", "") == "/Outlines" or "/Title" not in node:
+ node = node.get("/First", None)
+ if node is not None:
+ node = node.get_object()
+ new_outline += self._get_filtered_outline(node, pages, reader)
+ else:
+ v: Union[None, IndirectObject, NullObject]
+ while node is not None:
+ node = node.get_object()
+ o = cast("Destination", reader._build_outline_item(node))
+ v = self._get_cloned_page(cast("PageObject", o["/Page"]), pages, reader)
+ if v is None:
+ v = NullObject()
+ o[NameObject("/Page")] = v
+ if "/First" in node:
+ o._filtered_children = self._get_filtered_outline(
+ node["/First"], pages, reader
+ )
+ else:
+ o._filtered_children = []
+ if (
+ not isinstance(o["/Page"], NullObject)
+ or len(o._filtered_children) > 0
+ ):
+ new_outline.append(o)
+ node = node.get("/Next", None)
+ return new_outline
+
+ def _clone_outline(self, dest: Destination) -> TreeObject:
+ n_ol = TreeObject()
+ self._add_object(n_ol)
+ n_ol[NameObject("/Title")] = TextStringObject(dest["/Title"])
+ if not isinstance(dest["/Page"], NullObject):
+ if dest.node is not None and "/A" in dest.node:
+ n_ol[NameObject("/A")] = dest.node["/A"].clone(self)
+ else:
+ n_ol[NameObject("/Dest")] = dest.dest_array
+ # TODO: /SE
+ if dest.node is not None:
+ n_ol[NameObject("/F")] = NumberObject(dest.node.get("/F", 0))
+ n_ol[NameObject("/C")] = ArrayObject(
+ dest.node.get(
+ "/C", [FloatObject(0.0), FloatObject(0.0), FloatObject(0.0)]
+ )
+ )
+ return n_ol
+
+ def _insert_filtered_outline(
+ self,
+ outlines: List[Destination],
+ parent: Union[TreeObject, IndirectObject],
+ before: Union[None, TreeObject, IndirectObject] = None,
+ ) -> None:
+ for dest in outlines:
+ # TODO : can be improved to keep A and SE entries (ignored for the moment)
+ # with np=self.add_outline_item_destination(dest,parent,before)
+ if dest.get("/Type", "") == "/Outlines" or "/Title" not in dest:
+ np = parent
+ else:
+ np = self._clone_outline(dest)
+ cast(TreeObject, parent.get_object()).insert_child(np, before, self)
+ self._insert_filtered_outline(dest._filtered_children, np, None)
+
+ def close(self) -> None:
+ """Implemented for API harmonization."""
+ return
+
+ def find_outline_item(
+ self,
+ outline_item: Dict[str, Any],
+ root: Optional[OutlineType] = None,
+ ) -> Optional[List[int]]:
+ if root is None:
+ o = self.get_outline_root()
+ else:
+ o = cast("TreeObject", root)
+
+ i = 0
+ while o is not None:
+ if (
+ o.indirect_reference == outline_item
+ or o.get("/Title", None) == outline_item
+ ):
+ return [i]
+ elif "/First" in o:
+ res = self.find_outline_item(
+ outline_item, cast(OutlineType, o["/First"])
+ )
+ if res:
+ return ([i] if "/Title" in o else []) + res
+ if "/Next" in o:
+ i += 1
+ o = cast(TreeObject, o["/Next"])
+ else:
+ return None
+
+ def find_bookmark(
+ self,
+ outline_item: Dict[str, Any],
+ root: Optional[OutlineType] = None,
+ ) -> Optional[List[int]]: # deprecated
+ """
+ .. deprecated:: 2.9.0
+ Use :meth:`find_outline_item` instead.
+ """
+ deprecate_with_replacement("find_bookmark", "find_outline_item", "5.0.0")
+ return self.find_outline_item(outline_item, root)
+
+ def reset_translation(
+ self, reader: Union[None, PdfReader, IndirectObject] = None
+ ) -> None:
+ """
+ Reset the translation table between reader and the writer object.
+
+ Late cloning will create new independent objects.
+
+ Args:
+ reader: PdfReader or IndirectObject referencing a PdfReader object.
+ if set to None or omitted, all tables will be reset.
+ """
+ if reader is None:
+ self._id_translated = {}
+ elif isinstance(reader, PdfReader):
+ try:
+ del self._id_translated[id(reader)]
+ except Exception:
+ pass
+ elif isinstance(reader, IndirectObject):
+ try:
+ del self._id_translated[id(reader.pdf)]
+ except Exception:
+ pass
+ else:
+ raise Exception("invalid parameter {reader}")
+
+ def set_page_label(
+ self,
+ page_index_from: int,
+ page_index_to: int,
+ style: Optional[PageLabelStyle] = None,
+ prefix: Optional[str] = None,
+ start: Optional[int] = 0,
+ ) -> None:
+ """
+ Set a page label to a range of pages.
+
+ Page indexes must be given starting from 0.
+ Labels must have a style, a prefix or both.
+ If to a range is not assigned any page label a decimal label starting from 1 is applied.
+
+ Args:
+ page_index_from: page index of the beginning of the range starting from 0
+ page_index_to: page index of the beginning of the range starting from 0
+ style: The numbering style to be used for the numeric portion of each page label:
+
+ * ``/D`` Decimal arabic numerals
+ * ``/R`` Uppercase roman numerals
+ * ``/r`` Lowercase roman numerals
+ * ``/A`` Uppercase letters (A to Z for the first 26 pages,
+ AA to ZZ for the next 26, and so on)
+ * ``/a`` Lowercase letters (a to z for the first 26 pages,
+ aa to zz for the next 26, and so on)
+
+ prefix: The label prefix for page labels in this range.
+ start: The value of the numeric portion for the first page label
+ in the range.
+ Subsequent pages are numbered sequentially from this value,
+ which must be greater than or equal to 1.
+ Default value: 1.
+ """
+ if style is None and prefix is None:
+ raise ValueError("at least one between style and prefix must be given")
+ if page_index_from < 0:
+ raise ValueError("page_index_from must be equal or greater then 0")
+ if page_index_to < page_index_from:
+ raise ValueError(
+ "page_index_to must be equal or greater then page_index_from"
+ )
+ if page_index_to >= len(self.pages):
+ raise ValueError("page_index_to exceeds number of pages")
+ if start is not None and start != 0 and start < 1:
+ raise ValueError("if given, start must be equal or greater than one")
+
+ self._set_page_label(page_index_from, page_index_to, style, prefix, start)
+
+ def _set_page_label(
+ self,
+ page_index_from: int,
+ page_index_to: int,
+ style: Optional[PageLabelStyle] = None,
+ prefix: Optional[str] = None,
+ start: Optional[int] = 0,
+ ) -> None:
+ """
+ Set a page label to a range of pages.
+
+ Page indexes must be given
+ starting from 0. Labels must have a style, a prefix or both. If to a
+ range is not assigned any page label a decimal label starting from 1 is
+ applied.
+
+ Args:
+ page_index_from: page index of the beginning of the range starting from 0
+ page_index_to: page index of the beginning of the range starting from 0
+ style: The numbering style to be used for the numeric portion of each page label:
+ /D Decimal arabic numerals
+ /R Uppercase roman numerals
+ /r Lowercase roman numerals
+ /A Uppercase letters (A to Z for the first 26 pages,
+ AA to ZZ for the next 26, and so on)
+ /a Lowercase letters (a to z for the first 26 pages,
+ aa to zz for the next 26, and so on)
+ prefix: The label prefix for page labels in this range.
+ start: The value of the numeric portion for the first page label
+ in the range.
+ Subsequent pages are numbered sequentially from this value,
+ which must be greater than or equal to 1. Default value: 1.
+ """
+ default_page_label = DictionaryObject()
+ default_page_label[NameObject("/S")] = NameObject("/D")
+
+ new_page_label = DictionaryObject()
+ if style is not None:
+ new_page_label[NameObject("/S")] = NameObject(style)
+ if prefix is not None:
+ new_page_label[NameObject("/P")] = TextStringObject(prefix)
+ if start != 0:
+ new_page_label[NameObject("/St")] = NumberObject(start)
+
+ if NameObject(CatalogDictionary.PAGE_LABELS) not in self._root_object:
+ nums = ArrayObject()
+ nums_insert(NumberObject(0), default_page_label, nums)
+ page_labels = TreeObject()
+ page_labels[NameObject("/Nums")] = nums
+ self._root_object[NameObject(CatalogDictionary.PAGE_LABELS)] = page_labels
+
+ page_labels = cast(
+ TreeObject, self._root_object[NameObject(CatalogDictionary.PAGE_LABELS)]
+ )
+ nums = cast(ArrayObject, page_labels[NameObject("/Nums")])
+
+ nums_insert(NumberObject(page_index_from), new_page_label, nums)
+ nums_clear_range(NumberObject(page_index_from), page_index_to, nums)
+ next_label_pos, *_ = nums_next(NumberObject(page_index_from), nums)
+ if next_label_pos != page_index_to + 1 and page_index_to + 1 < len(self.pages):
+ nums_insert(NumberObject(page_index_to + 1), default_page_label, nums)
+
+ page_labels[NameObject("/Nums")] = nums
+ self._root_object[NameObject(CatalogDictionary.PAGE_LABELS)] = page_labels
+
+
+def _pdf_objectify(obj: Union[Dict[str, Any], str, int, List[Any]]) -> PdfObject:
+ if isinstance(obj, PdfObject):
+ return obj
+ if isinstance(obj, dict):
+ to_add = DictionaryObject()
+ for key, value in obj.items():
+ name_key = NameObject(key)
+ casted_value = _pdf_objectify(value)
+ to_add[name_key] = casted_value
+ return to_add
+ elif isinstance(obj, list):
+ return ArrayObject(_pdf_objectify(el) for el in obj)
+ elif isinstance(obj, str):
+ if obj.startswith("/"):
+ return NameObject(obj)
+ else:
+ return TextStringObject(obj)
+ elif isinstance(obj, (int, float)):
+ return FloatObject(obj)
+ else:
+ raise NotImplementedError(
+ f"type(obj)={type(obj)} could not be casted to PdfObject"
+ )
+
+
+def _create_outline_item(
+ action_ref: Union[None, IndirectObject],
+ title: str,
+ color: Union[Tuple[float, float, float], str, None],
+ italic: bool,
+ bold: bool,
+) -> TreeObject:
+ outline_item = TreeObject()
+ if action_ref is not None:
+ outline_item[NameObject("/A")] = action_ref
+ outline_item.update(
+ {
+ NameObject("/Title"): create_string_object(title),
+ }
+ )
+ if color:
+ if isinstance(color, str):
+ color = hex_to_rgb(color)
+ outline_item.update(
+ {NameObject("/C"): ArrayObject([FloatObject(c) for c in color])}
+ )
+ if italic or bold:
+ format_flag = 0
+ if italic:
+ format_flag += 1
+ if bold:
+ format_flag += 2
+ outline_item.update({NameObject("/F"): NumberObject(format_flag)})
+ return outline_item
diff --git a/.venv/lib/python3.12/site-packages/pypdf/_xobj_image_helpers.py b/.venv/lib/python3.12/site-packages/pypdf/_xobj_image_helpers.py
new file mode 100644
index 00000000..45b0c145
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/pypdf/_xobj_image_helpers.py
@@ -0,0 +1,307 @@
+"""Code in here is only used by pypdf.filters._xobj_to_image"""
+
+import sys
+from io import BytesIO
+from typing import Any, List, Tuple, Union, cast
+
+from ._utils import check_if_whitespace_only, logger_warning
+from .constants import ColorSpaces
+from .errors import PdfReadError
+from .generic import (
+ ArrayObject,
+ DecodedStreamObject,
+ EncodedStreamObject,
+ IndirectObject,
+ NullObject,
+)
+
+if sys.version_info[:2] >= (3, 8):
+ from typing import Literal
+else:
+ # PEP 586 introduced typing.Literal with Python 3.8
+ # For older Python versions, the backport typing_extensions is necessary:
+ from typing_extensions import Literal
+
+if sys.version_info[:2] >= (3, 10):
+ from typing import TypeAlias
+else:
+ from typing_extensions import TypeAlias
+
+
+try:
+ from PIL import Image, UnidentifiedImageError # noqa: F401
+except ImportError:
+ raise ImportError(
+ "pillow is required to do image extraction. "
+ "It can be installed via 'pip install pypdf[image]'"
+ )
+
+mode_str_type: TypeAlias = Literal[
+ "", "1", "RGB", "2bits", "4bits", "P", "L", "RGBA", "CMYK"
+]
+
+MAX_IMAGE_MODE_NESTING_DEPTH: int = 10
+
+
+def _get_imagemode(
+ color_space: Union[str, List[Any], Any],
+ color_components: int,
+ prev_mode: mode_str_type,
+ depth: int = 0,
+) -> Tuple[mode_str_type, bool]:
+ """
+ Returns
+ Image mode not taking into account mask(transparency)
+ ColorInversion is required (like for some DeviceCMYK)
+ """
+ if depth > MAX_IMAGE_MODE_NESTING_DEPTH:
+ raise PdfReadError(
+ "Color spaces nested too deep. If required, consider increasing MAX_IMAGE_MODE_NESTING_DEPTH."
+ )
+ if isinstance(color_space, NullObject):
+ return "", False
+ if isinstance(color_space, str):
+ pass
+ elif not isinstance(color_space, list):
+ raise PdfReadError(
+ "Cannot interpret colorspace", color_space
+ ) # pragma: no cover
+ elif color_space[0].startswith("/Cal"): # /CalRGB and /CalGray
+ color_space = "/Device" + color_space[0][4:]
+ elif color_space[0] == "/ICCBased":
+ icc_profile = color_space[1].get_object()
+ color_components = cast(int, icc_profile["/N"])
+ color_space = icc_profile.get("/Alternate", "")
+ elif color_space[0] == "/Indexed":
+ color_space = color_space[1].get_object()
+ mode2, invert_color = _get_imagemode(
+ color_space, color_components, prev_mode, depth + 1
+ )
+ if mode2 in ("RGB", "CMYK"):
+ mode2 = "P"
+ return mode2, invert_color
+ elif color_space[0] == "/Separation":
+ color_space = color_space[2]
+ if isinstance(color_space, IndirectObject):
+ color_space = color_space.get_object()
+ mode2, invert_color = _get_imagemode(
+ color_space, color_components, prev_mode, depth + 1
+ )
+ return mode2, True
+ elif color_space[0] == "/DeviceN":
+ original_color_space = color_space
+ color_components = len(color_space[1])
+ color_space = color_space[2]
+ if isinstance(color_space, IndirectObject): # pragma: no cover
+ color_space = color_space.get_object()
+ if color_space == "/DeviceCMYK" and color_components == 1:
+ if original_color_space[1][0] != "/Black":
+ logger_warning(
+ f"Color {original_color_space[1][0]} converted to Gray. Please share PDF with pypdf dev team",
+ __name__,
+ )
+ return "L", True
+ mode2, invert_color = _get_imagemode(
+ color_space, color_components, prev_mode, depth + 1
+ )
+ return mode2, invert_color
+
+ mode_map = {
+ "1bit": "1", # pos [0] will be used for 1 bit
+ "/DeviceGray": "L", # must be in pos [1]
+ "palette": "P", # must be in pos [2] for color_components align.
+ "/DeviceRGB": "RGB", # must be in pos [3]
+ "/DeviceCMYK": "CMYK", # must be in pos [4]
+ "2bit": "2bits", # 2 bits images
+ "4bit": "4bits", # 4 bits
+ }
+ mode: mode_str_type = (
+ mode_map.get(color_space) # type: ignore
+ or list(mode_map.values())[color_components]
+ or prev_mode
+ )
+ return mode, mode == "CMYK"
+
+
+def bits2byte(data: bytes, size: Tuple[int, int], bits: int) -> bytes:
+ mask = (1 << bits) - 1
+ nbuff = bytearray(size[0] * size[1])
+ by = 0
+ bit = 8 - bits
+ for y in range(size[1]):
+ if (bit != 0) and (bit != 8 - bits):
+ by += 1
+ bit = 8 - bits
+ for x in range(size[0]):
+ nbuff[y * size[0] + x] = (data[by] >> bit) & mask
+ bit -= bits
+ if bit < 0:
+ by += 1
+ bit = 8 - bits
+ return bytes(nbuff)
+
+
+def _extended_image_frombytes(
+ mode: str, size: Tuple[int, int], data: bytes
+) -> Image.Image:
+ try:
+ img = Image.frombytes(mode, size, data)
+ except ValueError as exc:
+ nb_pix = size[0] * size[1]
+ if len(data) % nb_pix != 0:
+ raise exc
+ k = nb_pix * len(mode) / len(data)
+ data = b"".join([bytes((x,) * int(k)) for x in data])
+ img = Image.frombytes(mode, size, data)
+ return img
+
+
+def _handle_flate(
+ size: Tuple[int, int],
+ data: bytes,
+ mode: mode_str_type,
+ color_space: str,
+ colors: int,
+ obj_as_text: str,
+) -> Tuple[Image.Image, str, str, bool]:
+ """
+ Process image encoded in flateEncode
+ Returns img, image_format, extension, color inversion
+ """
+ extension = ".png" # mime_type = "image/png"
+ image_format = "PNG"
+ lookup: Any
+ base: Any
+ hival: Any
+ if isinstance(color_space, ArrayObject) and color_space[0] == "/Indexed":
+ color_space, base, hival, lookup = (value.get_object() for value in color_space)
+ if mode == "2bits":
+ mode = "P"
+ data = bits2byte(data, size, 2)
+ elif mode == "4bits":
+ mode = "P"
+ data = bits2byte(data, size, 4)
+ img = _extended_image_frombytes(mode, size, data)
+ if color_space == "/Indexed":
+ from .generic import TextStringObject
+
+ if isinstance(lookup, (EncodedStreamObject, DecodedStreamObject)):
+ lookup = lookup.get_data()
+ if isinstance(lookup, TextStringObject):
+ lookup = lookup.original_bytes
+ if isinstance(lookup, str):
+ lookup = lookup.encode()
+ try:
+ nb, conv, mode = { # type: ignore
+ "1": (0, "", ""),
+ "L": (1, "P", "L"),
+ "P": (0, "", ""),
+ "RGB": (3, "P", "RGB"),
+ "CMYK": (4, "P", "CMYK"),
+ }[_get_imagemode(base, 0, "")[0]]
+ except KeyError: # pragma: no cover
+ logger_warning(
+ f"Base {base} not coded please share the pdf file with pypdf dev team",
+ __name__,
+ )
+ lookup = None
+ else:
+ if img.mode == "1":
+ # Two values ("high" and "low").
+ expected_count = 2 * nb
+ if len(lookup) != expected_count:
+ if len(lookup) < expected_count:
+ raise PdfReadError(
+ f"Not enough lookup values: Expected {expected_count}, got {len(lookup)}."
+ )
+ if not check_if_whitespace_only(lookup[expected_count:]):
+ raise PdfReadError(
+ f"Too many lookup values: Expected {expected_count}, got {len(lookup)}."
+ )
+ lookup = lookup[:expected_count]
+ colors_arr = [lookup[:nb], lookup[nb:]]
+ arr = b"".join(
+ [
+ b"".join(
+ [
+ colors_arr[1 if img.getpixel((x, y)) > 127 else 0]
+ for x in range(img.size[0])
+ ]
+ )
+ for y in range(img.size[1])
+ ]
+ )
+ img = Image.frombytes(mode, img.size, arr)
+ else:
+ img = img.convert(conv)
+ if len(lookup) != (hival + 1) * nb:
+ logger_warning(f"Invalid Lookup Table in {obj_as_text}", __name__)
+ lookup = None
+ elif mode == "L":
+ # gray lookup does not work : it is converted to a similar RGB lookup
+ lookup = b"".join([bytes([b, b, b]) for b in lookup])
+ mode = "RGB"
+ # TODO : cf https://github.com/py-pdf/pypdf/pull/2039
+ # this is a work around until PIL is able to process CMYK images
+ elif mode == "CMYK":
+ _rgb = []
+ for _c, _m, _y, _k in (
+ lookup[n : n + 4] for n in range(0, 4 * (len(lookup) // 4), 4)
+ ):
+ _r = int(255 * (1 - _c / 255) * (1 - _k / 255))
+ _g = int(255 * (1 - _m / 255) * (1 - _k / 255))
+ _b = int(255 * (1 - _y / 255) * (1 - _k / 255))
+ _rgb.append(bytes((_r, _g, _b)))
+ lookup = b"".join(_rgb)
+ mode = "RGB"
+ if lookup is not None:
+ img.putpalette(lookup, rawmode=mode)
+ img = img.convert("L" if base == ColorSpaces.DEVICE_GRAY else "RGB")
+ elif not isinstance(color_space, NullObject) and color_space[0] == "/ICCBased":
+ # see Table 66 - Additional Entries Specific to an ICC Profile
+ # Stream Dictionary
+ mode2 = _get_imagemode(color_space, colors, mode)[0]
+ if mode != mode2:
+ img = Image.frombytes(mode2, size, data) # reloaded as mode may have change
+ if mode == "CMYK":
+ extension = ".tif"
+ image_format = "TIFF"
+ return img, image_format, extension, False
+
+
+def _handle_jpx(
+ size: Tuple[int, int],
+ data: bytes,
+ mode: mode_str_type,
+ color_space: str,
+ colors: int,
+) -> Tuple[Image.Image, str, str, bool]:
+ """
+ Process image encoded in flateEncode
+ Returns img, image_format, extension, inversion
+ """
+ extension = ".jp2" # mime_type = "image/x-jp2"
+ img1 = Image.open(BytesIO(data), formats=("JPEG2000",))
+ mode, invert_color = _get_imagemode(color_space, colors, mode)
+ if mode == "":
+ mode = cast(mode_str_type, img1.mode)
+ invert_color = mode in ("CMYK",)
+ if img1.mode == "RGBA" and mode == "RGB":
+ mode = "RGBA"
+ # we need to convert to the good mode
+ if img1.mode == mode or {img1.mode, mode} == {"L", "P"}: # compare (unordered) sets
+ # L,P are indexed modes which should not be changed.
+ img = img1
+ elif {img1.mode, mode} == {"RGBA", "CMYK"}:
+ # RGBA / CMYK are 4bytes encoding where
+ # the encoding should be corrected
+ img = Image.frombytes(mode, img1.size, img1.tobytes())
+ else: # pragma: no cover
+ img = img1.convert(mode)
+ # for CMYK conversion :
+ # https://stcom/questions/38855022/conversion-from-cmyk-to-rgb-with-pillow-is-different-from-that-of-photoshop
+ # not implemented for the moment as I need to get properly the ICC
+ if img.mode == "CMYK":
+ img = img.convert("RGB")
+ image_format = "JPEG2000"
+ return img, image_format, extension, invert_color
diff --git a/.venv/lib/python3.12/site-packages/pypdf/annotations/__init__.py b/.venv/lib/python3.12/site-packages/pypdf/annotations/__init__.py
new file mode 100644
index 00000000..3ddf9856
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/pypdf/annotations/__init__.py
@@ -0,0 +1,45 @@
+"""
+PDF specifies several annotation types which pypdf makes available here.
+
+The names of the annotations and their attributes do not reflect the names in
+the specification in all cases. For example, the PDF standard defines a
+'Square' annotation that does not actually need to be square. For this reason,
+pypdf calls it 'Rectangle'.
+
+At their core, all annotation types are DictionaryObjects. That means if pypdf
+does not implement a feature, users can easily extend the given functionality.
+"""
+
+
+from ._base import NO_FLAGS, AnnotationDictionary
+from ._markup_annotations import (
+ Ellipse,
+ FreeText,
+ Highlight,
+ Line,
+ MarkupAnnotation,
+ Polygon,
+ PolyLine,
+ Rectangle,
+ Text,
+)
+from ._non_markup_annotations import Link, Popup
+
+__all__ = [
+ "NO_FLAGS",
+ # Export abstract base classes so that they are shown in the docs
+ "AnnotationDictionary",
+ "MarkupAnnotation",
+ # markup annotations
+ "Ellipse",
+ "FreeText",
+ "Highlight",
+ "Line",
+ "Link",
+ "Polygon",
+ "PolyLine",
+ "Rectangle",
+ "Text",
+ # Non-markup annotations
+ "Popup",
+]
diff --git a/.venv/lib/python3.12/site-packages/pypdf/annotations/_base.py b/.venv/lib/python3.12/site-packages/pypdf/annotations/_base.py
new file mode 100644
index 00000000..f235acf3
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/pypdf/annotations/_base.py
@@ -0,0 +1,27 @@
+from abc import ABC
+
+from ..constants import AnnotationFlag
+from ..generic import NameObject, NumberObject
+from ..generic._data_structures import DictionaryObject
+
+
+class AnnotationDictionary(DictionaryObject, ABC):
+ def __init__(self) -> None:
+ from ..generic._base import NameObject
+
+ # "rect" should not be added here as PolyLine can automatically set it
+ self[NameObject("/Type")] = NameObject("/Annot")
+ # The flags was NOT added to the constructor on purpose: We expect that
+ # most users don't want to change the default. If they want, they
+ # can use the property. The default is 0.
+
+ @property
+ def flags(self) -> AnnotationFlag:
+ return self.get(NameObject("/F"), AnnotationFlag(0))
+
+ @flags.setter
+ def flags(self, value: AnnotationFlag) -> None:
+ self[NameObject("/F")] = NumberObject(value)
+
+
+NO_FLAGS = AnnotationFlag(0)
diff --git a/.venv/lib/python3.12/site-packages/pypdf/annotations/_markup_annotations.py b/.venv/lib/python3.12/site-packages/pypdf/annotations/_markup_annotations.py
new file mode 100644
index 00000000..4db8dfdb
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/pypdf/annotations/_markup_annotations.py
@@ -0,0 +1,308 @@
+import sys
+from abc import ABC
+from typing import Any, List, Optional, Tuple, Union
+
+from .._utils import deprecate_with_replacement
+from ..constants import AnnotationFlag
+from ..generic import ArrayObject, DictionaryObject
+from ..generic._base import (
+ BooleanObject,
+ FloatObject,
+ NameObject,
+ NumberObject,
+ TextStringObject,
+)
+from ..generic._rectangle import RectangleObject
+from ..generic._utils import hex_to_rgb
+from ._base import NO_FLAGS, AnnotationDictionary
+
+if sys.version_info[:2] >= (3, 10):
+ from typing import TypeAlias
+else:
+ # PEP 613 introduced typing.TypeAlias with Python 3.10
+ # For older Python versions, the backport typing_extensions is necessary:
+ from typing_extensions import TypeAlias
+
+
+Vertex: TypeAlias = Tuple[float, float]
+
+
+def _get_bounding_rectangle(vertices: List[Vertex]) -> RectangleObject:
+ x_min, y_min = vertices[0][0], vertices[0][1]
+ x_max, y_max = vertices[0][0], vertices[0][1]
+ for x, y in vertices:
+ x_min = min(x_min, x)
+ y_min = min(y_min, y)
+ x_max = max(x_max, x)
+ y_max = max(y_max, y)
+ rect = RectangleObject((x_min, y_min, x_max, y_max))
+ return rect
+
+
+class MarkupAnnotation(AnnotationDictionary, ABC):
+ """
+ Base class for all markup annotations.
+
+ Args:
+ title_bar: Text to be displayed in the title bar of the annotation;
+ by convention this is the name of the author
+ """
+
+ def __init__(self, *, title_bar: Optional[str] = None):
+ if title_bar is not None:
+ self[NameObject("T")] = TextStringObject(title_bar)
+
+
+class Text(MarkupAnnotation):
+ """
+ A text annotation.
+
+ Args:
+ rect: array of four integers ``[xLL, yLL, xUR, yUR]``
+ specifying the clickable rectangular area
+ text: The text that is added to the document
+ open:
+ flags:
+ """
+
+ def __init__(
+ self,
+ *,
+ rect: Union[RectangleObject, Tuple[float, float, float, float]],
+ text: str,
+ open: bool = False,
+ flags: int = NO_FLAGS,
+ **kwargs: Any,
+ ):
+ super().__init__(**kwargs)
+ self[NameObject("/Subtype")] = NameObject("/Text")
+ self[NameObject("/Rect")] = RectangleObject(rect)
+ self[NameObject("/Contents")] = TextStringObject(text)
+ self[NameObject("/Open")] = BooleanObject(open)
+ self[NameObject("/Flags")] = NumberObject(flags)
+
+
+class FreeText(MarkupAnnotation):
+ """A FreeText annotation"""
+
+ def __init__(
+ self,
+ *,
+ text: str,
+ rect: Union[RectangleObject, Tuple[float, float, float, float]],
+ font: str = "Helvetica",
+ bold: bool = False,
+ italic: bool = False,
+ font_size: str = "14pt",
+ font_color: str = "000000",
+ border_color: Optional[str] = "000000",
+ background_color: Optional[str] = "ffffff",
+ **kwargs: Any,
+ ):
+ super().__init__(**kwargs)
+ self[NameObject("/Subtype")] = NameObject("/FreeText")
+ self[NameObject("/Rect")] = RectangleObject(rect)
+
+ font_str = "font: "
+ if bold is True:
+ font_str = f"{font_str}bold "
+ if italic is True:
+ font_str = f"{font_str}italic "
+ font_str = f"{font_str}{font} {font_size}"
+ font_str = f"{font_str};text-align:left;color:#{font_color}"
+
+ default_appearance_string = ""
+ if border_color:
+ for st in hex_to_rgb(border_color):
+ default_appearance_string = f"{default_appearance_string}{st} "
+ default_appearance_string = f"{default_appearance_string}rg"
+
+ self.update(
+ {
+ NameObject("/Subtype"): NameObject("/FreeText"),
+ NameObject("/Rect"): RectangleObject(rect),
+ NameObject("/Contents"): TextStringObject(text),
+ # font size color
+ NameObject("/DS"): TextStringObject(font_str),
+ NameObject("/DA"): TextStringObject(default_appearance_string),
+ }
+ )
+ if border_color is None:
+ # Border Style
+ self[NameObject("/BS")] = DictionaryObject(
+ {
+ # width of 0 means no border
+ NameObject("/W"): NumberObject(0)
+ }
+ )
+ if background_color is not None:
+ self[NameObject("/C")] = ArrayObject(
+ [FloatObject(n) for n in hex_to_rgb(background_color)]
+ )
+
+
+class Line(MarkupAnnotation):
+ def __init__(
+ self,
+ p1: Vertex,
+ p2: Vertex,
+ rect: Union[RectangleObject, Tuple[float, float, float, float]],
+ text: str = "",
+ **kwargs: Any,
+ ):
+ super().__init__(**kwargs)
+ self.update(
+ {
+ NameObject("/Subtype"): NameObject("/Line"),
+ NameObject("/Rect"): RectangleObject(rect),
+ NameObject("/L"): ArrayObject(
+ [
+ FloatObject(p1[0]),
+ FloatObject(p1[1]),
+ FloatObject(p2[0]),
+ FloatObject(p2[1]),
+ ]
+ ),
+ NameObject("/LE"): ArrayObject(
+ [
+ NameObject("/None"),
+ NameObject("/None"),
+ ]
+ ),
+ NameObject("/IC"): ArrayObject(
+ [
+ FloatObject(0.5),
+ FloatObject(0.5),
+ FloatObject(0.5),
+ ]
+ ),
+ NameObject("/Contents"): TextStringObject(text),
+ }
+ )
+
+
+class PolyLine(MarkupAnnotation):
+ def __init__(
+ self,
+ vertices: List[Vertex],
+ **kwargs: Any,
+ ):
+ super().__init__(**kwargs)
+ if len(vertices) == 0:
+ raise ValueError("A polygon needs at least 1 vertex with two coordinates")
+ coord_list = []
+ for x, y in vertices:
+ coord_list.append(NumberObject(x))
+ coord_list.append(NumberObject(y))
+ self.update(
+ {
+ NameObject("/Subtype"): NameObject("/PolyLine"),
+ NameObject("/Vertices"): ArrayObject(coord_list),
+ NameObject("/Rect"): RectangleObject(_get_bounding_rectangle(vertices)),
+ }
+ )
+
+
+class Rectangle(MarkupAnnotation):
+ def __init__(
+ self,
+ rect: Union[RectangleObject, Tuple[float, float, float, float]],
+ *,
+ interior_color: Optional[str] = None,
+ **kwargs: Any,
+ ):
+ if "interiour_color" in kwargs:
+ deprecate_with_replacement("interiour_color", "interior_color", "6.0.0")
+ interior_color = kwargs["interiour_color"]
+ del kwargs["interiour_color"]
+ super().__init__(**kwargs)
+ self.update(
+ {
+ NameObject("/Type"): NameObject("/Annot"),
+ NameObject("/Subtype"): NameObject("/Square"),
+ NameObject("/Rect"): RectangleObject(rect),
+ }
+ )
+
+ if interior_color:
+ self[NameObject("/IC")] = ArrayObject(
+ [FloatObject(n) for n in hex_to_rgb(interior_color)]
+ )
+
+
+class Highlight(MarkupAnnotation):
+ def __init__(
+ self,
+ *,
+ rect: Union[RectangleObject, Tuple[float, float, float, float]],
+ quad_points: ArrayObject,
+ highlight_color: str = "ff0000",
+ printing: bool = False,
+ **kwargs: Any,
+ ):
+ super().__init__(**kwargs)
+ self.update(
+ {
+ NameObject("/Subtype"): NameObject("/Highlight"),
+ NameObject("/Rect"): RectangleObject(rect),
+ NameObject("/QuadPoints"): quad_points,
+ NameObject("/C"): ArrayObject(
+ [FloatObject(n) for n in hex_to_rgb(highlight_color)]
+ ),
+ }
+ )
+ if printing:
+ self.flags = AnnotationFlag.PRINT
+
+
+class Ellipse(MarkupAnnotation):
+ def __init__(
+ self,
+ rect: Union[RectangleObject, Tuple[float, float, float, float]],
+ *,
+ interior_color: Optional[str] = None,
+ **kwargs: Any,
+ ):
+ if "interiour_color" in kwargs:
+ deprecate_with_replacement("interiour_color", "interior_color", "6.0.0")
+ interior_color = kwargs["interiour_color"]
+ del kwargs["interiour_color"]
+ super().__init__(**kwargs)
+
+ self.update(
+ {
+ NameObject("/Type"): NameObject("/Annot"),
+ NameObject("/Subtype"): NameObject("/Circle"),
+ NameObject("/Rect"): RectangleObject(rect),
+ }
+ )
+
+ if interior_color:
+ self[NameObject("/IC")] = ArrayObject(
+ [FloatObject(n) for n in hex_to_rgb(interior_color)]
+ )
+
+
+class Polygon(MarkupAnnotation):
+ def __init__(
+ self,
+ vertices: List[Tuple[float, float]],
+ **kwargs: Any,
+ ):
+ super().__init__(**kwargs)
+ if len(vertices) == 0:
+ raise ValueError("A polygon needs at least 1 vertex with two coordinates")
+
+ coord_list = []
+ for x, y in vertices:
+ coord_list.append(NumberObject(x))
+ coord_list.append(NumberObject(y))
+ self.update(
+ {
+ NameObject("/Type"): NameObject("/Annot"),
+ NameObject("/Subtype"): NameObject("/Polygon"),
+ NameObject("/Vertices"): ArrayObject(coord_list),
+ NameObject("/IT"): NameObject("/PolygonCloud"),
+ NameObject("/Rect"): RectangleObject(_get_bounding_rectangle(vertices)),
+ }
+ )
diff --git a/.venv/lib/python3.12/site-packages/pypdf/annotations/_non_markup_annotations.py b/.venv/lib/python3.12/site-packages/pypdf/annotations/_non_markup_annotations.py
new file mode 100644
index 00000000..dcdb3b0f
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/pypdf/annotations/_non_markup_annotations.py
@@ -0,0 +1,109 @@
+from typing import TYPE_CHECKING, Any, Optional, Tuple, Union
+
+from ..constants import AnnotationFlag
+from ..generic._base import (
+ BooleanObject,
+ NameObject,
+ NumberObject,
+ TextStringObject,
+)
+from ..generic._data_structures import ArrayObject, DictionaryObject
+from ..generic._fit import DEFAULT_FIT, Fit
+from ..generic._rectangle import RectangleObject
+from ._base import AnnotationDictionary
+
+DEFAULT_ANNOTATION_FLAG = AnnotationFlag(0)
+
+
+class Link(AnnotationDictionary):
+ def __init__(
+ self,
+ *,
+ rect: Union[RectangleObject, Tuple[float, float, float, float]],
+ border: Optional[ArrayObject] = None,
+ url: Optional[str] = None,
+ target_page_index: Optional[int] = None,
+ fit: Fit = DEFAULT_FIT,
+ **kwargs: Any,
+ ):
+ super().__init__(**kwargs)
+ if TYPE_CHECKING:
+ from ..types import BorderArrayType
+
+ is_external = url is not None
+ is_internal = target_page_index is not None
+ if not is_external and not is_internal:
+ raise ValueError(
+ "Either 'url' or 'target_page_index' have to be provided. Both were None."
+ )
+ if is_external and is_internal:
+ raise ValueError(
+ "Either 'url' or 'target_page_index' have to be provided. "
+ f"url={url}, target_page_index={target_page_index}"
+ )
+
+ border_arr: BorderArrayType
+ if border is not None:
+ border_arr = [NumberObject(n) for n in border[:3]]
+ if len(border) == 4:
+ dash_pattern = ArrayObject([NumberObject(n) for n in border[3]])
+ border_arr.append(dash_pattern)
+ else:
+ border_arr = [NumberObject(0)] * 3
+
+ self.update(
+ {
+ NameObject("/Type"): NameObject("/Annot"),
+ NameObject("/Subtype"): NameObject("/Link"),
+ NameObject("/Rect"): RectangleObject(rect),
+ NameObject("/Border"): ArrayObject(border_arr),
+ }
+ )
+ if is_external:
+ self[NameObject("/A")] = DictionaryObject(
+ {
+ NameObject("/S"): NameObject("/URI"),
+ NameObject("/Type"): NameObject("/Action"),
+ NameObject("/URI"): TextStringObject(url),
+ }
+ )
+ if is_internal:
+ # This needs to be updated later!
+ dest_deferred = DictionaryObject(
+ {
+ "target_page_index": NumberObject(target_page_index),
+ "fit": NameObject(fit.fit_type),
+ "fit_args": fit.fit_args,
+ }
+ )
+ self[NameObject("/Dest")] = dest_deferred
+
+
+class Popup(AnnotationDictionary):
+ def __init__(
+ self,
+ *,
+ rect: Union[RectangleObject, Tuple[float, float, float, float]],
+ parent: Optional[DictionaryObject] = None,
+ open: bool = False,
+ **kwargs: Any,
+ ):
+ super().__init__(**kwargs)
+ self.update(
+ {
+ NameObject("/Subtype"): NameObject("/Popup"),
+ NameObject("/Rect"): RectangleObject(rect),
+ NameObject("/Open"): BooleanObject(open),
+ }
+ )
+ if parent:
+ # This needs to be an indirect object
+ try:
+ self[NameObject("/Parent")] = parent.indirect_reference
+ except AttributeError:
+ from .._utils import logger_warning
+
+ logger_warning(
+ "Unregistered Parent object : No Parent field set",
+ __name__,
+ )
diff --git a/.venv/lib/python3.12/site-packages/pypdf/constants.py b/.venv/lib/python3.12/site-packages/pypdf/constants.py
new file mode 100644
index 00000000..745774e2
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/pypdf/constants.py
@@ -0,0 +1,762 @@
+"""
+PDF Specification Archive
+https://pdfa.org/resource/pdf-specification-archive/
+
+Portable Document Format Reference Manual, 1993. ISBN 0-201-62628-4
+https://opensource.adobe.com/dc-acrobat-sdk-docs/pdfstandards/pdfreference1.0.pdf
+
+ISO 32000-1:2008 (PDF 1.7)
+https://opensource.adobe.com/dc-acrobat-sdk-docs/pdfstandards/PDF32000_2008.pdf
+
+ISO 32000-2:2020 (PDF 2.0)
+"""
+
+from enum import IntFlag, auto
+from typing import Dict, Tuple
+
+from ._utils import classproperty, deprecate_with_replacement
+
+
+class Core:
+ """Keywords that don't quite belong anywhere else."""
+
+ OUTLINES = "/Outlines"
+ THREADS = "/Threads"
+ PAGE = "/Page"
+ PAGES = "/Pages"
+ CATALOG = "/Catalog"
+
+
+class TrailerKeys:
+ ROOT = "/Root"
+ ENCRYPT = "/Encrypt"
+ ID = "/ID"
+ INFO = "/Info"
+ SIZE = "/Size"
+
+
+class CatalogAttributes:
+ NAMES = "/Names"
+ DESTS = "/Dests"
+
+
+class EncryptionDictAttributes:
+ """
+ Additional encryption dictionary entries for the standard security handler.
+
+ Table 3.19, Page 122.
+ Table 21 of the 2.0 manual.
+ """
+
+ R = "/R" # number, required; revision of the standard security handler
+ O = "/O" # 32-byte string, required # noqa
+ U = "/U" # 32-byte string, required
+ P = "/P" # integer flag, required; permitted operations
+ ENCRYPT_METADATA = "/EncryptMetadata" # boolean flag, optional
+
+
+class UserAccessPermissions(IntFlag):
+ """
+ Table 3.20 User access permissions.
+ Table 22 of the 2.0 manual.
+ """
+
+ R1 = 1
+ R2 = 2
+ PRINT = 4
+ MODIFY = 8
+ EXTRACT = 16
+ ADD_OR_MODIFY = 32
+ R7 = 64
+ R8 = 128
+ FILL_FORM_FIELDS = 256
+ EXTRACT_TEXT_AND_GRAPHICS = 512
+ ASSEMBLE_DOC = 1024
+ PRINT_TO_REPRESENTATION = 2048
+ R13 = 2**12
+ R14 = 2**13
+ R15 = 2**14
+ R16 = 2**15
+ R17 = 2**16
+ R18 = 2**17
+ R19 = 2**18
+ R20 = 2**19
+ R21 = 2**20
+ R22 = 2**21
+ R23 = 2**22
+ R24 = 2**23
+ R25 = 2**24
+ R26 = 2**25
+ R27 = 2**26
+ R28 = 2**27
+ R29 = 2**28
+ R30 = 2**29
+ R31 = 2**30
+ R32 = 2**31
+
+ @classmethod
+ def _is_reserved(cls, name: str) -> bool:
+ """Check if the given name corresponds to a reserved flag entry."""
+ return name.startswith("R") and name[1:].isdigit()
+
+ @classmethod
+ def _is_active(cls, name: str) -> bool:
+ """Check if the given reserved name defaults to 1 = active."""
+ return name not in {"R1", "R2"}
+
+ def to_dict(self) -> Dict[str, bool]:
+ """Convert the given flag value to a corresponding verbose name mapping."""
+ result: Dict[str, bool] = {}
+ for name, flag in UserAccessPermissions.__members__.items():
+ if UserAccessPermissions._is_reserved(name):
+ continue
+ result[name.lower()] = (self & flag) == flag
+ return result
+
+ @classmethod
+ def from_dict(cls, value: Dict[str, bool]) -> "UserAccessPermissions":
+ """Convert the verbose name mapping to the corresponding flag value."""
+ value_copy = value.copy()
+ result = cls(0)
+ for name, flag in cls.__members__.items():
+ if cls._is_reserved(name):
+ # Reserved names have a required value. Use it.
+ if cls._is_active(name):
+ result |= flag
+ continue
+ is_active = value_copy.pop(name.lower(), False)
+ if is_active:
+ result |= flag
+ if value_copy:
+ raise ValueError(f"Unknown dictionary keys: {value_copy!r}")
+ return result
+
+ @classmethod
+ def all(cls) -> "UserAccessPermissions":
+ return cls((2**32 - 1) - cls.R1 - cls.R2)
+
+
+class Resources:
+ """
+ Table 3.30 Entries in a resource dictionary.
+ Used to be Ressources (a misspelling).
+
+ Table 34 in the 2.0 reference.
+ """
+
+ EXT_G_STATE = "/ExtGState" # dictionary, optional
+ COLOR_SPACE = "/ColorSpace" # dictionary, optional
+ PATTERN = "/Pattern" # dictionary, optional
+ SHADING = "/Shading" # dictionary, optional
+ XOBJECT = "/XObject" # dictionary, optional
+ FONT = "/Font" # dictionary, optional
+ PROC_SET = "/ProcSet" # array, optional
+ PROPERTIES = "/Properties" # dictionary, optional
+
+
+class Ressources: # deprecated
+ """
+ Use :class: `Resources` instead.
+
+ .. deprecated:: 5.0.0
+ """
+
+ @classproperty
+ def EXT_G_STATE(cls) -> str: # noqa: N805
+ deprecate_with_replacement("Ressources", "Resources", "5.0.0")
+ return "/ExtGState" # dictionary, optional
+
+ @classproperty
+ def COLOR_SPACE(cls) -> str: # noqa: N805
+ deprecate_with_replacement("Ressources", "Resources", "5.0.0")
+ return "/ColorSpace" # dictionary, optional
+
+ @classproperty
+ def PATTERN(cls) -> str: # noqa: N805
+ deprecate_with_replacement("Ressources", "Resources", "5.0.0")
+ return "/Pattern" # dictionary, optional
+
+ @classproperty
+ def SHADING(cls) -> str: # noqa: N805
+ deprecate_with_replacement("Ressources", "Resources", "5.0.0")
+ return "/Shading" # dictionary, optional
+
+ @classproperty
+ def XOBJECT(cls) -> str: # noqa: N805
+ deprecate_with_replacement("Ressources", "Resources", "5.0.0")
+ return "/XObject" # dictionary, optional
+
+ @classproperty
+ def FONT(cls) -> str: # noqa: N805
+ deprecate_with_replacement("Ressources", "Resources", "5.0.0")
+ return "/Font" # dictionary, optional
+
+ @classproperty
+ def PROC_SET(cls) -> str: # noqa: N805
+ deprecate_with_replacement("Ressources", "Resources", "5.0.0")
+ return "/ProcSet" # array, optional
+
+ @classproperty
+ def PROPERTIES(cls) -> str: # noqa: N805
+ deprecate_with_replacement("Ressources", "Resources", "5.0.0")
+ return "/Properties" # dictionary, optional
+
+
+class PagesAttributes:
+ """§7.7.3.2 of the 1.7 and 2.0 reference."""
+
+ TYPE = "/Type" # name, required; must be /Pages
+ PARENT = "/Parent" # dictionary, required; indirect reference to pages object
+ KIDS = "/Kids" # array, required; List of indirect references
+ COUNT = "/Count" # integer, required; the number of leaf nodes (page objects)
+ # that are descendants of this node within the page tree
+
+
+class PageAttributes:
+ """§7.7.3.3 of the 1.7 and 2.0 reference."""
+
+ TYPE = "/Type" # name, required; must be /Page
+ PARENT = "/Parent" # dictionary, required; a pages object
+ LAST_MODIFIED = "/LastModified" # date, optional; date and time of last modification
+ RESOURCES = "/Resources" # dictionary, required if there are any
+ MEDIABOX = "/MediaBox" # rectangle, required; rectangle specifying page size
+ CROPBOX = "/CropBox" # rectangle, optional
+ BLEEDBOX = "/BleedBox" # rectangle, optional
+ TRIMBOX = "/TrimBox" # rectangle, optional
+ ARTBOX = "/ArtBox" # rectangle, optional
+ BOX_COLOR_INFO = "/BoxColorInfo" # dictionary, optional
+ CONTENTS = "/Contents" # stream or array, optional
+ ROTATE = "/Rotate" # integer, optional; page rotation in degrees
+ GROUP = "/Group" # dictionary, optional; page group
+ THUMB = "/Thumb" # stream, optional; indirect reference to image of the page
+ B = "/B" # array, optional
+ DUR = "/Dur" # number, optional
+ TRANS = "/Trans" # dictionary, optional
+ ANNOTS = "/Annots" # array, optional; an array of annotations
+ AA = "/AA" # dictionary, optional
+ METADATA = "/Metadata" # stream, optional
+ PIECE_INFO = "/PieceInfo" # dictionary, optional
+ STRUCT_PARENTS = "/StructParents" # integer, optional
+ ID = "/ID" # byte string, optional
+ PZ = "/PZ" # number, optional
+ SEPARATION_INFO = "/SeparationInfo" # dictionary, optional
+ TABS = "/Tabs" # name, optional
+ TEMPLATE_INSTANTIATED = "/TemplateInstantiated" # name, optional
+ PRES_STEPS = "/PresSteps" # dictionary, optional
+ USER_UNIT = "/UserUnit" # number, optional
+ VP = "/VP" # dictionary, optional
+ AF = "/AF" # array of dictionaries, optional
+ OUTPUT_INTENTS = "/OutputIntents" # array, optional
+ D_PART = "/DPart" # dictionary, required, if this page is within the range of a DPart, not permitted otherwise
+
+
+class FileSpecificationDictionaryEntries:
+ """Table 3.41 Entries in a file specification dictionary."""
+
+ Type = "/Type"
+ FS = "/FS" # The name of the file system to be used to interpret this file specification
+ F = "/F" # A file specification string of the form described in Section 3.10.1
+ UF = "/UF" # A unicode string of the file as described in Section 3.10.1
+ DOS = "/DOS"
+ Mac = "/Mac"
+ Unix = "/Unix"
+ ID = "/ID"
+ V = "/V"
+ EF = "/EF" # dictionary, containing a subset of the keys F , UF , DOS , Mac , and Unix
+ RF = "/RF" # dictionary, containing arrays of /EmbeddedFile
+ DESC = "/Desc" # description of the file
+ Cl = "/Cl"
+
+
+class StreamAttributes:
+ """
+ Table 4.2.
+ Table 5 in the 2.0 reference.
+ """
+
+ LENGTH = "/Length" # integer, required
+ FILTER = "/Filter" # name or array of names, optional
+ DECODE_PARMS = "/DecodeParms" # variable, optional -- 'decodeParams is wrong
+
+
+class FilterTypes:
+ """§7.4 of the 1.7 and 2.0 references."""
+
+ ASCII_HEX_DECODE = "/ASCIIHexDecode" # abbreviation: AHx
+ ASCII_85_DECODE = "/ASCII85Decode" # abbreviation: A85
+ LZW_DECODE = "/LZWDecode" # abbreviation: LZW
+ FLATE_DECODE = "/FlateDecode" # abbreviation: Fl, PDF 1.2
+ RUN_LENGTH_DECODE = "/RunLengthDecode" # abbreviation: RL
+ CCITT_FAX_DECODE = "/CCITTFaxDecode" # abbreviation: CCF
+ DCT_DECODE = "/DCTDecode" # abbreviation: DCT
+ JPX_DECODE = "/JPXDecode"
+
+
+class FilterTypeAbbreviations:
+ """§8.9.7 of the 1.7 and 2.0 references."""
+
+ AHx = "/AHx"
+ A85 = "/A85"
+ LZW = "/LZW"
+ FL = "/Fl" # FlateDecode
+ RL = "/RL"
+ CCF = "/CCF"
+ DCT = "/DCT"
+
+
+class LzwFilterParameters:
+ """
+ Table 4.4.
+ Table 8 in the 2.0 reference.
+ """
+
+ PREDICTOR = "/Predictor" # integer
+ COLORS = "/Colors" # integer
+ BITS_PER_COMPONENT = "/BitsPerComponent" # integer
+ COLUMNS = "/Columns" # integer
+ EARLY_CHANGE = "/EarlyChange" # integer
+
+
+class CcittFaxDecodeParameters:
+ """
+ Table 4.5.
+ Table 11 in the 2.0 reference.
+ """
+
+ K = "/K" # integer
+ END_OF_LINE = "/EndOfLine" # boolean
+ ENCODED_BYTE_ALIGN = "/EncodedByteAlign" # boolean
+ COLUMNS = "/Columns" # integer
+ ROWS = "/Rows" # integer
+ END_OF_BLOCK = "/EndOfBlock" # boolean
+ BLACK_IS_1 = "/BlackIs1" # boolean
+ DAMAGED_ROWS_BEFORE_ERROR = "/DamagedRowsBeforeError" # integer
+
+
+class ImageAttributes:
+ """§11.6.5 of the 1.7 and 2.0 references."""
+
+ TYPE = "/Type" # name, required; must be /XObject
+ SUBTYPE = "/Subtype" # name, required; must be /Image
+ NAME = "/Name" # name, required
+ WIDTH = "/Width" # integer, required
+ HEIGHT = "/Height" # integer, required
+ BITS_PER_COMPONENT = "/BitsPerComponent" # integer, required
+ COLOR_SPACE = "/ColorSpace" # name, required
+ DECODE = "/Decode" # array, optional
+ INTENT = "/Intent" # string, optional
+ INTERPOLATE = "/Interpolate" # boolean, optional
+ IMAGE_MASK = "/ImageMask" # boolean, optional
+ MASK = "/Mask" # 1-bit image mask stream
+ S_MASK = "/SMask" # dictionary or name, optional
+
+
+class ColorSpaces:
+ DEVICE_RGB = "/DeviceRGB"
+ DEVICE_CMYK = "/DeviceCMYK"
+ DEVICE_GRAY = "/DeviceGray"
+
+
+class TypArguments:
+ """Table 8.2 of the PDF 1.7 reference."""
+
+ LEFT = "/Left"
+ RIGHT = "/Right"
+ BOTTOM = "/Bottom"
+ TOP = "/Top"
+
+
+class TypFitArguments:
+ """Table 8.2 of the PDF 1.7 reference."""
+
+ FIT = "/Fit"
+ FIT_V = "/FitV"
+ FIT_BV = "/FitBV"
+ FIT_B = "/FitB"
+ FIT_H = "/FitH"
+ FIT_BH = "/FitBH"
+ FIT_R = "/FitR"
+ XYZ = "/XYZ"
+
+
+class GoToActionArguments:
+ S = "/S" # name, required: type of action
+ D = "/D" # name / byte string /array, required: Destination to jump to
+
+
+class AnnotationDictionaryAttributes:
+ """Table 8.15 Entries common to all annotation dictionaries."""
+
+ Type = "/Type"
+ Subtype = "/Subtype"
+ Rect = "/Rect"
+ Contents = "/Contents"
+ P = "/P"
+ NM = "/NM"
+ M = "/M"
+ F = "/F"
+ AP = "/AP"
+ AS = "/AS"
+ DA = "/DA"
+ Border = "/Border"
+ C = "/C"
+ StructParent = "/StructParent"
+ OC = "/OC"
+
+
+class InteractiveFormDictEntries:
+ Fields = "/Fields"
+ NeedAppearances = "/NeedAppearances"
+ SigFlags = "/SigFlags"
+ CO = "/CO"
+ DR = "/DR"
+ DA = "/DA"
+ Q = "/Q"
+ XFA = "/XFA"
+
+
+class FieldDictionaryAttributes:
+ """
+ Entries common to all field dictionaries (Table 8.69 PDF 1.7 reference)
+ (*very partially documented here*).
+
+ FFBits provides the constants used for `/Ff` from Table 8.70/8.75/8.77/8.79
+ """
+
+ FT = "/FT" # name, required for terminal fields
+ Parent = "/Parent" # dictionary, required for children
+ Kids = "/Kids" # array, sometimes required
+ T = "/T" # text string, optional
+ TU = "/TU" # text string, optional
+ TM = "/TM" # text string, optional
+ Ff = "/Ff" # integer, optional
+ V = "/V" # text string or array, optional
+ DV = "/DV" # text string, optional
+ AA = "/AA" # dictionary, optional
+ Opt = "/Opt"
+
+ class FfBits(IntFlag):
+ """
+ Ease building /Ff flags
+ Some entries may be specific to:
+
+ * Text(Tx) (Table 8.75 PDF 1.7 reference)
+ * Buttons(Btn) (Table 8.77 PDF 1.7 reference)
+ * List(Ch) (Table 8.79 PDF 1.7 reference)
+ """
+
+ ReadOnly = 1 << 0
+ """common to Tx/Btn/Ch in Table 8.70"""
+ Required = 1 << 1
+ """common to Tx/Btn/Ch in Table 8.70"""
+ NoExport = 1 << 2
+ """common to Tx/Btn/Ch in Table 8.70"""
+
+ Multiline = 1 << 12
+ """Tx"""
+ Password = 1 << 13
+ """Tx"""
+
+ NoToggleToOff = 1 << 14
+ """Btn"""
+ Radio = 1 << 15
+ """Btn"""
+ Pushbutton = 1 << 16
+ """Btn"""
+
+ Combo = 1 << 17
+ """Ch"""
+ Edit = 1 << 18
+ """Ch"""
+ Sort = 1 << 19
+ """Ch"""
+
+ FileSelect = 1 << 20
+ """Tx"""
+
+ MultiSelect = 1 << 21
+ """Tx"""
+
+ DoNotSpellCheck = 1 << 22
+ """Tx/Ch"""
+ DoNotScroll = 1 << 23
+ """Tx"""
+ Comb = 1 << 24
+ """Tx"""
+
+ RadiosInUnison = 1 << 25
+ """Btn"""
+
+ RichText = 1 << 25
+ """Tx"""
+
+ CommitOnSelChange = 1 << 26
+ """Ch"""
+
+ @classmethod
+ def attributes(cls) -> Tuple[str, ...]:
+ """
+ Get a tuple of all the attributes present in a Field Dictionary.
+
+ This method returns a tuple of all the attribute constants defined in
+ the FieldDictionaryAttributes class. These attributes correspond to the
+ entries that are common to all field dictionaries as specified in the
+ PDF 1.7 reference.
+
+ Returns:
+ A tuple containing all the attribute constants.
+ """
+ return (
+ cls.TM,
+ cls.T,
+ cls.FT,
+ cls.Parent,
+ cls.TU,
+ cls.Ff,
+ cls.V,
+ cls.DV,
+ cls.Kids,
+ cls.AA,
+ )
+
+ @classmethod
+ def attributes_dict(cls) -> Dict[str, str]:
+ """
+ Get a dictionary of attribute keys and their human-readable names.
+
+ This method returns a dictionary where the keys are the attribute
+ constants defined in the FieldDictionaryAttributes class and the values
+ are their corresponding human-readable names. These attributes
+ correspond to the entries that are common to all field dictionaries as
+ specified in the PDF 1.7 reference.
+
+ Returns:
+ A dictionary containing attribute keys and their names.
+ """
+ return {
+ cls.FT: "Field Type",
+ cls.Parent: "Parent",
+ cls.T: "Field Name",
+ cls.TU: "Alternate Field Name",
+ cls.TM: "Mapping Name",
+ cls.Ff: "Field Flags",
+ cls.V: "Value",
+ cls.DV: "Default Value",
+ }
+
+
+class CheckboxRadioButtonAttributes:
+ """Table 8.76 Field flags common to all field types."""
+
+ Opt = "/Opt" # Options, Optional
+
+ @classmethod
+ def attributes(cls) -> Tuple[str, ...]:
+ """
+ Get a tuple of all the attributes present in a Field Dictionary.
+
+ This method returns a tuple of all the attribute constants defined in
+ the CheckboxRadioButtonAttributes class. These attributes correspond to
+ the entries that are common to all field dictionaries as specified in
+ the PDF 1.7 reference.
+
+ Returns:
+ A tuple containing all the attribute constants.
+ """
+ return (cls.Opt,)
+
+ @classmethod
+ def attributes_dict(cls) -> Dict[str, str]:
+ """
+ Get a dictionary of attribute keys and their human-readable names.
+
+ This method returns a dictionary where the keys are the attribute
+ constants defined in the CheckboxRadioButtonAttributes class and the
+ values are their corresponding human-readable names. These attributes
+ correspond to the entries that are common to all field dictionaries as
+ specified in the PDF 1.7 reference.
+
+ Returns:
+ A dictionary containing attribute keys and their names.
+ """
+ return {
+ cls.Opt: "Options",
+ }
+
+
+class FieldFlag(IntFlag):
+ """Table 8.70 Field flags common to all field types."""
+
+ READ_ONLY = 1
+ REQUIRED = 2
+ NO_EXPORT = 4
+
+
+class DocumentInformationAttributes:
+ """Table 10.2 Entries in the document information dictionary."""
+
+ TITLE = "/Title" # text string, optional
+ AUTHOR = "/Author" # text string, optional
+ SUBJECT = "/Subject" # text string, optional
+ KEYWORDS = "/Keywords" # text string, optional
+ CREATOR = "/Creator" # text string, optional
+ PRODUCER = "/Producer" # text string, optional
+ CREATION_DATE = "/CreationDate" # date, optional
+ MOD_DATE = "/ModDate" # date, optional
+ TRAPPED = "/Trapped" # name, optional
+
+
+class PageLayouts:
+ """
+ Page 84, PDF 1.4 reference.
+ Page 115, PDF 2.0 reference.
+ """
+
+ SINGLE_PAGE = "/SinglePage"
+ ONE_COLUMN = "/OneColumn"
+ TWO_COLUMN_LEFT = "/TwoColumnLeft"
+ TWO_COLUMN_RIGHT = "/TwoColumnRight"
+ TWO_PAGE_LEFT = "/TwoPageLeft" # (PDF 1.5)
+ TWO_PAGE_RIGHT = "/TwoPageRight" # (PDF 1.5)
+
+
+class GraphicsStateParameters:
+ """Table 58 – Entries in a Graphics State Parameter Dictionary"""
+
+ TYPE = "/Type" # name, optional
+ LW = "/LW" # number, optional
+ LC = "/LC" # integer, optional
+ LJ = "/LJ" # integer, optional
+ ML = "/ML" # number, optional
+ D = "/D" # array, optional
+ RI = "/RI" # name, optional
+ OP = "/OP"
+ op = "/op"
+ OPM = "/OPM"
+ FONT = "/Font" # array, optional
+ BG = "/BG"
+ BG2 = "/BG2"
+ UCR = "/UCR"
+ UCR2 = "/UCR2"
+ TR = "/TR"
+ TR2 = "/TR2"
+ HT = "/HT"
+ FL = "/FL"
+ SM = "/SM"
+ SA = "/SA"
+ BM = "/BM"
+ S_MASK = "/SMask" # dictionary or name, optional
+ CA = "/CA"
+ ca = "/ca"
+ AIS = "/AIS"
+ TK = "/TK"
+
+
+class CatalogDictionary:
+ """§7.7.2 of the 1.7 and 2.0 references."""
+
+ TYPE = "/Type" # name, required; must be /Catalog
+ VERSION = "/Version" # name
+ EXTENSIONS = "/Extensions" # dictionary, optional; ISO 32000-1
+ PAGES = "/Pages" # dictionary, required
+ PAGE_LABELS = "/PageLabels" # number tree, optional
+ NAMES = "/Names" # dictionary, optional
+ DESTS = "/Dests" # dictionary, optional
+ VIEWER_PREFERENCES = "/ViewerPreferences" # dictionary, optional
+ PAGE_LAYOUT = "/PageLayout" # name, optional
+ PAGE_MODE = "/PageMode" # name, optional
+ OUTLINES = "/Outlines" # dictionary, optional
+ THREADS = "/Threads" # array, optional
+ OPEN_ACTION = "/OpenAction" # array or dictionary or name, optional
+ AA = "/AA" # dictionary, optional
+ URI = "/URI" # dictionary, optional
+ ACRO_FORM = "/AcroForm" # dictionary, optional
+ METADATA = "/Metadata" # stream, optional
+ STRUCT_TREE_ROOT = "/StructTreeRoot" # dictionary, optional
+ MARK_INFO = "/MarkInfo" # dictionary, optional
+ LANG = "/Lang" # text string, optional
+ SPIDER_INFO = "/SpiderInfo" # dictionary, optional
+ OUTPUT_INTENTS = "/OutputIntents" # array, optional
+ PIECE_INFO = "/PieceInfo" # dictionary, optional
+ OC_PROPERTIES = "/OCProperties" # dictionary, optional
+ PERMS = "/Perms" # dictionary, optional
+ LEGAL = "/Legal" # dictionary, optional
+ REQUIREMENTS = "/Requirements" # array, optional
+ COLLECTION = "/Collection" # dictionary, optional
+ NEEDS_RENDERING = "/NeedsRendering" # boolean, optional
+ DSS = "/DSS" # dictionary, optional
+ AF = "/AF" # array of dictionaries, optional
+ D_PART_ROOT = "/DPartRoot" # dictionary, optional
+
+
+class OutlineFontFlag(IntFlag):
+ """A class used as an enumerable flag for formatting an outline font."""
+
+ italic = 1
+ bold = 2
+
+
+class PageLabelStyle:
+ """
+ Table 8.10 in the 1.7 reference.
+ Table 161 in the 2.0 reference.
+ """
+
+ DECIMAL = "/D" # Decimal Arabic numerals
+ UPPERCASE_ROMAN = "/R" # Uppercase Roman numerals
+ LOWERCASE_ROMAN = "/r" # Lowercase Roman numerals
+ UPPERCASE_LETTER = "/A" # Uppercase letters
+ LOWERCASE_LETTER = "/a" # Lowercase letters
+
+
+class AnnotationFlag(IntFlag):
+ """See §12.5.3 "Annotation Flags"."""
+
+ INVISIBLE = 1
+ HIDDEN = 2
+ PRINT = 4
+ NO_ZOOM = 8
+ NO_ROTATE = 16
+ NO_VIEW = 32
+ READ_ONLY = 64
+ LOCKED = 128
+ TOGGLE_NO_VIEW = 256
+ LOCKED_CONTENTS = 512
+
+
+PDF_KEYS = (
+ AnnotationDictionaryAttributes,
+ CatalogAttributes,
+ CatalogDictionary,
+ CcittFaxDecodeParameters,
+ CheckboxRadioButtonAttributes,
+ ColorSpaces,
+ Core,
+ DocumentInformationAttributes,
+ EncryptionDictAttributes,
+ FieldDictionaryAttributes,
+ FilterTypeAbbreviations,
+ FilterTypes,
+ GoToActionArguments,
+ GraphicsStateParameters,
+ ImageAttributes,
+ FileSpecificationDictionaryEntries,
+ LzwFilterParameters,
+ PageAttributes,
+ PageLayouts,
+ PagesAttributes,
+ Resources,
+ StreamAttributes,
+ TrailerKeys,
+ TypArguments,
+ TypFitArguments,
+)
+
+
+class ImageType(IntFlag):
+ NONE = 0
+ XOBJECT_IMAGES = auto()
+ INLINE_IMAGES = auto()
+ DRAWING_IMAGES = auto()
+ ALL = XOBJECT_IMAGES | INLINE_IMAGES | DRAWING_IMAGES
+ IMAGES = ALL # for consistency with ObjectDeletionFlag
diff --git a/.venv/lib/python3.12/site-packages/pypdf/errors.py b/.venv/lib/python3.12/site-packages/pypdf/errors.py
new file mode 100644
index 00000000..c962dec6
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/pypdf/errors.py
@@ -0,0 +1,62 @@
+"""
+All errors/exceptions pypdf raises and all of the warnings it uses.
+
+Please note that broken PDF files might cause other Exceptions.
+"""
+
+
+class DeprecationError(Exception):
+ """Raised when a deprecated feature is used."""
+
+
+class DependencyError(Exception):
+ """
+ Raised when a required dependency (a library or module that PyPDF depends on)
+ is not available or cannot be imported.
+ """
+
+
+class PyPdfError(Exception):
+ """Base class for all exceptions raised by PyPDF."""
+
+
+class PdfReadError(PyPdfError):
+ """Raised when there is an issue reading a PDF file."""
+
+
+class PageSizeNotDefinedError(PyPdfError):
+ """Raised when the page size of a PDF document is not defined."""
+
+
+class PdfReadWarning(UserWarning):
+ """Issued when there is a potential issue reading a PDF file, but it can still be read."""
+
+
+class PdfStreamError(PdfReadError):
+ """Raised when there is an issue reading the stream of data in a PDF file."""
+
+
+class ParseError(PyPdfError):
+ """
+ Raised when there is an issue parsing (analyzing and understanding the
+ structure and meaning of) a PDF file.
+ """
+
+
+class FileNotDecryptedError(PdfReadError):
+ """
+ Raised when a PDF file that has been encrypted
+ (meaning it requires a password to be accessed) has not been successfully
+ decrypted.
+ """
+
+
+class WrongPasswordError(FileNotDecryptedError):
+ """Raised when the wrong password is used to try to decrypt an encrypted PDF file."""
+
+
+class EmptyFileError(PdfReadError):
+ """Raised when a PDF file is empty or has no content."""
+
+
+STREAM_TRUNCATED_PREMATURELY = "Stream has ended unexpectedly"
diff --git a/.venv/lib/python3.12/site-packages/pypdf/filters.py b/.venv/lib/python3.12/site-packages/pypdf/filters.py
new file mode 100644
index 00000000..5e6a10f7
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/pypdf/filters.py
@@ -0,0 +1,910 @@
+# Copyright (c) 2006, Mathieu Fenniak
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
+#
+# * Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
+# * Redistributions in binary form must reproduce the above copyright notice,
+# this list of conditions and the following disclaimer in the documentation
+# and/or other materials provided with the distribution.
+# * The name of the author may not be used to endorse or promote products
+# derived from this software without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+# POSSIBILITY OF SUCH DAMAGE.
+
+
+"""
+Implementation of stream filters for PDF.
+
+See TABLE H.1 Abbreviations for standard filter names
+"""
+__author__ = "Mathieu Fenniak"
+__author_email__ = "biziqe@mathieu.fenniak.net"
+
+import math
+import struct
+import zlib
+from base64 import a85decode
+from io import BytesIO
+from typing import Any, Dict, List, Optional, Tuple, Union, cast
+
+from ._utils import (
+ WHITESPACES_AS_BYTES,
+ b_,
+ deprecate_with_replacement,
+ deprecation_no_replacement,
+ logger_warning,
+ ord_,
+)
+from .constants import CcittFaxDecodeParameters as CCITT
+from .constants import ColorSpaces
+from .constants import FilterTypeAbbreviations as FTA
+from .constants import FilterTypes as FT
+from .constants import ImageAttributes as IA
+from .constants import LzwFilterParameters as LZW
+from .constants import StreamAttributes as SA
+from .errors import DeprecationError, PdfReadError, PdfStreamError
+from .generic import (
+ ArrayObject,
+ DictionaryObject,
+ IndirectObject,
+ NullObject,
+)
+
+
+def decompress(data: bytes) -> bytes:
+ """
+ Decompress the given data using zlib.
+
+ This function attempts to decompress the input data using zlib. If the
+ decompression fails due to a zlib error, it falls back to using a
+ decompression object with a larger window size.
+
+ Args:
+ data: The input data to be decompressed.
+
+ Returns:
+ The decompressed data.
+ """
+ try:
+ return zlib.decompress(data)
+ except zlib.error:
+ try:
+ # For larger files, use Decompress object to enable buffered reading
+ return zlib.decompressobj().decompress(data)
+ except zlib.error:
+ # If still failed, then try with increased window size
+ d = zlib.decompressobj(zlib.MAX_WBITS | 32)
+ result_str = b""
+ for b in [data[i : i + 1] for i in range(len(data))]:
+ try:
+ result_str += d.decompress(b)
+ except zlib.error:
+ pass
+ return result_str
+
+
+class FlateDecode:
+ @staticmethod
+ def decode(
+ data: bytes,
+ decode_parms: Optional[DictionaryObject] = None,
+ **kwargs: Any,
+ ) -> bytes:
+ """
+ Decode data which is flate-encoded.
+
+ Args:
+ data: flate-encoded data.
+ decode_parms: a dictionary of values, understanding the
+ "/Predictor":<int> key only
+
+ Returns:
+ The flate-decoded data.
+
+ Raises:
+ PdfReadError:
+ """
+ if "decodeParms" in kwargs: # deprecated
+ deprecate_with_replacement("decodeParms", "parameters", "4.0.0")
+ decode_parms = kwargs["decodeParms"]
+ if isinstance(decode_parms, ArrayObject):
+ raise DeprecationError("decode_parms as ArrayObject is depreciated")
+
+ str_data = decompress(data)
+ predictor = 1
+
+ if decode_parms:
+ try:
+ predictor = decode_parms.get("/Predictor", 1)
+ except (AttributeError, TypeError): # Type Error is NullObject
+ pass # Usually an array with a null object was read
+ # predictor 1 == no predictor
+ if predictor != 1:
+ # /Columns, the number of samples in each row, has a default value of 1;
+ # §7.4.4.3, ISO 32000.
+ DEFAULT_BITS_PER_COMPONENT = 8
+ try:
+ columns = cast(int, decode_parms[LZW.COLUMNS].get_object()) # type: ignore
+ except (TypeError, KeyError):
+ columns = 1
+ try:
+ colors = cast(int, decode_parms[LZW.COLORS].get_object()) # type: ignore
+ except (TypeError, KeyError):
+ colors = 1
+ try:
+ bits_per_component = cast(
+ int,
+ decode_parms[LZW.BITS_PER_COMPONENT].get_object(), # type: ignore
+ )
+ except (TypeError, KeyError):
+ bits_per_component = DEFAULT_BITS_PER_COMPONENT
+
+ # PNG predictor can vary by row and so is the lead byte on each row
+ rowlength = (
+ math.ceil(columns * colors * bits_per_component / 8) + 1
+ ) # number of bytes
+
+ # TIFF prediction:
+ if predictor == 2:
+ rowlength -= 1 # remove the predictor byte
+ bpp = rowlength // columns
+ str_data = bytearray(str_data)
+ for i in range(len(str_data)):
+ if i % rowlength >= bpp:
+ str_data[i] = (str_data[i] + str_data[i - bpp]) % 256
+ str_data = bytes(str_data)
+ # PNG prediction:
+ elif 10 <= predictor <= 15:
+ str_data = FlateDecode._decode_png_prediction(
+ str_data, columns, rowlength
+ )
+ else:
+ # unsupported predictor
+ raise PdfReadError(f"Unsupported flatedecode predictor {predictor!r}")
+ return str_data
+
+ @staticmethod
+ def _decode_png_prediction(data: bytes, columns: int, rowlength: int) -> bytes:
+ # PNG prediction can vary from row to row
+ if len(data) % rowlength != 0:
+ raise PdfReadError("Image data is not rectangular")
+ output = []
+ prev_rowdata = (0,) * rowlength
+ bpp = (rowlength - 1) // columns # recomputed locally to not change params
+ for row in range(0, len(data), rowlength):
+ rowdata: List[int] = list(data[row : row + rowlength])
+ filter_byte = rowdata[0]
+
+ if filter_byte == 0:
+ pass
+ elif filter_byte == 1:
+ for i in range(bpp + 1, rowlength):
+ rowdata[i] = (rowdata[i] + rowdata[i - bpp]) % 256
+ elif filter_byte == 2:
+ for i in range(1, rowlength):
+ rowdata[i] = (rowdata[i] + prev_rowdata[i]) % 256
+ elif filter_byte == 3:
+ for i in range(1, bpp + 1):
+ # left = 0
+ floor = prev_rowdata[i] // 2
+ rowdata[i] = (rowdata[i] + floor) % 256
+ for i in range(bpp + 1, rowlength):
+ left = rowdata[i - bpp]
+ floor = (left + prev_rowdata[i]) // 2
+ rowdata[i] = (rowdata[i] + floor) % 256
+ elif filter_byte == 4:
+ for i in range(1, bpp + 1):
+ # left = 0
+ up = prev_rowdata[i]
+ # up_left = 0
+ paeth = up
+ rowdata[i] = (rowdata[i] + paeth) % 256
+ for i in range(bpp + 1, rowlength):
+ left = rowdata[i - bpp]
+ up = prev_rowdata[i]
+ up_left = prev_rowdata[i - bpp]
+
+ p = left + up - up_left
+ dist_left = abs(p - left)
+ dist_up = abs(p - up)
+ dist_up_left = abs(p - up_left)
+
+ if dist_left <= dist_up and dist_left <= dist_up_left:
+ paeth = left
+ elif dist_up <= dist_up_left:
+ paeth = up
+ else:
+ paeth = up_left
+
+ rowdata[i] = (rowdata[i] + paeth) % 256
+ else:
+ # unsupported PNG filter
+ raise PdfReadError(
+ f"Unsupported PNG filter {filter_byte!r}"
+ ) # pragma: no cover
+ prev_rowdata = tuple(rowdata)
+ output.extend(rowdata[1:])
+ return bytes(output)
+
+ @staticmethod
+ def encode(data: bytes, level: int = -1) -> bytes:
+ """
+ Compress the input data using zlib.
+
+ Args:
+ data: The data to be compressed.
+ level: See https://docs.python.org/3/library/zlib.html#zlib.compress
+
+ Returns:
+ The compressed data.
+ """
+ return zlib.compress(data, level)
+
+
+class ASCIIHexDecode:
+ """
+ The ASCIIHexDecode filter decodes data that has been encoded in ASCII
+ hexadecimal form into a base-7 ASCII format.
+ """
+
+ @staticmethod
+ def decode(
+ data: Union[str, bytes],
+ decode_parms: Optional[DictionaryObject] = None,
+ **kwargs: Any,
+ ) -> bytes:
+ """
+ Decode an ASCII-Hex encoded data stream.
+
+ Args:
+ data: a str sequence of hexadecimal-encoded values to be
+ converted into a base-7 ASCII string
+ decode_parms: a string conversion in base-7 ASCII, where each of its values
+ v is such that 0 <= ord(v) <= 127.
+
+ Returns:
+ A string conversion in base-7 ASCII, where each of its values
+ v is such that 0 <= ord(v) <= 127.
+
+ Raises:
+ PdfStreamError:
+ """
+ # decode_parms is unused here
+
+ if isinstance(data, str):
+ data = data.encode()
+ retval = b""
+ hex_pair = b""
+ index = 0
+ while True:
+ if index >= len(data):
+ logger_warning(
+ "missing EOD in ASCIIHexDecode, check if output is OK", __name__
+ )
+ break # reach End Of String even if no EOD
+ char = data[index : index + 1]
+ if char == b">":
+ break
+ elif char.isspace():
+ index += 1
+ continue
+ hex_pair += char
+ if len(hex_pair) == 2:
+ retval += bytes((int(hex_pair, base=16),))
+ hex_pair = b""
+ index += 1
+ assert hex_pair == b""
+ return retval
+
+
+class RunLengthDecode:
+ """
+ The RunLengthDecode filter decodes data that has been encoded in a
+ simple byte-oriented format based on run length.
+ The encoded data is a sequence of runs, where each run consists of
+ a length byte followed by 1 to 128 bytes of data. If the length byte is
+ in the range 0 to 127,
+ the following length + 1 (1 to 128) bytes are copied literally during
+ decompression.
+ If length is in the range 129 to 255, the following single byte is to be
+ copied 257 − length (2 to 128) times during decompression. A length value
+ of 128 denotes EOD.
+ """
+
+ @staticmethod
+ def decode(
+ data: bytes,
+ decode_parms: Optional[DictionaryObject] = None,
+ **kwargs: Any,
+ ) -> bytes:
+ """
+ Decode a run length encoded data stream.
+
+ Args:
+ data: a bytes sequence of length/data
+ decode_parms: ignored.
+
+ Returns:
+ A bytes decompressed sequence.
+
+ Raises:
+ PdfStreamError:
+ """
+ # decode_parms is unused here
+
+ lst = []
+ index = 0
+ while True:
+ if index >= len(data):
+ logger_warning(
+ "missing EOD in RunLengthDecode, check if output is OK", __name__
+ )
+ break # reach End Of String even if no EOD
+ length = data[index]
+ index += 1
+ if length == 128:
+ if index < len(data):
+ raise PdfStreamError("early EOD in RunLengthDecode")
+ else:
+ break
+ elif length < 128:
+ length += 1
+ lst.append(data[index : (index + length)])
+ index += length
+ else: # >128
+ length = 257 - length
+ lst.append(bytes((data[index],)) * length)
+ index += 1
+ return b"".join(lst)
+
+
+class LZWDecode:
+ """
+ Taken from:
+
+ http://www.java2s.com/Open-Source/Java-Document/PDF/PDF-
+ Renderer/com/sun/pdfview/decode/LZWDecode.java.htm
+ """
+
+ class Decoder:
+ def __init__(self, data: bytes) -> None:
+ self.STOP = 257
+ self.CLEARDICT = 256
+ self.data = data
+ self.bytepos = 0
+ self.bitpos = 0
+ self.dict = [""] * 4096
+ for i in range(256):
+ self.dict[i] = chr(i)
+ self.reset_dict()
+
+ def reset_dict(self) -> None:
+ self.dictlen = 258
+ self.bitspercode = 9
+
+ def next_code(self) -> int:
+ fillbits = self.bitspercode
+ value = 0
+ while fillbits > 0:
+ if self.bytepos >= len(self.data):
+ return -1
+ nextbits = ord_(self.data[self.bytepos])
+ bitsfromhere = 8 - self.bitpos
+ bitsfromhere = min(bitsfromhere, fillbits)
+ value |= (
+ (nextbits >> (8 - self.bitpos - bitsfromhere))
+ & (0xFF >> (8 - bitsfromhere))
+ ) << (fillbits - bitsfromhere)
+ fillbits -= bitsfromhere
+ self.bitpos += bitsfromhere
+ if self.bitpos >= 8:
+ self.bitpos = 0
+ self.bytepos = self.bytepos + 1
+ return value
+
+ def decode(self) -> str:
+ """
+ TIFF 6.0 specification explains in sufficient details the steps to
+ implement the LZW encode() and decode() algorithms.
+
+ algorithm derived from:
+ http://www.rasip.fer.hr/research/compress/algorithms/fund/lz/lzw.html
+ and the PDFReference
+
+ Raises:
+ PdfReadError: If the stop code is missing
+ """
+ cW = self.CLEARDICT
+ baos = ""
+ while True:
+ pW = cW
+ cW = self.next_code()
+ if cW == -1:
+ raise PdfReadError("Missed the stop code in LZWDecode!")
+ if cW == self.STOP:
+ break
+ elif cW == self.CLEARDICT:
+ self.reset_dict()
+ elif pW == self.CLEARDICT:
+ baos += self.dict[cW]
+ else:
+ if cW < self.dictlen:
+ baos += self.dict[cW]
+ p = self.dict[pW] + self.dict[cW][0]
+ self.dict[self.dictlen] = p
+ self.dictlen += 1
+ else:
+ p = self.dict[pW] + self.dict[pW][0]
+ baos += p
+ self.dict[self.dictlen] = p
+ self.dictlen += 1
+ if (
+ self.dictlen >= (1 << self.bitspercode) - 1
+ and self.bitspercode < 12
+ ):
+ self.bitspercode += 1
+ return baos
+
+ @staticmethod
+ def decode(
+ data: bytes,
+ decode_parms: Optional[DictionaryObject] = None,
+ **kwargs: Any,
+ ) -> str:
+ """
+ Decode an LZW encoded data stream.
+
+ Args:
+ data: ``bytes`` or ``str`` text to decode.
+ decode_parms: a dictionary of parameter values.
+
+ Returns:
+ decoded data.
+ """
+ # decode_parms is unused here
+
+ return LZWDecode.Decoder(data).decode()
+
+
+class ASCII85Decode:
+ """Decodes string ASCII85-encoded data into a byte format."""
+
+ @staticmethod
+ def decode(
+ data: Union[str, bytes],
+ decode_parms: Optional[DictionaryObject] = None,
+ **kwargs: Any,
+ ) -> bytes:
+ """
+ Decode an Ascii85 encoded data stream.
+
+ Args:
+ data: ``bytes`` or ``str`` text to decode.
+ decode_parms: a dictionary of parameter values.
+
+ Returns:
+ decoded data.
+ """
+ if isinstance(data, str):
+ data = data.encode()
+ data = data.strip(WHITESPACES_AS_BYTES)
+ return a85decode(data, adobe=True, ignorechars=WHITESPACES_AS_BYTES)
+
+
+class DCTDecode:
+ @staticmethod
+ def decode(
+ data: bytes,
+ decode_parms: Optional[DictionaryObject] = None,
+ **kwargs: Any,
+ ) -> bytes:
+ # decode_parms is unused here
+ return data
+
+
+class JPXDecode:
+ @staticmethod
+ def decode(
+ data: bytes,
+ decode_parms: Optional[DictionaryObject] = None,
+ **kwargs: Any,
+ ) -> bytes:
+ # decode_parms is unused here
+ return data
+
+
+class CCITParameters:
+ """§7.4.6, optional parameters for the CCITTFaxDecode filter."""
+
+ def __init__(self, K: int = 0, columns: int = 0, rows: int = 0) -> None:
+ self.K = K
+ self.EndOfBlock = None
+ self.EndOfLine = None
+ self.EncodedByteAlign = None
+ self.columns = columns # width
+ self.rows = rows # height
+ self.DamagedRowsBeforeError = None
+
+ @property
+ def group(self) -> int:
+ if self.K < 0:
+ CCITTgroup = 4
+ else:
+ # k == 0: Pure one-dimensional encoding (Group 3, 1-D)
+ # k > 0: Mixed one- and two-dimensional encoding (Group 3, 2-D)
+ CCITTgroup = 3
+ return CCITTgroup
+
+
+class CCITTFaxDecode:
+ """
+ §7.4.6, CCITTFaxDecode filter (ISO 32000).
+
+ Either Group 3 or Group 4 CCITT facsimile (fax) encoding.
+ CCITT encoding is bit-oriented, not byte-oriented.
+
+ §7.4.6, optional parameters for the CCITTFaxDecode filter.
+ """
+
+ @staticmethod
+ def _get_parameters(
+ parameters: Union[None, ArrayObject, DictionaryObject, IndirectObject],
+ rows: int,
+ ) -> CCITParameters:
+ # §7.4.6, optional parameters for the CCITTFaxDecode filter
+ k = 0
+ columns = 1728
+ if parameters:
+ parameters_unwrapped = cast(
+ Union[ArrayObject, DictionaryObject], parameters.get_object()
+ )
+ if isinstance(parameters_unwrapped, ArrayObject):
+ for decode_parm in parameters_unwrapped:
+ if CCITT.COLUMNS in decode_parm:
+ columns = decode_parm[CCITT.COLUMNS]
+ if CCITT.K in decode_parm:
+ k = decode_parm[CCITT.K]
+ else:
+ if CCITT.COLUMNS in parameters_unwrapped:
+ columns = parameters_unwrapped[CCITT.COLUMNS] # type: ignore
+ if CCITT.K in parameters_unwrapped:
+ k = parameters_unwrapped[CCITT.K] # type: ignore
+
+ return CCITParameters(k, columns, rows)
+
+ @staticmethod
+ def decode(
+ data: bytes,
+ decode_parms: Optional[DictionaryObject] = None,
+ height: int = 0,
+ **kwargs: Any,
+ ) -> bytes:
+ # decode_parms is unused here
+ if "decodeParms" in kwargs: # deprecated
+ deprecate_with_replacement("decodeParms", "parameters", "4.0.0")
+ decode_parms = kwargs["decodeParms"]
+ if isinstance(decode_parms, ArrayObject): # deprecated
+ deprecation_no_replacement(
+ "decode_parms being an ArrayObject", removed_in="3.15.5"
+ )
+ params = CCITTFaxDecode._get_parameters(decode_parms, height)
+
+ img_size = len(data)
+ tiff_header_struct = "<2shlh" + "hhll" * 8 + "h"
+ tiff_header = struct.pack(
+ tiff_header_struct,
+ b"II", # Byte order indication: Little endian
+ 42, # Version number (always 42)
+ 8, # Offset to first IFD
+ 8, # Number of tags in IFD
+ 256,
+ 4,
+ 1,
+ params.columns, # ImageWidth, LONG, 1, width
+ 257,
+ 4,
+ 1,
+ params.rows, # ImageLength, LONG, 1, length
+ 258,
+ 3,
+ 1,
+ 1, # BitsPerSample, SHORT, 1, 1
+ 259,
+ 3,
+ 1,
+ params.group, # Compression, SHORT, 1, 4 = CCITT Group 4 fax encoding
+ 262,
+ 3,
+ 1,
+ 0, # Thresholding, SHORT, 1, 0 = WhiteIsZero
+ 273,
+ 4,
+ 1,
+ struct.calcsize(
+ tiff_header_struct
+ ), # StripOffsets, LONG, 1, length of header
+ 278,
+ 4,
+ 1,
+ params.rows, # RowsPerStrip, LONG, 1, length
+ 279,
+ 4,
+ 1,
+ img_size, # StripByteCounts, LONG, 1, size of image
+ 0, # last IFD
+ )
+
+ return tiff_header + data
+
+
+def decode_stream_data(stream: Any) -> Union[bytes, str]: # utils.StreamObject
+ """
+ Decode the stream data based on the specified filters.
+
+ This function decodes the stream data using the filters provided in the
+ stream. It supports various filter types, including FlateDecode,
+ ASCIIHexDecode, RunLengthDecode, LZWDecode, ASCII85Decode, DCTDecode, JPXDecode, and
+ CCITTFaxDecode.
+
+ Args:
+ stream: The input stream object containing the data and filters.
+
+ Returns:
+ The decoded stream data.
+
+ Raises:
+ NotImplementedError: If an unsupported filter type is encountered.
+ """
+ filters = stream.get(SA.FILTER, ())
+ if isinstance(filters, IndirectObject):
+ filters = cast(ArrayObject, filters.get_object())
+ if not isinstance(filters, ArrayObject):
+ # we have a single filter instance
+ filters = (filters,)
+ decodparms = stream.get(SA.DECODE_PARMS, ({},) * len(filters))
+ if not isinstance(decodparms, (list, tuple)):
+ decodparms = (decodparms,)
+ data: bytes = b_(stream._data)
+ # If there is not data to decode we should not try to decode the data.
+ if data:
+ for filter_type, params in zip(filters, decodparms):
+ if isinstance(params, NullObject):
+ params = {}
+ if filter_type in (FT.FLATE_DECODE, FTA.FL):
+ data = FlateDecode.decode(data, params)
+ elif filter_type in (FT.ASCII_HEX_DECODE, FTA.AHx):
+ data = ASCIIHexDecode.decode(data)
+ elif filter_type in (FT.RUN_LENGTH_DECODE, FTA.RL):
+ data = RunLengthDecode.decode(data)
+ elif filter_type in (FT.LZW_DECODE, FTA.LZW):
+ data = LZWDecode.decode(data, params) # type: ignore
+ elif filter_type in (FT.ASCII_85_DECODE, FTA.A85):
+ data = ASCII85Decode.decode(data)
+ elif filter_type == FT.DCT_DECODE:
+ data = DCTDecode.decode(data)
+ elif filter_type == FT.JPX_DECODE:
+ data = JPXDecode.decode(data)
+ elif filter_type == FT.CCITT_FAX_DECODE:
+ height = stream.get(IA.HEIGHT, ())
+ data = CCITTFaxDecode.decode(data, params, height)
+ elif filter_type == "/Crypt":
+ if "/Name" in params or "/Type" in params:
+ raise NotImplementedError(
+ "/Crypt filter with /Name or /Type not supported yet"
+ )
+ else:
+ # Unsupported filter
+ raise NotImplementedError(f"unsupported filter {filter_type}")
+ return data
+
+
+def decodeStreamData(stream: Any) -> Union[str, bytes]: # deprecated
+ """Deprecated. Use decode_stream_data."""
+ deprecate_with_replacement("decodeStreamData", "decode_stream_data", "4.0.0")
+ return decode_stream_data(stream)
+
+
+def _xobj_to_image(x_object_obj: Dict[str, Any]) -> Tuple[Optional[str], bytes, Any]:
+ """
+ Users need to have the pillow package installed.
+
+ It's unclear if pypdf will keep this function here, hence it's private.
+ It might get removed at any point.
+
+ Args:
+ x_object_obj:
+
+ Returns:
+ Tuple[file extension, bytes, PIL.Image.Image]
+ """
+ from ._xobj_image_helpers import (
+ Image,
+ UnidentifiedImageError,
+ _extended_image_frombytes,
+ _get_imagemode,
+ _handle_flate,
+ _handle_jpx,
+ mode_str_type,
+ )
+
+ # for error reporting
+ if (
+ hasattr(x_object_obj, "indirect_reference") and x_object_obj is None
+ ): # pragma: no cover
+ obj_as_text = x_object_obj.indirect_reference.__repr__()
+ else:
+ obj_as_text = x_object_obj.__repr__()
+
+ size = (cast(int, x_object_obj[IA.WIDTH]), cast(int, x_object_obj[IA.HEIGHT]))
+ data = x_object_obj.get_data() # type: ignore
+ if isinstance(data, str): # pragma: no cover
+ data = data.encode()
+ if len(data) % (size[0] * size[1]) == 1 and data[-1] == 0x0A: # ie. '\n'
+ data = data[:-1]
+ colors = x_object_obj.get("/Colors", 1)
+ color_space: Any = x_object_obj.get("/ColorSpace", NullObject()).get_object()
+ if isinstance(color_space, list) and len(color_space) == 1:
+ color_space = color_space[0].get_object()
+ if (
+ IA.COLOR_SPACE in x_object_obj
+ and x_object_obj[IA.COLOR_SPACE] == ColorSpaces.DEVICE_RGB
+ ):
+ # https://pillow.readthedocs.io/en/stable/handbook/concepts.html#modes
+ mode: mode_str_type = "RGB"
+ if x_object_obj.get("/BitsPerComponent", 8) < 8:
+ mode, invert_color = _get_imagemode(
+ f"{x_object_obj.get('/BitsPerComponent', 8)}bit", 0, ""
+ )
+ else:
+ mode, invert_color = _get_imagemode(
+ color_space,
+ 2
+ if (
+ colors == 1
+ and (
+ not isinstance(color_space, NullObject)
+ and "Gray" not in color_space
+ )
+ )
+ else colors,
+ "",
+ )
+ extension = None
+ alpha = None
+ filters = x_object_obj.get(SA.FILTER, NullObject()).get_object()
+ lfilters = filters[-1] if isinstance(filters, list) else filters
+ if lfilters in (FT.FLATE_DECODE, FT.RUN_LENGTH_DECODE):
+ img, image_format, extension, _ = _handle_flate(
+ size,
+ data,
+ mode,
+ color_space,
+ colors,
+ obj_as_text,
+ )
+ elif lfilters in (FT.LZW_DECODE, FT.ASCII_85_DECODE, FT.CCITT_FAX_DECODE):
+ # I'm not sure if the following logic is correct.
+ # There might not be any relationship between the filters and the
+ # extension
+ if lfilters in (FT.LZW_DECODE, FT.CCITT_FAX_DECODE):
+ extension = ".tiff" # mime_type = "image/tiff"
+ image_format = "TIFF"
+ else:
+ extension = ".png" # mime_type = "image/png"
+ image_format = "PNG"
+ try:
+ img = Image.open(BytesIO(data), formats=("TIFF", "PNG"))
+ except UnidentifiedImageError:
+ img = _extended_image_frombytes(mode, size, data)
+ elif lfilters == FT.DCT_DECODE:
+ img, image_format, extension = Image.open(BytesIO(data)), "JPEG", ".jpg"
+ # invert_color kept unchanged
+ elif lfilters == FT.JPX_DECODE:
+ img, image_format, extension, invert_color = _handle_jpx(
+ size, data, mode, color_space, colors
+ )
+ elif lfilters == FT.CCITT_FAX_DECODE:
+ img, image_format, extension, invert_color = (
+ Image.open(BytesIO(data), formats=("TIFF",)),
+ "TIFF",
+ ".tiff",
+ False,
+ )
+ elif mode == "CMYK":
+ img, image_format, extension, invert_color = (
+ _extended_image_frombytes(mode, size, data),
+ "TIFF",
+ ".tif",
+ False,
+ )
+ elif mode == "":
+ raise PdfReadError(f"ColorSpace field not found in {x_object_obj}")
+ else:
+ img, image_format, extension, invert_color = (
+ _extended_image_frombytes(mode, size, data),
+ "PNG",
+ ".png",
+ False,
+ )
+ # CMYK image and other colorspaces without decode
+ # requires reverting scale (cf p243,2§ last sentence)
+ decode = x_object_obj.get(
+ IA.DECODE,
+ ([1.0, 0.0] * len(img.getbands()))
+ if (
+ (img.mode == "CMYK" and lfilters in (FT.DCT_DECODE, FT.JPX_DECODE))
+ or (invert_color and img.mode == "L")
+ )
+ else None,
+ )
+ if (
+ isinstance(color_space, ArrayObject)
+ and color_space[0].get_object() == "/Indexed"
+ ):
+ decode = None # decode is meanless of Indexed
+ if (
+ isinstance(color_space, ArrayObject)
+ and color_space[0].get_object() == "/Separation"
+ ):
+ decode = [1.0, 0.0] * len(img.getbands())
+ if decode is not None and not all(decode[i] == i % 2 for i in range(len(decode))):
+ lut: List[int] = []
+ for i in range(0, len(decode), 2):
+ dmin = decode[i]
+ dmax = decode[i + 1]
+ lut.extend(
+ round(255.0 * (j / 255.0 * (dmax - dmin) + dmin)) for j in range(256)
+ )
+ img = img.point(lut)
+
+ if IA.S_MASK in x_object_obj: # add alpha channel
+ alpha = _xobj_to_image(x_object_obj[IA.S_MASK])[2]
+ if img.size != alpha.size:
+ logger_warning(f"image and mask size not matching: {obj_as_text}", __name__)
+ else:
+ # TODO : implement mask
+ if alpha.mode != "L":
+ alpha = alpha.convert("L")
+ if img.mode == "P":
+ img = img.convert("RGB")
+ elif img.mode == "1":
+ img = img.convert("L")
+ img.putalpha(alpha)
+ if "JPEG" in image_format:
+ extension = ".jp2"
+ image_format = "JPEG2000"
+ else:
+ extension = ".png"
+ image_format = "PNG"
+
+ img_byte_arr = BytesIO()
+ try:
+ img.save(img_byte_arr, format=image_format)
+ except OSError: # pragma: no cover # covered with pillow 10.3
+ # in case of we convert to RGBA and then to PNG
+ img1 = img.convert("RGBA")
+ image_format = "PNG"
+ extension = ".png"
+ img_byte_arr = BytesIO()
+ img1.save(img_byte_arr, format=image_format)
+ data = img_byte_arr.getvalue()
+
+ try: # temporary try/except until other fixes of images
+ img = Image.open(BytesIO(data))
+ except Exception:
+ img = None # type: ignore
+ return extension, data, img
diff --git a/.venv/lib/python3.12/site-packages/pypdf/generic/__init__.py b/.venv/lib/python3.12/site-packages/pypdf/generic/__init__.py
new file mode 100644
index 00000000..48045e0a
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/pypdf/generic/__init__.py
@@ -0,0 +1,464 @@
+# Copyright (c) 2006, Mathieu Fenniak
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
+#
+# * Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
+# * Redistributions in binary form must reproduce the above copyright notice,
+# this list of conditions and the following disclaimer in the documentation
+# and/or other materials provided with the distribution.
+# * The name of the author may not be used to endorse or promote products
+# derived from this software without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+# POSSIBILITY OF SUCH DAMAGE.
+
+"""Implementation of generic PDF objects (dictionary, number, string, ...)."""
+__author__ = "Mathieu Fenniak"
+__author_email__ = "biziqe@mathieu.fenniak.net"
+
+from typing import Dict, List, Optional, Tuple, Union
+
+from .._utils import StreamType, deprecate_with_replacement
+from ..constants import OutlineFontFlag
+from ._base import (
+ BooleanObject,
+ ByteStringObject,
+ FloatObject,
+ IndirectObject,
+ NameObject,
+ NullObject,
+ NumberObject,
+ PdfObject,
+ TextStringObject,
+ encode_pdfdocencoding,
+)
+from ._data_structures import (
+ ArrayObject,
+ ContentStream,
+ DecodedStreamObject,
+ Destination,
+ DictionaryObject,
+ EncodedStreamObject,
+ Field,
+ StreamObject,
+ TreeObject,
+ read_object,
+)
+from ._fit import Fit
+from ._outline import OutlineItem
+from ._rectangle import RectangleObject
+from ._utils import (
+ create_string_object,
+ decode_pdfdocencoding,
+ hex_to_rgb,
+ read_hex_string_from_stream,
+ read_string_from_stream,
+)
+from ._viewerpref import ViewerPreferences
+
+
+def readHexStringFromStream(
+ stream: StreamType,
+) -> Union["TextStringObject", "ByteStringObject"]: # deprecated
+ """Deprecated, use read_hex_string_from_stream."""
+ deprecate_with_replacement(
+ "readHexStringFromStream", "read_hex_string_from_stream", "4.0.0"
+ )
+ return read_hex_string_from_stream(stream)
+
+
+def readStringFromStream(
+ stream: StreamType,
+ forced_encoding: Union[None, str, List[str], Dict[int, str]] = None,
+) -> Union["TextStringObject", "ByteStringObject"]: # deprecated
+ """Deprecated, use read_string_from_stream."""
+ deprecate_with_replacement(
+ "readStringFromStream", "read_string_from_stream", "4.0.0"
+ )
+ return read_string_from_stream(stream, forced_encoding)
+
+
+def createStringObject(
+ string: Union[str, bytes],
+ forced_encoding: Union[None, str, List[str], Dict[int, str]] = None,
+) -> Union[TextStringObject, ByteStringObject]: # deprecated
+ """Deprecated, use create_string_object."""
+ deprecate_with_replacement("createStringObject", "create_string_object", "4.0.0")
+ return create_string_object(string, forced_encoding)
+
+
+PAGE_FIT = Fit.fit()
+
+
+class AnnotationBuilder:
+ """
+ The AnnotationBuilder is deprecated.
+
+ Instead, use the annotation classes in pypdf.annotations.
+
+ See `adding PDF annotations <../user/adding-pdf-annotations.html>`_ for
+ its usage combined with PdfWriter.
+ """
+
+ from ..generic._rectangle import RectangleObject
+
+ @staticmethod
+ def text(
+ rect: Union[RectangleObject, Tuple[float, float, float, float]],
+ text: str,
+ open: bool = False,
+ flags: int = 0,
+ ) -> DictionaryObject:
+ """
+ Add text annotation.
+
+ Args:
+ rect: array of four integers ``[xLL, yLL, xUR, yUR]``
+ specifying the clickable rectangular area
+ text: The text that is added to the document
+ open:
+ flags:
+
+ Returns:
+ A dictionary object representing the annotation.
+ """
+ deprecate_with_replacement(
+ "AnnotationBuilder.text", "pypdf.annotations.Text", "4.0.0"
+ )
+ from ..annotations import Text
+
+ return Text(rect=rect, text=text, open=open, flags=flags)
+
+ @staticmethod
+ def free_text(
+ text: str,
+ rect: Union[RectangleObject, Tuple[float, float, float, float]],
+ font: str = "Helvetica",
+ bold: bool = False,
+ italic: bool = False,
+ font_size: str = "14pt",
+ font_color: str = "000000",
+ border_color: Optional[str] = "000000",
+ background_color: Optional[str] = "ffffff",
+ ) -> DictionaryObject:
+ """
+ Add text in a rectangle to a page.
+
+ Args:
+ text: Text to be added
+ rect: array of four integers ``[xLL, yLL, xUR, yUR]``
+ specifying the clickable rectangular area
+ font: Name of the Font, e.g. 'Helvetica'
+ bold: Print the text in bold
+ italic: Print the text in italic
+ font_size: How big the text will be, e.g. '14pt'
+ font_color: Hex-string for the color, e.g. cdcdcd
+ border_color: Hex-string for the border color, e.g. cdcdcd.
+ Use ``None`` for no border.
+ background_color: Hex-string for the background of the annotation,
+ e.g. cdcdcd. Use ``None`` for transparent background.
+
+ Returns:
+ A dictionary object representing the annotation.
+ """
+ deprecate_with_replacement(
+ "AnnotationBuilder.free_text", "pypdf.annotations.FreeText", "4.0.0"
+ )
+ from ..annotations import FreeText
+
+ return FreeText(
+ text=text,
+ rect=rect,
+ font=font,
+ bold=bold,
+ italic=italic,
+ font_size=font_size,
+ font_color=font_color,
+ background_color=background_color,
+ border_color=border_color,
+ )
+
+ @staticmethod
+ def popup(
+ *,
+ rect: Union[RectangleObject, Tuple[float, float, float, float]],
+ flags: int = 0,
+ parent: Optional[DictionaryObject] = None,
+ open: bool = False,
+ ) -> DictionaryObject:
+ """
+ Add a popup to the document.
+
+ Args:
+ rect:
+ Specifies the clickable rectangular area as `[xLL, yLL, xUR, yUR]`
+ flags:
+ 1 - invisible, 2 - hidden, 3 - print, 4 - no zoom,
+ 5 - no rotate, 6 - no view, 7 - read only, 8 - locked,
+ 9 - toggle no view, 10 - locked contents
+ open:
+ Whether the popup should be shown directly (default is False).
+ parent:
+ The contents of the popup. Create this via the AnnotationBuilder.
+
+ Returns:
+ A dictionary object representing the annotation.
+ """
+ deprecate_with_replacement(
+ "AnnotationBuilder.popup", "pypdf.annotations.Popup", "4.0.0"
+ )
+ from ..annotations import Popup
+
+ popup = Popup(rect=rect, open=open, parent=parent)
+ popup.flags = flags # type: ignore
+
+ return popup
+
+ @staticmethod
+ def line(
+ p1: Tuple[float, float],
+ p2: Tuple[float, float],
+ rect: Union[RectangleObject, Tuple[float, float, float, float]],
+ text: str = "",
+ title_bar: Optional[str] = None,
+ ) -> DictionaryObject:
+ """
+ Draw a line on the PDF.
+
+ Args:
+ p1: First point
+ p2: Second point
+ rect: array of four integers ``[xLL, yLL, xUR, yUR]``
+ specifying the clickable rectangular area
+ text: Text to be displayed as the line annotation
+ title_bar: Text to be displayed in the title bar of the
+ annotation; by convention this is the name of the author
+
+ Returns:
+ A dictionary object representing the annotation.
+ """
+ deprecate_with_replacement(
+ "AnnotationBuilder.line", "pypdf.annotations.Line", "4.0.0"
+ )
+ from ..annotations import Line
+
+ return Line(p1=p1, p2=p2, rect=rect, text=text, title_bar=title_bar)
+
+ @staticmethod
+ def polyline(
+ vertices: List[Tuple[float, float]],
+ ) -> DictionaryObject:
+ """
+ Draw a polyline on the PDF.
+
+ Args:
+ vertices: Array specifying the vertices (x, y) coordinates of the poly-line.
+
+ Returns:
+ A dictionary object representing the annotation.
+ """
+ deprecate_with_replacement(
+ "AnnotationBuilder.polyline", "pypdf.annotations.PolyLine", "4.0.0"
+ )
+ from ..annotations import PolyLine
+
+ return PolyLine(vertices=vertices)
+
+ @staticmethod
+ def rectangle(
+ rect: Union[RectangleObject, Tuple[float, float, float, float]],
+ interiour_color: Optional[str] = None,
+ ) -> DictionaryObject:
+ """
+ Draw a rectangle on the PDF.
+
+ This method uses the /Square annotation type of the PDF format.
+
+ Args:
+ rect: array of four integers ``[xLL, yLL, xUR, yUR]``
+ specifying the clickable rectangular area
+ interiour_color: None or hex-string for the color, e.g. cdcdcd
+ If None is used, the interiour is transparent.
+
+ Returns:
+ A dictionary object representing the annotation.
+ """
+ deprecate_with_replacement(
+ "AnnotationBuilder.rectangle", "pypdf.annotations.Rectangle", "4.0.0"
+ )
+ from ..annotations import Rectangle
+
+ return Rectangle(rect=rect, interiour_color=interiour_color)
+
+ @staticmethod
+ def highlight(
+ *,
+ rect: Union[RectangleObject, Tuple[float, float, float, float]],
+ quad_points: ArrayObject,
+ highlight_color: str = "ff0000",
+ printing: bool = False,
+ ) -> DictionaryObject:
+ """
+ Add a highlight annotation to the document.
+
+ Args:
+ rect: Array of four integers ``[xLL, yLL, xUR, yUR]``
+ specifying the highlighted area
+ quad_points: An ArrayObject of 8 FloatObjects. Must match a word or
+ a group of words, otherwise no highlight will be shown.
+ highlight_color: The color used for the highlight.
+ printing: Whether to print out the highlight annotation when the page
+ is printed.
+
+ Returns:
+ A dictionary object representing the annotation.
+ """
+ deprecate_with_replacement(
+ "AnnotationBuilder.highlight", "pypdf.annotations.Highlight", "4.0.0"
+ )
+ from ..annotations import Highlight
+
+ return Highlight(
+ rect=rect, quad_points=quad_points, highlight_color=highlight_color, printing=printing
+ )
+
+ @staticmethod
+ def ellipse(
+ rect: Union[RectangleObject, Tuple[float, float, float, float]],
+ interiour_color: Optional[str] = None,
+ ) -> DictionaryObject:
+ """
+ Draw an ellipse on the PDF.
+
+ This method uses the /Circle annotation type of the PDF format.
+
+ Args:
+ rect: array of four integers ``[xLL, yLL, xUR, yUR]`` specifying
+ the bounding box of the ellipse
+ interiour_color: None or hex-string for the color, e.g. cdcdcd
+ If None is used, the interiour is transparent.
+
+ Returns:
+ A dictionary object representing the annotation.
+ """
+ deprecate_with_replacement(
+ "AnnotationBuilder.ellipse", "pypdf.annotations.Ellipse", "4.0.0"
+ )
+ from ..annotations import Ellipse
+
+ return Ellipse(rect=rect, interiour_color=interiour_color)
+
+ @staticmethod
+ def polygon(vertices: List[Tuple[float, float]]) -> DictionaryObject:
+ deprecate_with_replacement(
+ "AnnotationBuilder.polygon", "pypdf.annotations.Polygon", "4.0.0"
+ )
+ from ..annotations import Polygon
+
+ return Polygon(vertices=vertices)
+
+ from ._fit import DEFAULT_FIT
+
+ @staticmethod
+ def link(
+ rect: Union[RectangleObject, Tuple[float, float, float, float]],
+ border: Optional[ArrayObject] = None,
+ url: Optional[str] = None,
+ target_page_index: Optional[int] = None,
+ fit: Fit = DEFAULT_FIT,
+ ) -> DictionaryObject:
+ """
+ Add a link to the document.
+
+ The link can either be an external link or an internal link.
+
+ An external link requires the URL parameter.
+ An internal link requires the target_page_index, fit, and fit args.
+
+ Args:
+ rect: array of four integers ``[xLL, yLL, xUR, yUR]``
+ specifying the clickable rectangular area
+ border: if provided, an array describing border-drawing
+ properties. See the PDF spec for details. No border will be
+ drawn if this argument is omitted.
+ - horizontal corner radius,
+ - vertical corner radius, and
+ - border width
+ - Optionally: Dash
+ url: Link to a website (if you want to make an external link)
+ target_page_index: index of the page to which the link should go
+ (if you want to make an internal link)
+ fit: Page fit or 'zoom' option.
+
+ Returns:
+ A dictionary object representing the annotation.
+ """
+ deprecate_with_replacement(
+ "AnnotationBuilder.link", "pypdf.annotations.Link", "4.0.0"
+ )
+ from ..annotations import Link
+
+ return Link(
+ rect=rect,
+ border=border,
+ url=url,
+ target_page_index=target_page_index,
+ fit=fit,
+ )
+
+
+__all__ = [
+ # Base types
+ "BooleanObject",
+ "FloatObject",
+ "NumberObject",
+ "NameObject",
+ "IndirectObject",
+ "NullObject",
+ "PdfObject",
+ "TextStringObject",
+ "ByteStringObject",
+ # Annotations
+ "AnnotationBuilder",
+ # Fit
+ "Fit",
+ "PAGE_FIT",
+ # Data structures
+ "ArrayObject",
+ "DictionaryObject",
+ "TreeObject",
+ "StreamObject",
+ "DecodedStreamObject",
+ "EncodedStreamObject",
+ "ContentStream",
+ "RectangleObject",
+ "Field",
+ "Destination",
+ "ViewerPreferences",
+ # --- More specific stuff
+ # Outline
+ "OutlineItem",
+ "OutlineFontFlag",
+ # Data structures core functions
+ "read_object",
+ # Utility functions
+ "create_string_object",
+ "encode_pdfdocencoding",
+ "decode_pdfdocencoding",
+ "hex_to_rgb",
+ "read_hex_string_from_stream",
+ "read_string_from_stream",
+]
diff --git a/.venv/lib/python3.12/site-packages/pypdf/generic/_base.py b/.venv/lib/python3.12/site-packages/pypdf/generic/_base.py
new file mode 100644
index 00000000..2d606b41
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/pypdf/generic/_base.py
@@ -0,0 +1,721 @@
+# Copyright (c) 2006, Mathieu Fenniak
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
+#
+# * Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
+# * Redistributions in binary form must reproduce the above copyright notice,
+# this list of conditions and the following disclaimer in the documentation
+# and/or other materials provided with the distribution.
+# * The name of the author may not be used to endorse or promote products
+# derived from this software without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+# POSSIBILITY OF SUCH DAMAGE.
+import binascii
+import codecs
+import hashlib
+import re
+from binascii import unhexlify
+from math import log10
+from typing import Any, Callable, ClassVar, Dict, Optional, Sequence, Union, cast
+
+from .._codecs import _pdfdoc_encoding_rev
+from .._protocols import PdfObjectProtocol, PdfWriterProtocol
+from .._utils import (
+ StreamType,
+ b_,
+ deprecate_no_replacement,
+ logger_warning,
+ read_non_whitespace,
+ read_until_regex,
+ str_,
+)
+from ..errors import STREAM_TRUNCATED_PREMATURELY, PdfReadError, PdfStreamError
+
+__author__ = "Mathieu Fenniak"
+__author_email__ = "biziqe@mathieu.fenniak.net"
+
+
+class PdfObject(PdfObjectProtocol):
+ # function for calculating a hash value
+ hash_func: Callable[..., "hashlib._Hash"] = hashlib.sha1
+ indirect_reference: Optional["IndirectObject"]
+
+ def hash_value_data(self) -> bytes:
+ return ("%s" % self).encode()
+
+ def hash_value(self) -> bytes:
+ return (
+ "%s:%s"
+ % (
+ self.__class__.__name__,
+ self.hash_func(self.hash_value_data()).hexdigest(),
+ )
+ ).encode()
+
+ def clone(
+ self,
+ pdf_dest: PdfWriterProtocol,
+ force_duplicate: bool = False,
+ ignore_fields: Optional[Sequence[Union[str, int]]] = (),
+ ) -> "PdfObject":
+ """
+ Clone object into pdf_dest (PdfWriterProtocol which is an interface for PdfWriter).
+
+ By default, this method will call ``_reference_clone`` (see ``_reference``).
+
+
+ Args:
+ pdf_dest: Target to clone to.
+ force_duplicate: By default, if the object has already been cloned and referenced,
+ the copy will be returned; when ``True``, a new copy will be created.
+ (Default value = ``False``)
+ ignore_fields: List/tuple of field names (for dictionaries) that will be ignored
+ during cloning (applies to children duplication as well). If fields are to be
+ considered for a limited number of levels, you have to add it as integer, for
+ example ``[1,"/B","/TOTO"]`` means that ``"/B"`` will be ignored at the first
+ level only but ``"/TOTO"`` on all levels.
+
+ Returns:
+ The cloned PdfObject
+ """
+ raise NotImplementedError(
+ f"{self.__class__.__name__} does not implement .clone so far"
+ )
+
+ def _reference_clone(
+ self, clone: Any, pdf_dest: PdfWriterProtocol, force_duplicate: bool = False
+ ) -> PdfObjectProtocol:
+ """
+ Reference the object within the _objects of pdf_dest only if
+ indirect_reference attribute exists (which means the objects was
+ already identified in xref/xobjstm) if object has been already
+ referenced do nothing.
+
+ Args:
+ clone:
+ pdf_dest:
+
+ Returns:
+ The clone
+ """
+ try:
+ if not force_duplicate and clone.indirect_reference.pdf == pdf_dest:
+ return clone
+ except Exception:
+ pass
+ # if hasattr(clone, "indirect_reference"):
+ try:
+ ind = self.indirect_reference
+ except AttributeError:
+ return clone
+ i = len(pdf_dest._objects) + 1
+ if ind is not None:
+ if id(ind.pdf) not in pdf_dest._id_translated:
+ pdf_dest._id_translated[id(ind.pdf)] = {}
+ pdf_dest._id_translated[id(ind.pdf)]["PreventGC"] = ind.pdf # type: ignore
+ if (
+ not force_duplicate
+ and ind.idnum in pdf_dest._id_translated[id(ind.pdf)]
+ ):
+ obj = pdf_dest.get_object(
+ pdf_dest._id_translated[id(ind.pdf)][ind.idnum]
+ )
+ assert obj is not None
+ return obj
+ pdf_dest._id_translated[id(ind.pdf)][ind.idnum] = i
+ pdf_dest._objects.append(clone)
+ clone.indirect_reference = IndirectObject(i, 0, pdf_dest)
+ return clone
+
+ def get_object(self) -> Optional["PdfObject"]:
+ """Resolve indirect references."""
+ return self
+
+ def write_to_stream(
+ self, stream: StreamType, encryption_key: Union[None, str, bytes] = None
+ ) -> None:
+ raise NotImplementedError
+
+
+class NullObject(PdfObject):
+ def clone(
+ self,
+ pdf_dest: PdfWriterProtocol,
+ force_duplicate: bool = False,
+ ignore_fields: Optional[Sequence[Union[str, int]]] = (),
+ ) -> "NullObject":
+ """Clone object into pdf_dest."""
+ return cast(
+ "NullObject", self._reference_clone(NullObject(), pdf_dest, force_duplicate)
+ )
+
+ def write_to_stream(
+ self, stream: StreamType, encryption_key: Union[None, str, bytes] = None
+ ) -> None:
+ if encryption_key is not None: # deprecated
+ deprecate_no_replacement(
+ "the encryption_key parameter of write_to_stream", "5.0.0"
+ )
+ stream.write(b"null")
+
+ @staticmethod
+ def read_from_stream(stream: StreamType) -> "NullObject":
+ nulltxt = stream.read(4)
+ if nulltxt != b"null":
+ raise PdfReadError("Could not read Null object")
+ return NullObject()
+
+ def __repr__(self) -> str:
+ return "NullObject"
+
+
+class BooleanObject(PdfObject):
+ def __init__(self, value: Any) -> None:
+ self.value = value
+
+ def clone(
+ self,
+ pdf_dest: PdfWriterProtocol,
+ force_duplicate: bool = False,
+ ignore_fields: Optional[Sequence[Union[str, int]]] = (),
+ ) -> "BooleanObject":
+ """Clone object into pdf_dest."""
+ return cast(
+ "BooleanObject",
+ self._reference_clone(BooleanObject(self.value), pdf_dest, force_duplicate),
+ )
+
+ def __eq__(self, __o: object) -> bool:
+ if isinstance(__o, BooleanObject):
+ return self.value == __o.value
+ elif isinstance(__o, bool):
+ return self.value == __o
+ else:
+ return False
+
+ def __repr__(self) -> str:
+ return "True" if self.value else "False"
+
+ def write_to_stream(
+ self, stream: StreamType, encryption_key: Union[None, str, bytes] = None
+ ) -> None:
+ if encryption_key is not None: # deprecated
+ deprecate_no_replacement(
+ "the encryption_key parameter of write_to_stream", "5.0.0"
+ )
+ if self.value:
+ stream.write(b"true")
+ else:
+ stream.write(b"false")
+
+ @staticmethod
+ def read_from_stream(stream: StreamType) -> "BooleanObject":
+ word = stream.read(4)
+ if word == b"true":
+ return BooleanObject(True)
+ elif word == b"fals":
+ stream.read(1)
+ return BooleanObject(False)
+ else:
+ raise PdfReadError("Could not read Boolean object")
+
+
+class IndirectObject(PdfObject):
+ def __init__(self, idnum: int, generation: int, pdf: Any) -> None: # PdfReader
+ self.idnum = idnum
+ self.generation = generation
+ self.pdf = pdf
+
+ def clone(
+ self,
+ pdf_dest: PdfWriterProtocol,
+ force_duplicate: bool = False,
+ ignore_fields: Optional[Sequence[Union[str, int]]] = (),
+ ) -> "IndirectObject":
+ """Clone object into pdf_dest."""
+ if self.pdf == pdf_dest and not force_duplicate:
+ # Already duplicated and no extra duplication required
+ return self
+ if id(self.pdf) not in pdf_dest._id_translated:
+ pdf_dest._id_translated[id(self.pdf)] = {}
+
+ if self.idnum in pdf_dest._id_translated[id(self.pdf)]:
+ dup = pdf_dest.get_object(pdf_dest._id_translated[id(self.pdf)][self.idnum])
+ if force_duplicate:
+ assert dup is not None
+ assert dup.indirect_reference is not None
+ idref = dup.indirect_reference
+ return IndirectObject(idref.idnum, idref.generation, idref.pdf)
+ else:
+ obj = self.get_object()
+ # case observed : a pointed object can not be found
+ if obj is None:
+ # this normally
+ obj = NullObject()
+ assert isinstance(self, (IndirectObject,))
+ obj.indirect_reference = self
+ dup = pdf_dest._add_object(
+ obj.clone(pdf_dest, force_duplicate, ignore_fields)
+ )
+ # asserts added to prevent errors in mypy
+ assert dup is not None
+ assert dup.indirect_reference is not None
+ return dup.indirect_reference
+
+ @property
+ def indirect_reference(self) -> "IndirectObject": # type: ignore[override]
+ return self
+
+ def get_object(self) -> Optional["PdfObject"]:
+ return self.pdf.get_object(self)
+
+ def __deepcopy__(self, memo: Any) -> "IndirectObject":
+ return IndirectObject(self.idnum, self.generation, self.pdf)
+
+ def _get_object_with_check(self) -> Optional["PdfObject"]:
+ o = self.get_object()
+ # the check is done here to not slow down get_object()
+ if isinstance(o, IndirectObject):
+ raise PdfStreamError(
+ f"{self.__repr__()} references an IndirectObject {o.__repr__()}"
+ )
+ return o
+
+ def __getattr__(self, name: str) -> Any:
+ # Attribute not found in object: look in pointed object
+ try:
+ return getattr(self._get_object_with_check(), name)
+ except AttributeError:
+ raise AttributeError(
+ f"No attribute {name} found in IndirectObject or pointed object"
+ )
+
+ def __getitem__(self, key: Any) -> Any:
+ # items should be extracted from pointed Object
+ return self._get_object_with_check()[key] # type: ignore
+
+ def __str__(self) -> str:
+ # in this case we are looking for the pointed data
+ return self.get_object().__str__()
+
+ def __repr__(self) -> str:
+ return f"IndirectObject({self.idnum!r}, {self.generation!r}, {id(self.pdf)})"
+
+ def __eq__(self, other: object) -> bool:
+ return (
+ other is not None
+ and isinstance(other, IndirectObject)
+ and self.idnum == other.idnum
+ and self.generation == other.generation
+ and self.pdf is other.pdf
+ )
+
+ def __ne__(self, other: object) -> bool:
+ return not self.__eq__(other)
+
+ def write_to_stream(
+ self, stream: StreamType, encryption_key: Union[None, str, bytes] = None
+ ) -> None:
+ if encryption_key is not None: # deprecated
+ deprecate_no_replacement(
+ "the encryption_key parameter of write_to_stream", "5.0.0"
+ )
+ stream.write(f"{self.idnum} {self.generation} R".encode())
+
+ @staticmethod
+ def read_from_stream(stream: StreamType, pdf: Any) -> "IndirectObject": # PdfReader
+ idnum = b""
+ while True:
+ tok = stream.read(1)
+ if not tok:
+ raise PdfStreamError(STREAM_TRUNCATED_PREMATURELY)
+ if tok.isspace():
+ break
+ idnum += tok
+ generation = b""
+ while True:
+ tok = stream.read(1)
+ if not tok:
+ raise PdfStreamError(STREAM_TRUNCATED_PREMATURELY)
+ if tok.isspace():
+ if not generation:
+ continue
+ break
+ generation += tok
+ r = read_non_whitespace(stream)
+ if r != b"R":
+ raise PdfReadError(
+ f"Error reading indirect object reference at byte {hex(stream.tell())}"
+ )
+ return IndirectObject(int(idnum), int(generation), pdf)
+
+
+FLOAT_WRITE_PRECISION = 8 # shall be min 5 digits max, allow user adj
+
+
+class FloatObject(float, PdfObject):
+ def __new__(
+ cls, value: Union[str, Any] = "0.0", context: Optional[Any] = None
+ ) -> "FloatObject":
+ try:
+ value = float(str_(value))
+ return float.__new__(cls, value)
+ except Exception as e:
+ # If this isn't a valid decimal (happens in malformed PDFs)
+ # fallback to 0
+ logger_warning(
+ f"{e} : FloatObject ({value}) invalid; use 0.0 instead", __name__
+ )
+ return float.__new__(cls, 0.0)
+
+ def clone(
+ self,
+ pdf_dest: Any,
+ force_duplicate: bool = False,
+ ignore_fields: Optional[Sequence[Union[str, int]]] = (),
+ ) -> "FloatObject":
+ """Clone object into pdf_dest."""
+ return cast(
+ "FloatObject",
+ self._reference_clone(FloatObject(self), pdf_dest, force_duplicate),
+ )
+
+ def myrepr(self) -> str:
+ if self == 0:
+ return "0.0"
+ nb = FLOAT_WRITE_PRECISION - int(log10(abs(self)))
+ s = f"{self:.{max(1,nb)}f}".rstrip("0").rstrip(".")
+ return s
+
+ def __repr__(self) -> str:
+ return self.myrepr() # repr(float(self))
+
+ def as_numeric(self) -> float:
+ return float(self)
+
+ def write_to_stream(
+ self, stream: StreamType, encryption_key: Union[None, str, bytes] = None
+ ) -> None:
+ if encryption_key is not None: # deprecated
+ deprecate_no_replacement(
+ "the encryption_key parameter of write_to_stream", "5.0.0"
+ )
+ stream.write(self.myrepr().encode("utf8"))
+
+
+class NumberObject(int, PdfObject):
+ NumberPattern = re.compile(b"[^+-.0-9]")
+
+ def __new__(cls, value: Any) -> "NumberObject":
+ try:
+ return int.__new__(cls, int(value))
+ except ValueError:
+ logger_warning(f"NumberObject({value}) invalid; use 0 instead", __name__)
+ return int.__new__(cls, 0)
+
+ def clone(
+ self,
+ pdf_dest: Any,
+ force_duplicate: bool = False,
+ ignore_fields: Optional[Sequence[Union[str, int]]] = (),
+ ) -> "NumberObject":
+ """Clone object into pdf_dest."""
+ return cast(
+ "NumberObject",
+ self._reference_clone(NumberObject(self), pdf_dest, force_duplicate),
+ )
+
+ def as_numeric(self) -> int:
+ return int(repr(self).encode("utf8"))
+
+ def write_to_stream(
+ self, stream: StreamType, encryption_key: Union[None, str, bytes] = None
+ ) -> None:
+ if encryption_key is not None: # deprecated
+ deprecate_no_replacement(
+ "the encryption_key parameter of write_to_stream", "5.0.0"
+ )
+ stream.write(repr(self).encode("utf8"))
+
+ @staticmethod
+ def read_from_stream(stream: StreamType) -> Union["NumberObject", "FloatObject"]:
+ num = read_until_regex(stream, NumberObject.NumberPattern)
+ if num.find(b".") != -1:
+ return FloatObject(num)
+ return NumberObject(num)
+
+
+class ByteStringObject(bytes, PdfObject):
+ """
+ Represents a string object where the text encoding could not be determined.
+
+ This occurs quite often, as the PDF spec doesn't provide an alternate way to
+ represent strings -- for example, the encryption data stored in files (like
+ /O) is clearly not text, but is still stored in a "String" object.
+ """
+
+ def clone(
+ self,
+ pdf_dest: Any,
+ force_duplicate: bool = False,
+ ignore_fields: Optional[Sequence[Union[str, int]]] = (),
+ ) -> "ByteStringObject":
+ """Clone object into pdf_dest."""
+ return cast(
+ "ByteStringObject",
+ self._reference_clone(
+ ByteStringObject(bytes(self)), pdf_dest, force_duplicate
+ ),
+ )
+
+ @property
+ def original_bytes(self) -> bytes:
+ """For compatibility with TextStringObject.original_bytes."""
+ return self
+
+ def write_to_stream(
+ self, stream: StreamType, encryption_key: Union[None, str, bytes] = None
+ ) -> None:
+ if encryption_key is not None: # deprecated
+ deprecate_no_replacement(
+ "the encryption_key parameter of write_to_stream", "5.0.0"
+ )
+ stream.write(b"<")
+ stream.write(binascii.hexlify(self))
+ stream.write(b">")
+
+
+class TextStringObject(str, PdfObject): # noqa: SLOT000
+ """
+ A string object that has been decoded into a real unicode string.
+
+ If read from a PDF document, this string appeared to match the
+ PDFDocEncoding, or contained a UTF-16BE BOM mark to cause UTF-16 decoding
+ to occur.
+ """
+
+ autodetect_pdfdocencoding: bool
+ autodetect_utf16: bool
+ utf16_bom: bytes
+
+ def __new__(cls, value: Any) -> "TextStringObject":
+ if isinstance(value, bytes):
+ value = value.decode("charmap")
+ o = str.__new__(cls, value)
+ o.autodetect_utf16 = False
+ o.autodetect_pdfdocencoding = False
+ o.utf16_bom = b""
+ if value.startswith(("\xfe\xff", "\xff\xfe")):
+ o.autodetect_utf16 = True
+ o.utf16_bom = value[:2].encode("charmap")
+ else:
+ try:
+ encode_pdfdocencoding(o)
+ o.autodetect_pdfdocencoding = True
+ except UnicodeEncodeError:
+ o.autodetect_utf16 = True
+ return o
+
+ def clone(
+ self,
+ pdf_dest: Any,
+ force_duplicate: bool = False,
+ ignore_fields: Optional[Sequence[Union[str, int]]] = (),
+ ) -> "TextStringObject":
+ """Clone object into pdf_dest."""
+ obj = TextStringObject(self)
+ obj.autodetect_pdfdocencoding = self.autodetect_pdfdocencoding
+ obj.autodetect_utf16 = self.autodetect_utf16
+ obj.utf16_bom = self.utf16_bom
+ return cast(
+ "TextStringObject", self._reference_clone(obj, pdf_dest, force_duplicate)
+ )
+
+ @property
+ def original_bytes(self) -> bytes:
+ """
+ It is occasionally possible that a text string object gets created where
+ a byte string object was expected due to the autodetection mechanism --
+ if that occurs, this "original_bytes" property can be used to
+ back-calculate what the original encoded bytes were.
+ """
+ return self.get_original_bytes()
+
+ def get_original_bytes(self) -> bytes:
+ # We're a text string object, but the library is trying to get our raw
+ # bytes. This can happen if we auto-detected this string as text, but
+ # we were wrong. It's pretty common. Return the original bytes that
+ # would have been used to create this object, based upon the autodetect
+ # method.
+ if self.autodetect_utf16:
+ if self.utf16_bom == codecs.BOM_UTF16_LE:
+ return codecs.BOM_UTF16_LE + self.encode("utf-16le")
+ elif self.utf16_bom == codecs.BOM_UTF16_BE:
+ return codecs.BOM_UTF16_BE + self.encode("utf-16be")
+ else:
+ return self.encode("utf-16be")
+ elif self.autodetect_pdfdocencoding:
+ return encode_pdfdocencoding(self)
+ else:
+ raise Exception("no information about original bytes") # pragma: no cover
+
+ def get_encoded_bytes(self) -> bytes:
+ # Try to write the string out as a PDFDocEncoding encoded string. It's
+ # nicer to look at in the PDF file. Sadly, we take a performance hit
+ # here for trying...
+ try:
+ if self.autodetect_utf16:
+ raise UnicodeEncodeError("", "forced", -1, -1, "")
+ bytearr = encode_pdfdocencoding(self)
+ except UnicodeEncodeError:
+ if self.utf16_bom == codecs.BOM_UTF16_LE:
+ bytearr = codecs.BOM_UTF16_LE + self.encode("utf-16le")
+ elif self.utf16_bom == codecs.BOM_UTF16_BE:
+ bytearr = codecs.BOM_UTF16_BE + self.encode("utf-16be")
+ else:
+ bytearr = self.encode("utf-16be")
+ return bytearr
+
+ def write_to_stream(
+ self, stream: StreamType, encryption_key: Union[None, str, bytes] = None
+ ) -> None:
+ if encryption_key is not None: # deprecated
+ deprecate_no_replacement(
+ "the encryption_key parameter of write_to_stream", "5.0.0"
+ )
+ bytearr = self.get_encoded_bytes()
+ stream.write(b"(")
+ for c in bytearr:
+ if not chr(c).isalnum() and c != b" ":
+ # This:
+ # stream.write(rf"\{c:0>3o}".encode())
+ # gives
+ # https://github.com/davidhalter/parso/issues/207
+ stream.write(("\\%03o" % c).encode())
+ else:
+ stream.write(b_(chr(c)))
+ stream.write(b")")
+
+
+class NameObject(str, PdfObject): # noqa: SLOT000
+ delimiter_pattern = re.compile(rb"\s+|[\(\)<>\[\]{}/%]")
+ surfix = b"/"
+ renumber_table: ClassVar[Dict[str, bytes]] = {
+ "#": b"#23",
+ "(": b"#28",
+ ")": b"#29",
+ "/": b"#2F",
+ "%": b"#25",
+ **{chr(i): f"#{i:02X}".encode() for i in range(33)},
+ }
+
+ def clone(
+ self,
+ pdf_dest: Any,
+ force_duplicate: bool = False,
+ ignore_fields: Optional[Sequence[Union[str, int]]] = (),
+ ) -> "NameObject":
+ """Clone object into pdf_dest."""
+ return cast(
+ "NameObject",
+ self._reference_clone(NameObject(self), pdf_dest, force_duplicate),
+ )
+
+ def write_to_stream(
+ self, stream: StreamType, encryption_key: Union[None, str, bytes] = None
+ ) -> None:
+ if encryption_key is not None: # deprecated
+ deprecate_no_replacement(
+ "the encryption_key parameter of write_to_stream", "5.0.0"
+ )
+ stream.write(self.renumber())
+
+ def renumber(self) -> bytes:
+ out = self[0].encode("utf-8")
+ if out != b"/":
+ deprecate_no_replacement(
+ f"Incorrect first char in NameObject, should start with '/': ({self})",
+ "6.0.0",
+ )
+ for c in self[1:]:
+ if c > "~":
+ for x in c.encode("utf-8"):
+ out += f"#{x:02X}".encode()
+ else:
+ try:
+ out += self.renumber_table[c]
+ except KeyError:
+ out += c.encode("utf-8")
+ return out
+
+ @staticmethod
+ def unnumber(sin: bytes) -> bytes:
+ i = sin.find(b"#", 0)
+ while i >= 0:
+ try:
+ sin = sin[:i] + unhexlify(sin[i + 1 : i + 3]) + sin[i + 3 :]
+ i = sin.find(b"#", i + 1)
+ except ValueError:
+ # if the 2 characters after # can not be converted to hex
+ # we change nothing and carry on
+ i = i + 1
+ return sin
+
+ CHARSETS = ("utf-8", "gbk", "latin1")
+
+ @staticmethod
+ def read_from_stream(stream: StreamType, pdf: Any) -> "NameObject": # PdfReader
+ name = stream.read(1)
+ if name != NameObject.surfix:
+ raise PdfReadError("name read error")
+ name += read_until_regex(stream, NameObject.delimiter_pattern)
+ try:
+ # Name objects should represent irregular characters
+ # with a '#' followed by the symbol's hex number
+ name = NameObject.unnumber(name)
+ for enc in NameObject.CHARSETS:
+ try:
+ ret = name.decode(enc)
+ return NameObject(ret)
+ except Exception:
+ pass
+ raise UnicodeDecodeError("", name, 0, 0, "Code Not Found")
+ except (UnicodeEncodeError, UnicodeDecodeError) as e:
+ if not pdf.strict:
+ logger_warning(
+ f"Illegal character in NameObject ({name!r}), "
+ "you may need to adjust NameObject.CHARSETS",
+ __name__,
+ )
+ return NameObject(name.decode("charmap"))
+ else:
+ raise PdfReadError(
+ f"Illegal character in NameObject ({name!r}). "
+ "You may need to adjust NameObject.CHARSETS.",
+ ) from e
+
+
+def encode_pdfdocencoding(unicode_string: str) -> bytes:
+ retval = bytearray()
+ for c in unicode_string:
+ try:
+ retval += b_(chr(_pdfdoc_encoding_rev[c]))
+ except KeyError:
+ raise UnicodeEncodeError(
+ "pdfdocencoding", c, -1, -1, "does not exist in translation table"
+ )
+ return bytes(retval)
diff --git a/.venv/lib/python3.12/site-packages/pypdf/generic/_data_structures.py b/.venv/lib/python3.12/site-packages/pypdf/generic/_data_structures.py
new file mode 100644
index 00000000..87d68867
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/pypdf/generic/_data_structures.py
@@ -0,0 +1,1616 @@
+# Copyright (c) 2006, Mathieu Fenniak
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
+#
+# * Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
+# * Redistributions in binary form must reproduce the above copyright notice,
+# this list of conditions and the following disclaimer in the documentation
+# and/or other materials provided with the distribution.
+# * The name of the author may not be used to endorse or promote products
+# derived from this software without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+# POSSIBILITY OF SUCH DAMAGE.
+
+
+__author__ = "Mathieu Fenniak"
+__author_email__ = "biziqe@mathieu.fenniak.net"
+
+import logging
+import re
+import sys
+from io import BytesIO
+from math import ceil
+from typing import (
+ Any,
+ Callable,
+ Dict,
+ Iterable,
+ List,
+ Optional,
+ Sequence,
+ Set,
+ Tuple,
+ Union,
+ cast,
+)
+
+from .._protocols import PdfReaderProtocol, PdfWriterProtocol, XmpInformationProtocol
+from .._utils import (
+ WHITESPACES,
+ StreamType,
+ b_,
+ deprecate_no_replacement,
+ deprecate_with_replacement,
+ logger_warning,
+ read_non_whitespace,
+ read_until_regex,
+ skip_over_comment,
+)
+from ..constants import (
+ CheckboxRadioButtonAttributes,
+ FieldDictionaryAttributes,
+ OutlineFontFlag,
+)
+from ..constants import FilterTypes as FT
+from ..constants import StreamAttributes as SA
+from ..constants import TypArguments as TA
+from ..constants import TypFitArguments as TF
+from ..errors import STREAM_TRUNCATED_PREMATURELY, PdfReadError, PdfStreamError
+from ._base import (
+ BooleanObject,
+ ByteStringObject,
+ FloatObject,
+ IndirectObject,
+ NameObject,
+ NullObject,
+ NumberObject,
+ PdfObject,
+ TextStringObject,
+)
+from ._fit import Fit
+from ._image_inline import (
+ extract_inline_A85,
+ extract_inline_AHx,
+ extract_inline_DCT,
+ extract_inline_default,
+ extract_inline_RL,
+)
+from ._utils import read_hex_string_from_stream, read_string_from_stream
+
+if sys.version_info >= (3, 11):
+ from typing import Self
+else:
+ from typing_extensions import Self
+
+logger = logging.getLogger(__name__)
+NumberSigns = b"+-"
+IndirectPattern = re.compile(rb"[+-]?(\d+)\s+(\d+)\s+R[^a-zA-Z]")
+
+
+class ArrayObject(List[Any], PdfObject):
+ def clone(
+ self,
+ pdf_dest: PdfWriterProtocol,
+ force_duplicate: bool = False,
+ ignore_fields: Optional[Sequence[Union[str, int]]] = (),
+ ) -> "ArrayObject":
+ """Clone object into pdf_dest."""
+ try:
+ if self.indirect_reference.pdf == pdf_dest and not force_duplicate: # type: ignore
+ return self
+ except Exception:
+ pass
+ arr = cast(
+ "ArrayObject",
+ self._reference_clone(ArrayObject(), pdf_dest, force_duplicate),
+ )
+ for data in self:
+ if isinstance(data, StreamObject):
+ dup = data._reference_clone(
+ data.clone(pdf_dest, force_duplicate, ignore_fields),
+ pdf_dest,
+ force_duplicate,
+ )
+ arr.append(dup.indirect_reference)
+ elif hasattr(data, "clone"):
+ arr.append(data.clone(pdf_dest, force_duplicate, ignore_fields))
+ else:
+ arr.append(data)
+ return arr
+
+ def items(self) -> Iterable[Any]:
+ """Emulate DictionaryObject.items for a list (index, object)."""
+ return enumerate(self)
+
+ def _to_lst(self, lst: Any) -> List[Any]:
+ # Convert to list, internal
+ if isinstance(lst, (list, tuple, set)):
+ pass
+ elif isinstance(lst, PdfObject):
+ lst = [lst]
+ elif isinstance(lst, str):
+ if lst[0] == "/":
+ lst = [NameObject(lst)]
+ else:
+ lst = [TextStringObject(lst)]
+ elif isinstance(lst, bytes):
+ lst = [ByteStringObject(lst)]
+ else: # for numbers,...
+ lst = [lst]
+ return lst
+
+ def __add__(self, lst: Any) -> "ArrayObject":
+ """
+ Allow extension by adding list or add one element only
+
+ Args:
+ lst: any list, tuples are extended the list.
+ other types(numbers,...) will be appended.
+ if str is passed it will be converted into TextStringObject
+ or NameObject (if starting with "/")
+ if bytes is passed it will be converted into ByteStringObject
+
+ Returns:
+ ArrayObject with all elements
+ """
+ temp = ArrayObject(self)
+ temp.extend(self._to_lst(lst))
+ return temp
+
+ def __iadd__(self, lst: Any) -> Self:
+ """
+ Allow extension by adding list or add one element only
+
+ Args:
+ lst: any list, tuples are extended the list.
+ other types(numbers,...) will be appended.
+ if str is passed it will be converted into TextStringObject
+ or NameObject (if starting with "/")
+ if bytes is passed it will be converted into ByteStringObject
+ """
+ self.extend(self._to_lst(lst))
+ return self
+
+ def __isub__(self, lst: Any) -> Self:
+ """Allow to remove items"""
+ for x in self._to_lst(lst):
+ try:
+ x = self.index(x)
+ del self[x]
+ except ValueError:
+ pass
+ return self
+
+ def write_to_stream(
+ self, stream: StreamType, encryption_key: Union[None, str, bytes] = None
+ ) -> None:
+ if encryption_key is not None: # deprecated
+ deprecate_no_replacement(
+ "the encryption_key parameter of write_to_stream", "5.0.0"
+ )
+ stream.write(b"[")
+ for data in self:
+ stream.write(b" ")
+ data.write_to_stream(stream)
+ stream.write(b" ]")
+
+ @staticmethod
+ def read_from_stream(
+ stream: StreamType,
+ pdf: Optional[PdfReaderProtocol],
+ forced_encoding: Union[None, str, List[str], Dict[int, str]] = None,
+ ) -> "ArrayObject":
+ arr = ArrayObject()
+ tmp = stream.read(1)
+ if tmp != b"[":
+ raise PdfReadError("Could not read array")
+ while True:
+ # skip leading whitespace
+ tok = stream.read(1)
+ while tok.isspace():
+ tok = stream.read(1)
+ stream.seek(-1, 1)
+ # check for array ending
+ peek_ahead = stream.read(1)
+ if peek_ahead == b"]":
+ break
+ stream.seek(-1, 1)
+ # read and append obj
+ arr.append(read_object(stream, pdf, forced_encoding))
+ return arr
+
+
+class DictionaryObject(Dict[Any, Any], PdfObject):
+ def clone(
+ self,
+ pdf_dest: PdfWriterProtocol,
+ force_duplicate: bool = False,
+ ignore_fields: Optional[Sequence[Union[str, int]]] = (),
+ ) -> "DictionaryObject":
+ """Clone object into pdf_dest."""
+ try:
+ if self.indirect_reference.pdf == pdf_dest and not force_duplicate: # type: ignore
+ return self
+ except Exception:
+ pass
+
+ visited: Set[Tuple[int, int]] = set() # (idnum, generation)
+ d__ = cast(
+ "DictionaryObject",
+ self._reference_clone(self.__class__(), pdf_dest, force_duplicate),
+ )
+ if ignore_fields is None:
+ ignore_fields = []
+ if len(d__.keys()) == 0:
+ d__._clone(self, pdf_dest, force_duplicate, ignore_fields, visited)
+ return d__
+
+ def _clone(
+ self,
+ src: "DictionaryObject",
+ pdf_dest: PdfWriterProtocol,
+ force_duplicate: bool,
+ ignore_fields: Optional[Sequence[Union[str, int]]],
+ visited: Set[Tuple[int, int]], # (idnum, generation)
+ ) -> None:
+ """
+ Update the object from src.
+
+ Args:
+ src: "DictionaryObject":
+ pdf_dest:
+ force_duplicate:
+ ignore_fields:
+ """
+ # first we remove for the ignore_fields
+ # that are for a limited number of levels
+ x = 0
+ assert ignore_fields is not None
+ ignore_fields = list(ignore_fields)
+ while x < len(ignore_fields):
+ if isinstance(ignore_fields[x], int):
+ if cast(int, ignore_fields[x]) <= 0:
+ del ignore_fields[x]
+ del ignore_fields[x]
+ continue
+ else:
+ ignore_fields[x] -= 1 # type:ignore
+ x += 1
+ # First check if this is a chain list, we need to loop to prevent recur
+ if any(
+ field not in ignore_fields
+ and field in src
+ and isinstance(src.raw_get(field), IndirectObject)
+ and isinstance(src[field], DictionaryObject)
+ and (
+ src.get("/Type", None) is None
+ or cast(DictionaryObject, src[field]).get("/Type", None) is None
+ or src.get("/Type", None)
+ == cast(DictionaryObject, src[field]).get("/Type", None)
+ )
+ for field in ["/Next", "/Prev", "/N", "/V"]
+ ):
+ ignore_fields = list(ignore_fields)
+ for lst in (("/Next", "/Prev"), ("/N", "/V")):
+ for k in lst:
+ objs = []
+ if (
+ k in src
+ and k not in self
+ and isinstance(src.raw_get(k), IndirectObject)
+ and isinstance(src[k], DictionaryObject)
+ # IF need to go further the idea is to check
+ # that the types are the same:
+ and (
+ src.get("/Type", None) is None
+ or cast(DictionaryObject, src[k]).get("/Type", None) is None
+ or src.get("/Type", None)
+ == cast(DictionaryObject, src[k]).get("/Type", None)
+ )
+ ):
+ cur_obj: Optional[DictionaryObject] = cast(
+ "DictionaryObject", src[k]
+ )
+ prev_obj: Optional[DictionaryObject] = self
+ while cur_obj is not None:
+ clon = cast(
+ "DictionaryObject",
+ cur_obj._reference_clone(
+ cur_obj.__class__(), pdf_dest, force_duplicate
+ ),
+ )
+ # check to see if we've previously processed our item
+ if clon.indirect_reference is not None:
+ idnum = clon.indirect_reference.idnum
+ generation = clon.indirect_reference.generation
+ if (idnum, generation) in visited:
+ cur_obj = None
+ break
+ visited.add((idnum, generation))
+ objs.append((cur_obj, clon))
+ assert prev_obj is not None
+ prev_obj[NameObject(k)] = clon.indirect_reference
+ prev_obj = clon
+ try:
+ if cur_obj == src:
+ cur_obj = None
+ else:
+ cur_obj = cast("DictionaryObject", cur_obj[k])
+ except Exception:
+ cur_obj = None
+ for s, c in objs:
+ c._clone(
+ s, pdf_dest, force_duplicate, ignore_fields, visited
+ )
+
+ for k, v in src.items():
+ if k not in ignore_fields:
+ if isinstance(v, StreamObject):
+ if not hasattr(v, "indirect_reference"):
+ v.indirect_reference = None
+ vv = v.clone(pdf_dest, force_duplicate, ignore_fields)
+ assert vv.indirect_reference is not None
+ self[k.clone(pdf_dest)] = vv.indirect_reference # type: ignore[attr-defined]
+ elif k not in self:
+ self[NameObject(k)] = (
+ v.clone(pdf_dest, force_duplicate, ignore_fields)
+ if hasattr(v, "clone")
+ else v
+ )
+
+ def raw_get(self, key: Any) -> Any:
+ return dict.__getitem__(self, key)
+
+ def get_inherited(self, key: str, default: Any = None) -> Any:
+ """
+ Returns the value of a key or from the parent if not found.
+ If not found returns default.
+
+ Args:
+ key: string identifying the field to return
+
+ default: default value to return
+
+ Returns:
+ Current key or inherited one, otherwise default value.
+ """
+ if key in self:
+ return self[key]
+ try:
+ if "/Parent" not in self:
+ return default
+ raise KeyError("not present")
+ except KeyError:
+ return cast("DictionaryObject", self["/Parent"].get_object()).get_inherited(
+ key, default
+ )
+
+ def __setitem__(self, key: Any, value: Any) -> Any:
+ if not isinstance(key, PdfObject):
+ raise ValueError("key must be PdfObject")
+ if not isinstance(value, PdfObject):
+ raise ValueError("value must be PdfObject")
+ return dict.__setitem__(self, key, value)
+
+ def setdefault(self, key: Any, value: Optional[Any] = None) -> Any:
+ if not isinstance(key, PdfObject):
+ raise ValueError("key must be PdfObject")
+ if not isinstance(value, PdfObject):
+ raise ValueError("value must be PdfObject")
+ return dict.setdefault(self, key, value) # type: ignore
+
+ def __getitem__(self, key: Any) -> PdfObject:
+ return dict.__getitem__(self, key).get_object()
+
+ @property
+ def xmp_metadata(self) -> Optional[XmpInformationProtocol]:
+ """
+ Retrieve XMP (Extensible Metadata Platform) data relevant to the this
+ object, if available.
+
+ See Table 347 — Additional entries in a metadata stream dictionary.
+
+ Returns:
+ Returns a :class:`~pypdf.xmp.XmpInformation` instance
+ that can be used to access XMP metadata from the document. Can also
+ return None if no metadata was found on the document root.
+ """
+ from ..xmp import XmpInformation
+
+ metadata = self.get("/Metadata", None)
+ if metadata is None:
+ return None
+ metadata = metadata.get_object()
+
+ if not isinstance(metadata, XmpInformation):
+ metadata = XmpInformation(metadata)
+ self[NameObject("/Metadata")] = metadata
+ return metadata
+
+ def write_to_stream(
+ self, stream: StreamType, encryption_key: Union[None, str, bytes] = None
+ ) -> None:
+ if encryption_key is not None: # deprecated
+ deprecate_no_replacement(
+ "the encryption_key parameter of write_to_stream", "5.0.0"
+ )
+ stream.write(b"<<\n")
+ for key, value in list(self.items()):
+ if len(key) > 2 and key[1] == "%" and key[-1] == "%":
+ continue
+ key.write_to_stream(stream, encryption_key)
+ stream.write(b" ")
+ value.write_to_stream(stream)
+ stream.write(b"\n")
+ stream.write(b">>")
+
+ @staticmethod
+ def read_from_stream(
+ stream: StreamType,
+ pdf: Optional[PdfReaderProtocol],
+ forced_encoding: Union[None, str, List[str], Dict[int, str]] = None,
+ ) -> "DictionaryObject":
+ def get_next_obj_pos(
+ p: int, p1: int, rem_gens: List[int], pdf: PdfReaderProtocol
+ ) -> int:
+ out = p1
+ for gen in rem_gens:
+ loc = pdf.xref[gen]
+ try:
+ out = min(out, min([x for x in loc.values() if p < x <= p1]))
+ except ValueError:
+ pass
+ return out
+
+ def read_unsized_from_stream(
+ stream: StreamType, pdf: PdfReaderProtocol
+ ) -> bytes:
+ # we are just pointing at beginning of the stream
+ eon = get_next_obj_pos(stream.tell(), 2**32, list(pdf.xref), pdf) - 1
+ curr = stream.tell()
+ rw = stream.read(eon - stream.tell())
+ p = rw.find(b"endstream")
+ if p < 0:
+ raise PdfReadError(
+ f"Unable to find 'endstream' marker for obj starting at {curr}."
+ )
+ stream.seek(curr + p + 9)
+ return rw[: p - 1]
+
+ tmp = stream.read(2)
+ if tmp != b"<<":
+ raise PdfReadError(
+ f"Dictionary read error at byte {hex(stream.tell())}: "
+ "stream must begin with '<<'"
+ )
+ data: Dict[Any, Any] = {}
+ while True:
+ tok = read_non_whitespace(stream)
+ if tok == b"\x00":
+ continue
+ elif tok == b"%":
+ stream.seek(-1, 1)
+ skip_over_comment(stream)
+ continue
+ if not tok:
+ raise PdfStreamError(STREAM_TRUNCATED_PREMATURELY)
+
+ if tok == b">":
+ stream.read(1)
+ break
+ stream.seek(-1, 1)
+ try:
+ key = read_object(stream, pdf)
+ tok = read_non_whitespace(stream)
+ stream.seek(-1, 1)
+ value = read_object(stream, pdf, forced_encoding)
+ except Exception as exc:
+ if pdf is not None and pdf.strict:
+ raise PdfReadError(exc.__repr__())
+ logger_warning(exc.__repr__(), __name__)
+ retval = DictionaryObject()
+ retval.update(data)
+ return retval # return partial data
+
+ if not data.get(key):
+ data[key] = value
+ else:
+ # multiple definitions of key not permitted
+ msg = (
+ f"Multiple definitions in dictionary at byte "
+ f"{hex(stream.tell())} for key {key}"
+ )
+ if pdf is not None and pdf.strict:
+ raise PdfReadError(msg)
+ logger_warning(msg, __name__)
+
+ pos = stream.tell()
+ s = read_non_whitespace(stream)
+ if s == b"s" and stream.read(5) == b"tream":
+ eol = stream.read(1)
+ # odd PDF file output has spaces after 'stream' keyword but before EOL.
+ # patch provided by Danial Sandler
+ while eol == b" ":
+ eol = stream.read(1)
+ if eol not in (b"\n", b"\r"):
+ raise PdfStreamError("Stream data must be followed by a newline")
+ if eol == b"\r" and stream.read(1) != b"\n":
+ stream.seek(-1, 1)
+ # this is a stream object, not a dictionary
+ if SA.LENGTH not in data:
+ if pdf is not None and pdf.strict:
+ raise PdfStreamError("Stream length not defined")
+ else:
+ logger_warning(
+ f"Stream length not defined @pos={stream.tell()}", __name__
+ )
+ data[NameObject(SA.LENGTH)] = NumberObject(-1)
+ length = data[SA.LENGTH]
+ if isinstance(length, IndirectObject):
+ t = stream.tell()
+ assert pdf is not None # hint for mypy
+ length = pdf.get_object(length)
+ stream.seek(t, 0)
+ if length is None: # if the PDF is damaged
+ length = -1
+ pstart = stream.tell()
+ if length > 0:
+ data["__streamdata__"] = stream.read(length)
+ else:
+ data["__streamdata__"] = read_until_regex(
+ stream, re.compile(b"endstream")
+ )
+ e = read_non_whitespace(stream)
+ ndstream = stream.read(8)
+ if (e + ndstream) != b"endstream":
+ # (sigh) - the odd PDF file has a length that is too long, so
+ # we need to read backwards to find the "endstream" ending.
+ # ReportLab (unknown version) generates files with this bug,
+ # and Python users into PDF files tend to be our audience.
+ # we need to do this to correct the streamdata and chop off
+ # an extra character.
+ pos = stream.tell()
+ stream.seek(-10, 1)
+ end = stream.read(9)
+ if end == b"endstream":
+ # we found it by looking back one character further.
+ data["__streamdata__"] = data["__streamdata__"][:-1]
+ elif pdf is not None and not pdf.strict:
+ stream.seek(pstart, 0)
+ data["__streamdata__"] = read_unsized_from_stream(stream, pdf)
+ pos = stream.tell()
+ else:
+ stream.seek(pos, 0)
+ raise PdfReadError(
+ "Unable to find 'endstream' marker after stream at byte "
+ f"{hex(stream.tell())} (nd='{ndstream!r}', end='{end!r}')."
+ )
+ else:
+ stream.seek(pos, 0)
+ if "__streamdata__" in data:
+ return StreamObject.initialize_from_dictionary(data)
+ else:
+ retval = DictionaryObject()
+ retval.update(data)
+ return retval
+
+
+class TreeObject(DictionaryObject):
+ def __init__(self, dct: Optional[DictionaryObject] = None) -> None:
+ DictionaryObject.__init__(self)
+ if dct:
+ self.update(dct)
+
+ def hasChildren(self) -> bool: # deprecated
+ deprecate_with_replacement("hasChildren", "has_children", "4.0.0")
+ return self.has_children()
+
+ def has_children(self) -> bool:
+ return "/First" in self
+
+ def __iter__(self) -> Any:
+ return self.children()
+
+ def children(self) -> Iterable[Any]:
+ if not self.has_children():
+ return
+
+ child_ref = self[NameObject("/First")]
+ child = child_ref.get_object()
+ while True:
+ yield child
+ if child == self[NameObject("/Last")]:
+ return
+ child_ref = child.get(NameObject("/Next")) # type: ignore
+ if child_ref is None:
+ return
+ child = child_ref.get_object()
+
+ def add_child(self, child: Any, pdf: PdfWriterProtocol) -> None:
+ self.insert_child(child, None, pdf)
+
+ def inc_parent_counter_default(
+ self, parent: Union[None, IndirectObject, "TreeObject"], n: int
+ ) -> None:
+ if parent is None:
+ return
+ parent = cast("TreeObject", parent.get_object())
+ if "/Count" in parent:
+ parent[NameObject("/Count")] = NumberObject(
+ max(0, cast(int, parent[NameObject("/Count")]) + n)
+ )
+ self.inc_parent_counter_default(parent.get("/Parent", None), n)
+
+ def inc_parent_counter_outline(
+ self, parent: Union[None, IndirectObject, "TreeObject"], n: int
+ ) -> None:
+ if parent is None:
+ return
+ parent = cast("TreeObject", parent.get_object())
+ # BooleanObject requires comparison with == not is
+ opn = parent.get("/%is_open%", True) == True # noqa
+ c = cast(int, parent.get("/Count", 0))
+ if c < 0:
+ c = abs(c)
+ parent[NameObject("/Count")] = NumberObject((c + n) * (1 if opn else -1))
+ if not opn:
+ return
+ self.inc_parent_counter_outline(parent.get("/Parent", None), n)
+
+ def insert_child(
+ self,
+ child: Any,
+ before: Any,
+ pdf: PdfWriterProtocol,
+ inc_parent_counter: Optional[Callable[..., Any]] = None,
+ ) -> IndirectObject:
+ if inc_parent_counter is None:
+ inc_parent_counter = self.inc_parent_counter_default
+ child_obj = child.get_object()
+ child = child.indirect_reference # get_reference(child_obj)
+
+ prev: Optional[DictionaryObject]
+ if "/First" not in self: # no child yet
+ self[NameObject("/First")] = child
+ self[NameObject("/Count")] = NumberObject(0)
+ self[NameObject("/Last")] = child
+ child_obj[NameObject("/Parent")] = self.indirect_reference
+ inc_parent_counter(self, child_obj.get("/Count", 1))
+ if "/Next" in child_obj:
+ del child_obj["/Next"]
+ if "/Prev" in child_obj:
+ del child_obj["/Prev"]
+ return child
+ else:
+ prev = cast("DictionaryObject", self["/Last"])
+
+ while prev.indirect_reference != before:
+ if "/Next" in prev:
+ prev = cast("TreeObject", prev["/Next"])
+ else: # append at the end
+ prev[NameObject("/Next")] = cast("TreeObject", child)
+ child_obj[NameObject("/Prev")] = prev.indirect_reference
+ child_obj[NameObject("/Parent")] = self.indirect_reference
+ if "/Next" in child_obj:
+ del child_obj["/Next"]
+ self[NameObject("/Last")] = child
+ inc_parent_counter(self, child_obj.get("/Count", 1))
+ return child
+ try: # insert as first or in the middle
+ assert isinstance(prev["/Prev"], DictionaryObject)
+ prev["/Prev"][NameObject("/Next")] = child
+ child_obj[NameObject("/Prev")] = prev["/Prev"]
+ except Exception: # it means we are inserting in first position
+ del child_obj["/Next"]
+ child_obj[NameObject("/Next")] = prev
+ prev[NameObject("/Prev")] = child
+ child_obj[NameObject("/Parent")] = self.indirect_reference
+ inc_parent_counter(self, child_obj.get("/Count", 1))
+ return child
+
+ def _remove_node_from_tree(
+ self, prev: Any, prev_ref: Any, cur: Any, last: Any
+ ) -> None:
+ """
+ Adjust the pointers of the linked list and tree node count.
+
+ Args:
+ prev:
+ prev_ref:
+ cur:
+ last:
+ """
+ next_ref = cur.get(NameObject("/Next"), None)
+ if prev is None:
+ if next_ref:
+ # Removing first tree node
+ next_obj = next_ref.get_object()
+ del next_obj[NameObject("/Prev")]
+ self[NameObject("/First")] = next_ref
+ self[NameObject("/Count")] = NumberObject(
+ self[NameObject("/Count")] - 1 # type: ignore
+ )
+
+ else:
+ # Removing only tree node
+ self[NameObject("/Count")] = NumberObject(0)
+ del self[NameObject("/First")]
+ if NameObject("/Last") in self:
+ del self[NameObject("/Last")]
+ else:
+ if next_ref:
+ # Removing middle tree node
+ next_obj = next_ref.get_object()
+ next_obj[NameObject("/Prev")] = prev_ref
+ prev[NameObject("/Next")] = next_ref
+ else:
+ # Removing last tree node
+ assert cur == last
+ del prev[NameObject("/Next")]
+ self[NameObject("/Last")] = prev_ref
+ self[NameObject("/Count")] = NumberObject(self[NameObject("/Count")] - 1) # type: ignore
+
+ def remove_child(self, child: Any) -> None:
+ child_obj = child.get_object()
+ child = child_obj.indirect_reference
+
+ if NameObject("/Parent") not in child_obj:
+ raise ValueError("Removed child does not appear to be a tree item")
+ elif child_obj[NameObject("/Parent")] != self:
+ raise ValueError("Removed child is not a member of this tree")
+
+ found = False
+ prev_ref = None
+ prev = None
+ cur_ref: Optional[Any] = self[NameObject("/First")]
+ cur: Optional[Dict[str, Any]] = cur_ref.get_object() # type: ignore
+ last_ref = self[NameObject("/Last")]
+ last = last_ref.get_object()
+ while cur is not None:
+ if cur == child_obj:
+ self._remove_node_from_tree(prev, prev_ref, cur, last)
+ found = True
+ break
+
+ # Go to the next node
+ prev_ref = cur_ref
+ prev = cur
+ if NameObject("/Next") in cur:
+ cur_ref = cur[NameObject("/Next")]
+ cur = cur_ref.get_object()
+ else:
+ cur_ref = None
+ cur = None
+
+ if not found:
+ raise ValueError("Removal couldn't find item in tree")
+
+ _reset_node_tree_relationship(child_obj)
+
+ def remove_from_tree(self) -> None:
+ """Remove the object from the tree it is in."""
+ if NameObject("/Parent") not in self:
+ raise ValueError("Removed child does not appear to be a tree item")
+ else:
+ cast("TreeObject", self["/Parent"]).remove_child(self)
+
+ def emptyTree(self) -> None: # deprecated
+ deprecate_with_replacement("emptyTree", "empty_tree", "4.0.0")
+ self.empty_tree()
+
+ def empty_tree(self) -> None:
+ for child in self:
+ child_obj = child.get_object()
+ _reset_node_tree_relationship(child_obj)
+
+ if NameObject("/Count") in self:
+ del self[NameObject("/Count")]
+ if NameObject("/First") in self:
+ del self[NameObject("/First")]
+ if NameObject("/Last") in self:
+ del self[NameObject("/Last")]
+
+
+def _reset_node_tree_relationship(child_obj: Any) -> None:
+ """
+ Call this after a node has been removed from a tree.
+
+ This resets the nodes attributes in respect to that tree.
+
+ Args:
+ child_obj:
+ """
+ del child_obj[NameObject("/Parent")]
+ if NameObject("/Next") in child_obj:
+ del child_obj[NameObject("/Next")]
+ if NameObject("/Prev") in child_obj:
+ del child_obj[NameObject("/Prev")]
+
+
+class StreamObject(DictionaryObject):
+ def __init__(self) -> None:
+ self._data: Union[bytes, str] = b""
+ self.decoded_self: Optional[DecodedStreamObject] = None
+
+ def _clone(
+ self,
+ src: DictionaryObject,
+ pdf_dest: PdfWriterProtocol,
+ force_duplicate: bool,
+ ignore_fields: Optional[Sequence[Union[str, int]]],
+ visited: Set[Tuple[int, int]],
+ ) -> None:
+ """
+ Update the object from src.
+
+ Args:
+ src:
+ pdf_dest:
+ force_duplicate:
+ ignore_fields:
+ """
+ self._data = cast("StreamObject", src)._data
+ try:
+ decoded_self = cast("StreamObject", src).decoded_self
+ if decoded_self is None:
+ self.decoded_self = None
+ else:
+ self.decoded_self = cast(
+ "DecodedStreamObject",
+ decoded_self.clone(pdf_dest, force_duplicate, ignore_fields),
+ )
+ except Exception:
+ pass
+ super()._clone(src, pdf_dest, force_duplicate, ignore_fields, visited)
+
+ def get_data(self) -> Union[bytes, str]:
+ return self._data
+
+ def set_data(self, data: bytes) -> None:
+ self._data = data
+
+ def hash_value_data(self) -> bytes:
+ data = super().hash_value_data()
+ data += b_(self._data)
+ return data
+
+ def write_to_stream(
+ self, stream: StreamType, encryption_key: Union[None, str, bytes] = None
+ ) -> None:
+ if encryption_key is not None: # deprecated
+ deprecate_no_replacement(
+ "the encryption_key parameter of write_to_stream", "5.0.0"
+ )
+ self[NameObject(SA.LENGTH)] = NumberObject(len(self._data))
+ DictionaryObject.write_to_stream(self, stream)
+ del self[SA.LENGTH]
+ stream.write(b"\nstream\n")
+ stream.write(self._data)
+ stream.write(b"\nendstream")
+
+ @staticmethod
+ def initializeFromDictionary(
+ data: Dict[str, Any]
+ ) -> Union["EncodedStreamObject", "DecodedStreamObject"]:
+ deprecate_with_replacement(
+ "initializeFromDictionary", "initialize_from_dictionary", "5.0.0"
+ ) # pragma: no cover
+ return StreamObject.initialize_from_dictionary(data) # pragma: no cover
+
+ @staticmethod
+ def initialize_from_dictionary(
+ data: Dict[str, Any]
+ ) -> Union["EncodedStreamObject", "DecodedStreamObject"]:
+ retval: Union[EncodedStreamObject, DecodedStreamObject]
+ if SA.FILTER in data:
+ retval = EncodedStreamObject()
+ else:
+ retval = DecodedStreamObject()
+ retval._data = data["__streamdata__"]
+ del data["__streamdata__"]
+ del data[SA.LENGTH]
+ retval.update(data)
+ return retval
+
+ def flate_encode(self, level: int = -1) -> "EncodedStreamObject":
+ from ..filters import FlateDecode
+
+ if SA.FILTER in self:
+ f = self[SA.FILTER]
+ if isinstance(f, ArrayObject):
+ f = ArrayObject([NameObject(FT.FLATE_DECODE), *f])
+ try:
+ params = ArrayObject(
+ [NullObject(), *self.get(SA.DECODE_PARMS, ArrayObject())]
+ )
+ except TypeError:
+ # case of error where the * operator is not working (not an array
+ params = ArrayObject(
+ [NullObject(), self.get(SA.DECODE_PARMS, ArrayObject())]
+ )
+ else:
+ f = ArrayObject([NameObject(FT.FLATE_DECODE), f])
+ params = ArrayObject(
+ [NullObject(), self.get(SA.DECODE_PARMS, NullObject())]
+ )
+ else:
+ f = NameObject(FT.FLATE_DECODE)
+ params = None
+ retval = EncodedStreamObject()
+ retval.update(self)
+ retval[NameObject(SA.FILTER)] = f
+ if params is not None:
+ retval[NameObject(SA.DECODE_PARMS)] = params
+ retval._data = FlateDecode.encode(b_(self._data), level)
+ return retval
+
+ def decode_as_image(self) -> Any:
+ """
+ Try to decode the stream object as an image
+
+ Returns:
+ a PIL image if proper decoding has been found
+ Raises:
+ Exception: (any)during decoding to to invalid object or
+ errors during decoding will be reported
+ It is recommended to catch exceptions to prevent
+ stops in your program.
+ """
+ from ..filters import _xobj_to_image
+
+ if self.get("/Subtype", "") != "/Image":
+ try:
+ msg = f"{self.indirect_reference} does not seem to be an Image" # pragma: no cover
+ except AttributeError:
+ msg = f"{self.__repr__()} object does not seem to be an Image" # pragma: no cover
+ logger_warning(msg, __name__)
+ extension, byte_stream, img = _xobj_to_image(self)
+ if extension is None:
+ return None # pragma: no cover
+ return img
+
+
+class DecodedStreamObject(StreamObject):
+ pass
+
+
+class EncodedStreamObject(StreamObject):
+ def __init__(self) -> None:
+ self.decoded_self: Optional[DecodedStreamObject] = None
+
+ # This overrides the parent method:
+ def get_data(self) -> Union[bytes, str]:
+ from ..filters import decode_stream_data
+
+ if self.decoded_self is not None:
+ # cached version of decoded object
+ return self.decoded_self.get_data()
+ else:
+ # create decoded object
+ decoded = DecodedStreamObject()
+
+ decoded.set_data(b_(decode_stream_data(self)))
+ for key, value in list(self.items()):
+ if key not in (SA.LENGTH, SA.FILTER, SA.DECODE_PARMS):
+ decoded[key] = value
+ self.decoded_self = decoded
+ return decoded.get_data()
+
+ # This overrides the parent method:
+ def set_data(self, data: bytes) -> None: # deprecated
+ from ..filters import FlateDecode
+
+ if self.get(SA.FILTER, "") == FT.FLATE_DECODE:
+ if not isinstance(data, bytes):
+ raise TypeError("data must be bytes")
+ assert self.decoded_self is not None
+ self.decoded_self.set_data(data)
+ super().set_data(FlateDecode.encode(data))
+ else:
+ raise PdfReadError(
+ "Streams encoded with different filter from only FlateDecode is not supported"
+ )
+
+
+class ContentStream(DecodedStreamObject):
+ """
+ In order to be fast, this data structure can contain either:
+
+ * raw data in ._data
+ * parsed stream operations in ._operations.
+
+ At any time, ContentStream object can either have both of those fields defined,
+ or one field defined and the other set to None.
+
+ These fields are "rebuilt" lazily, when accessed:
+
+ * when .get_data() is called, if ._data is None, it is rebuilt from ._operations.
+ * when .operations is called, if ._operations is None, it is rebuilt from ._data.
+
+ Conversely, these fields can be invalidated:
+
+ * when .set_data() is called, ._operations is set to None.
+ * when .operations is set, ._data is set to None.
+ """
+
+ def __init__(
+ self,
+ stream: Any,
+ pdf: Any,
+ forced_encoding: Union[None, str, List[str], Dict[int, str]] = None,
+ ) -> None:
+ self.pdf = pdf
+
+ # The inner list has two elements:
+ # Element 0: List
+ # Element 1: str
+ self._operations: List[Tuple[Any, Any]] = []
+
+ # stream may be a StreamObject or an ArrayObject containing
+ # multiple StreamObjects to be cat'd together.
+ if stream is None:
+ super().set_data(b"")
+ else:
+ stream = stream.get_object()
+ if isinstance(stream, ArrayObject):
+ data = b""
+ for s in stream:
+ data += b_(s.get_object().get_data())
+ if len(data) == 0 or data[-1] != b"\n":
+ data += b"\n"
+ super().set_data(bytes(data))
+ else:
+ stream_data = stream.get_data()
+ assert stream_data is not None
+ super().set_data(b_(stream_data))
+ self.forced_encoding = forced_encoding
+
+ def clone(
+ self,
+ pdf_dest: Any,
+ force_duplicate: bool = False,
+ ignore_fields: Optional[Sequence[Union[str, int]]] = (),
+ ) -> "ContentStream":
+ """
+ Clone object into pdf_dest.
+
+ Args:
+ pdf_dest:
+ force_duplicate:
+ ignore_fields:
+
+ Returns:
+ The cloned ContentStream
+ """
+ try:
+ if self.indirect_reference.pdf == pdf_dest and not force_duplicate: # type: ignore
+ return self
+ except Exception:
+ pass
+
+ visited: Set[Tuple[int, int]] = set()
+ d__ = cast(
+ "ContentStream",
+ self._reference_clone(
+ self.__class__(None, None), pdf_dest, force_duplicate
+ ),
+ )
+ if ignore_fields is None:
+ ignore_fields = []
+ d__._clone(self, pdf_dest, force_duplicate, ignore_fields, visited)
+ return d__
+
+ def _clone(
+ self,
+ src: DictionaryObject,
+ pdf_dest: PdfWriterProtocol,
+ force_duplicate: bool,
+ ignore_fields: Optional[Sequence[Union[str, int]]],
+ visited: Set[Tuple[int, int]],
+ ) -> None:
+ """
+ Update the object from src.
+
+ Args:
+ src:
+ pdf_dest:
+ force_duplicate:
+ ignore_fields:
+ """
+ src_cs = cast("ContentStream", src)
+ super().set_data(b_(src_cs._data))
+ self.pdf = pdf_dest
+ self._operations = list(src_cs._operations)
+ self.forced_encoding = src_cs.forced_encoding
+ # no need to call DictionaryObjection or anything
+ # like super(DictionaryObject,self)._clone(src, pdf_dest, force_duplicate, ignore_fields, visited)
+
+ def _parse_content_stream(self, stream: StreamType) -> None:
+ # 7.8.2 Content Streams
+ stream.seek(0, 0)
+ operands: List[Union[int, str, PdfObject]] = []
+ while True:
+ peek = read_non_whitespace(stream)
+ if peek == b"" or peek == 0:
+ break
+ stream.seek(-1, 1)
+ if peek.isalpha() or peek in (b"'", b'"'):
+ operator = read_until_regex(stream, NameObject.delimiter_pattern)
+ if operator == b"BI":
+ # begin inline image - a completely different parsing
+ # mechanism is required, of course... thanks buddy...
+ assert operands == []
+ ii = self._read_inline_image(stream)
+ self._operations.append((ii, b"INLINE IMAGE"))
+ else:
+ self._operations.append((operands, operator))
+ operands = []
+ elif peek == b"%":
+ # If we encounter a comment in the content stream, we have to
+ # handle it here. Typically, read_object will handle
+ # encountering a comment -- but read_object assumes that
+ # following the comment must be the object we're trying to
+ # read. In this case, it could be an operator instead.
+ while peek not in (b"\r", b"\n", b""):
+ peek = stream.read(1)
+ else:
+ operands.append(read_object(stream, None, self.forced_encoding))
+
+ def _read_inline_image(self, stream: StreamType) -> Dict[str, Any]:
+ # begin reading just after the "BI" - begin image
+ # first read the dictionary of settings.
+ settings = DictionaryObject()
+ while True:
+ tok = read_non_whitespace(stream)
+ stream.seek(-1, 1)
+ if tok == b"I":
+ # "ID" - begin of image data
+ break
+ key = read_object(stream, self.pdf)
+ tok = read_non_whitespace(stream)
+ stream.seek(-1, 1)
+ value = read_object(stream, self.pdf)
+ settings[key] = value
+ # left at beginning of ID
+ tmp = stream.read(3)
+ assert tmp[:2] == b"ID"
+ filtr = settings.get("/F", settings.get("/Filter", "not set"))
+ savpos = stream.tell()
+ if isinstance(filtr, list):
+ filtr = filtr[0] # used forencoding
+ if "AHx" in filtr or "ASCIIHexDecode" in filtr:
+ data = extract_inline_AHx(stream)
+ elif "A85" in filtr or "ASCII85Decode" in filtr:
+ data = extract_inline_A85(stream)
+ elif "RL" in filtr or "RunLengthDecode" in filtr:
+ data = extract_inline_RL(stream)
+ elif "DCT" in filtr or "DCTDecode" in filtr:
+ data = extract_inline_DCT(stream)
+ elif filtr == "not set":
+ cs = settings.get("/CS", "")
+ if "RGB" in cs:
+ lcs = 3
+ elif "CMYK" in cs:
+ lcs = 4
+ else:
+ bits = settings.get(
+ "/BPC",
+ 8 if cs in {"/I", "/G", "/Indexed", "/DeviceGray"} else -1,
+ )
+ if bits > 0:
+ lcs = bits / 8.0
+ else:
+ data = extract_inline_default(stream)
+ lcs = -1
+ if lcs > 0:
+ data = stream.read(
+ ceil(cast(int, settings["/W"]) * lcs) * cast(int, settings["/H"])
+ )
+ ei = read_non_whitespace(stream)
+ stream.seek(-1, 1)
+ else:
+ data = extract_inline_default(stream)
+
+ ei = stream.read(3)
+ stream.seek(-1, 1)
+ if ei[0:2] != b"EI" or ei[2:3] not in WHITESPACES:
+ stream.seek(savpos, 0)
+ data = extract_inline_default(stream)
+ return {"settings": settings, "data": data}
+
+ # This overrides the parent method:
+ def get_data(self) -> bytes:
+ if not self._data:
+ new_data = BytesIO()
+ for operands, operator in self._operations:
+ if operator == b"INLINE IMAGE":
+ new_data.write(b"BI")
+ dict_text = BytesIO()
+ operands["settings"].write_to_stream(dict_text)
+ new_data.write(dict_text.getvalue()[2:-2])
+ new_data.write(b"ID ")
+ new_data.write(operands["data"])
+ new_data.write(b"EI")
+ else:
+ for op in operands:
+ op.write_to_stream(new_data)
+ new_data.write(b" ")
+ new_data.write(b_(operator))
+ new_data.write(b"\n")
+ self._data = new_data.getvalue()
+ return b_(self._data)
+
+ # This overrides the parent method:
+ def set_data(self, data: bytes) -> None:
+ super().set_data(data)
+ self._operations = []
+
+ @property
+ def operations(self) -> List[Tuple[Any, Any]]:
+ if not self._operations and self._data:
+ self._parse_content_stream(BytesIO(b_(self._data)))
+ self._data = b""
+ return self._operations
+
+ @operations.setter
+ def operations(self, operations: List[Tuple[Any, Any]]) -> None:
+ self._operations = operations
+ self._data = b""
+
+ def isolate_graphics_state(self) -> None:
+ if self._operations:
+ self._operations.insert(0, ([], "q"))
+ self._operations.append(([], "Q"))
+ elif self._data:
+ self._data = b"q\n" + b_(self._data) + b"\nQ\n"
+
+ # This overrides the parent method:
+ def write_to_stream(
+ self, stream: StreamType, encryption_key: Union[None, str, bytes] = None
+ ) -> None:
+ if not self._data and self._operations:
+ self.get_data() # this ensures ._data is rebuilt
+ super().write_to_stream(stream, encryption_key)
+
+
+def read_object(
+ stream: StreamType,
+ pdf: Optional[PdfReaderProtocol],
+ forced_encoding: Union[None, str, List[str], Dict[int, str]] = None,
+) -> Union[PdfObject, int, str, ContentStream]:
+ tok = stream.read(1)
+ stream.seek(-1, 1) # reset to start
+ if tok == b"/":
+ return NameObject.read_from_stream(stream, pdf)
+ elif tok == b"<":
+ # hexadecimal string OR dictionary
+ peek = stream.read(2)
+ stream.seek(-2, 1) # reset to start
+ if peek == b"<<":
+ return DictionaryObject.read_from_stream(stream, pdf, forced_encoding)
+ else:
+ return read_hex_string_from_stream(stream, forced_encoding)
+ elif tok == b"[":
+ return ArrayObject.read_from_stream(stream, pdf, forced_encoding)
+ elif tok == b"t" or tok == b"f":
+ return BooleanObject.read_from_stream(stream)
+ elif tok == b"(":
+ return read_string_from_stream(stream, forced_encoding)
+ elif tok == b"e" and stream.read(6) == b"endobj":
+ stream.seek(-6, 1)
+ return NullObject()
+ elif tok == b"n":
+ return NullObject.read_from_stream(stream)
+ elif tok == b"%":
+ # comment
+ while tok not in (b"\r", b"\n"):
+ tok = stream.read(1)
+ # Prevents an infinite loop by raising an error if the stream is at
+ # the EOF
+ if len(tok) <= 0:
+ raise PdfStreamError("File ended unexpectedly.")
+ tok = read_non_whitespace(stream)
+ stream.seek(-1, 1)
+ return read_object(stream, pdf, forced_encoding)
+ elif tok in b"0123456789+-.":
+ # number object OR indirect reference
+ peek = stream.read(20)
+ stream.seek(-len(peek), 1) # reset to start
+ if IndirectPattern.match(peek) is not None:
+ assert pdf is not None # hint for mypy
+ return IndirectObject.read_from_stream(stream, pdf)
+ else:
+ return NumberObject.read_from_stream(stream)
+ else:
+ stream.seek(-20, 1)
+ raise PdfReadError(
+ f"Invalid Elementary Object starting with {tok!r} @{stream.tell()}: {stream.read(80).__repr__()}"
+ )
+
+
+class Field(TreeObject):
+ """
+ A class representing a field dictionary.
+
+ This class is accessed through
+ :meth:`get_fields()<pypdf.PdfReader.get_fields>`
+ """
+
+ def __init__(self, data: DictionaryObject) -> None:
+ DictionaryObject.__init__(self)
+ field_attributes = (
+ FieldDictionaryAttributes.attributes()
+ + CheckboxRadioButtonAttributes.attributes()
+ )
+ self.indirect_reference = data.indirect_reference
+ for attr in field_attributes:
+ try:
+ self[NameObject(attr)] = data[attr]
+ except KeyError:
+ pass
+ if isinstance(self.get("/V"), EncodedStreamObject):
+ d = cast(EncodedStreamObject, self[NameObject("/V")]).get_data()
+ if isinstance(d, bytes):
+ d_str = d.decode()
+ elif d is None:
+ d_str = ""
+ else:
+ raise Exception("Should never happen")
+ self[NameObject("/V")] = TextStringObject(d_str)
+
+ # TABLE 8.69 Entries common to all field dictionaries
+ @property
+ def field_type(self) -> Optional[NameObject]:
+ """Read-only property accessing the type of this field."""
+ return self.get(FieldDictionaryAttributes.FT)
+
+ @property
+ def parent(self) -> Optional[DictionaryObject]:
+ """Read-only property accessing the parent of this field."""
+ return self.get(FieldDictionaryAttributes.Parent)
+
+ @property
+ def kids(self) -> Optional["ArrayObject"]:
+ """Read-only property accessing the kids of this field."""
+ return self.get(FieldDictionaryAttributes.Kids)
+
+ @property
+ def name(self) -> Optional[str]:
+ """Read-only property accessing the name of this field."""
+ return self.get(FieldDictionaryAttributes.T)
+
+ @property
+ def alternate_name(self) -> Optional[str]:
+ """Read-only property accessing the alternate name of this field."""
+ return self.get(FieldDictionaryAttributes.TU)
+
+ @property
+ def mapping_name(self) -> Optional[str]:
+ """
+ Read-only property accessing the mapping name of this field.
+
+ This name is used by pypdf as a key in the dictionary returned by
+ :meth:`get_fields()<pypdf.PdfReader.get_fields>`
+ """
+ return self.get(FieldDictionaryAttributes.TM)
+
+ @property
+ def flags(self) -> Optional[int]:
+ """
+ Read-only property accessing the field flags, specifying various
+ characteristics of the field (see Table 8.70 of the PDF 1.7 reference).
+ """
+ return self.get(FieldDictionaryAttributes.Ff)
+
+ @property
+ def value(self) -> Optional[Any]:
+ """
+ Read-only property accessing the value of this field.
+
+ Format varies based on field type.
+ """
+ return self.get(FieldDictionaryAttributes.V)
+
+ @property
+ def default_value(self) -> Optional[Any]:
+ """Read-only property accessing the default value of this field."""
+ return self.get(FieldDictionaryAttributes.DV)
+
+ @property
+ def additional_actions(self) -> Optional[DictionaryObject]:
+ """
+ Read-only property accessing the additional actions dictionary.
+
+ This dictionary defines the field's behavior in response to trigger
+ events. See Section 8.5.2 of the PDF 1.7 reference.
+ """
+ return self.get(FieldDictionaryAttributes.AA)
+
+
+class Destination(TreeObject):
+ """
+ A class representing a destination within a PDF file.
+
+ See section 12.3.2 of the PDF 2.0 reference.
+
+ Args:
+ title: Title of this destination.
+ page: Reference to the page of this destination. Should
+ be an instance of :class:`IndirectObject<pypdf.generic.IndirectObject>`.
+ fit: How the destination is displayed.
+
+ Raises:
+ PdfReadError: If destination type is invalid.
+ """
+
+ node: Optional[
+ DictionaryObject
+ ] = None # node provide access to the original Object
+
+ def __init__(
+ self,
+ title: str,
+ page: Union[NumberObject, IndirectObject, NullObject, DictionaryObject],
+ fit: Fit,
+ ) -> None:
+ self._filtered_children: List[Any] = [] # used in PdfWriter
+
+ typ = fit.fit_type
+ args = fit.fit_args
+
+ DictionaryObject.__init__(self)
+ self[NameObject("/Title")] = TextStringObject(title)
+ self[NameObject("/Page")] = page
+ self[NameObject("/Type")] = typ
+
+ # from table 8.2 of the PDF 1.7 reference.
+ if typ == "/XYZ":
+ if len(args) < 1: # left is missing : should never occur
+ args.append(NumberObject(0.0))
+ if len(args) < 2: # top is missing
+ args.append(NumberObject(0.0))
+ if len(args) < 3: # zoom is missing
+ args.append(NumberObject(0.0))
+ (
+ self[NameObject(TA.LEFT)],
+ self[NameObject(TA.TOP)],
+ self[NameObject("/Zoom")],
+ ) = args
+ elif len(args) == 0:
+ pass
+ elif typ == TF.FIT_R:
+ (
+ self[NameObject(TA.LEFT)],
+ self[NameObject(TA.BOTTOM)],
+ self[NameObject(TA.RIGHT)],
+ self[NameObject(TA.TOP)],
+ ) = args
+ elif typ in [TF.FIT_H, TF.FIT_BH]:
+ try: # Preferred to be more robust not only to null parameters
+ (self[NameObject(TA.TOP)],) = args
+ except Exception:
+ (self[NameObject(TA.TOP)],) = (NullObject(),)
+ elif typ in [TF.FIT_V, TF.FIT_BV]:
+ try: # Preferred to be more robust not only to null parameters
+ (self[NameObject(TA.LEFT)],) = args
+ except Exception:
+ (self[NameObject(TA.LEFT)],) = (NullObject(),)
+ elif typ in [TF.FIT, TF.FIT_B]:
+ pass
+ else:
+ raise PdfReadError(f"Unknown Destination Type: {typ!r}")
+
+ @property
+ def dest_array(self) -> "ArrayObject":
+ return ArrayObject(
+ [self.raw_get("/Page"), self["/Type"]]
+ + [
+ self[x]
+ for x in ["/Left", "/Bottom", "/Right", "/Top", "/Zoom"]
+ if x in self
+ ]
+ )
+
+ def write_to_stream(
+ self, stream: StreamType, encryption_key: Union[None, str, bytes] = None
+ ) -> None:
+ if encryption_key is not None: # deprecated
+ deprecate_no_replacement(
+ "the encryption_key parameter of write_to_stream", "5.0.0"
+ )
+ stream.write(b"<<\n")
+ key = NameObject("/D")
+ key.write_to_stream(stream)
+ stream.write(b" ")
+ value = self.dest_array
+ value.write_to_stream(stream)
+
+ key = NameObject("/S")
+ key.write_to_stream(stream)
+ stream.write(b" ")
+ value_s = NameObject("/GoTo")
+ value_s.write_to_stream(stream)
+
+ stream.write(b"\n")
+ stream.write(b">>")
+
+ @property
+ def title(self) -> Optional[str]:
+ """Read-only property accessing the destination title."""
+ return self.get("/Title")
+
+ @property
+ def page(self) -> Optional[int]:
+ """Read-only property accessing the destination page number."""
+ return self.get("/Page")
+
+ @property
+ def typ(self) -> Optional[str]:
+ """Read-only property accessing the destination type."""
+ return self.get("/Type")
+
+ @property
+ def zoom(self) -> Optional[int]:
+ """Read-only property accessing the zoom factor."""
+ return self.get("/Zoom", None)
+
+ @property
+ def left(self) -> Optional[FloatObject]:
+ """Read-only property accessing the left horizontal coordinate."""
+ return self.get("/Left", None)
+
+ @property
+ def right(self) -> Optional[FloatObject]:
+ """Read-only property accessing the right horizontal coordinate."""
+ return self.get("/Right", None)
+
+ @property
+ def top(self) -> Optional[FloatObject]:
+ """Read-only property accessing the top vertical coordinate."""
+ return self.get("/Top", None)
+
+ @property
+ def bottom(self) -> Optional[FloatObject]:
+ """Read-only property accessing the bottom vertical coordinate."""
+ return self.get("/Bottom", None)
+
+ @property
+ def color(self) -> Optional["ArrayObject"]:
+ """Read-only property accessing the color in (R, G, B) with values 0.0-1.0."""
+ return self.get(
+ "/C", ArrayObject([FloatObject(0), FloatObject(0), FloatObject(0)])
+ )
+
+ @property
+ def font_format(self) -> Optional[OutlineFontFlag]:
+ """
+ Read-only property accessing the font type.
+
+ 1=italic, 2=bold, 3=both
+ """
+ return self.get("/F", 0)
+
+ @property
+ def outline_count(self) -> Optional[int]:
+ """
+ Read-only property accessing the outline count.
+
+ positive = expanded
+ negative = collapsed
+ absolute value = number of visible descendants at all levels
+ """
+ return self.get("/Count", None)
diff --git a/.venv/lib/python3.12/site-packages/pypdf/generic/_fit.py b/.venv/lib/python3.12/site-packages/pypdf/generic/_fit.py
new file mode 100644
index 00000000..4132f4b7
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/pypdf/generic/_fit.py
@@ -0,0 +1,168 @@
+from typing import Any, Optional, Tuple, Union
+
+
+class Fit:
+ def __init__(
+ self, fit_type: str, fit_args: Tuple[Union[None, float, Any], ...] = ()
+ ):
+ from ._base import FloatObject, NameObject, NullObject
+
+ self.fit_type = NameObject(fit_type)
+ self.fit_args = [
+ NullObject() if a is None or isinstance(a, NullObject) else FloatObject(a)
+ for a in fit_args
+ ]
+
+ @classmethod
+ def xyz(
+ cls,
+ left: Optional[float] = None,
+ top: Optional[float] = None,
+ zoom: Optional[float] = None,
+ ) -> "Fit":
+ """
+ Display the page designated by page, with the coordinates (left, top)
+ positioned at the upper-left corner of the window and the contents
+ of the page magnified by the factor zoom.
+
+ A null value for any of the parameters left, top, or zoom specifies
+ that the current value of that parameter is to be retained unchanged.
+
+ A zoom value of 0 has the same meaning as a null value.
+
+ Args:
+ left:
+ top:
+ zoom:
+
+ Returns:
+ The created fit object.
+ """
+ return Fit(fit_type="/XYZ", fit_args=(left, top, zoom))
+
+ @classmethod
+ def fit(cls) -> "Fit":
+ """
+ Display the page designated by page, with its contents magnified just
+ enough to fit the entire page within the window both horizontally and
+ vertically.
+
+ If the required horizontal and vertical magnification factors are
+ different, use the smaller of the two, centering the page within the
+ window in the other dimension.
+ """
+ return Fit(fit_type="/Fit")
+
+ @classmethod
+ def fit_horizontally(cls, top: Optional[float] = None) -> "Fit":
+ """
+ Display the page designated by page, with the vertical coordinate top
+ positioned at the top edge of the window and the contents of the page
+ magnified just enough to fit the entire width of the page within the
+ window.
+
+ A null value for ``top`` specifies that the current value of that
+ parameter is to be retained unchanged.
+
+ Args:
+ top:
+
+ Returns:
+ The created fit object.
+ """
+ return Fit(fit_type="/FitH", fit_args=(top,))
+
+ @classmethod
+ def fit_vertically(cls, left: Optional[float] = None) -> "Fit":
+ return Fit(fit_type="/FitV", fit_args=(left,))
+
+ @classmethod
+ def fit_rectangle(
+ cls,
+ left: Optional[float] = None,
+ bottom: Optional[float] = None,
+ right: Optional[float] = None,
+ top: Optional[float] = None,
+ ) -> "Fit":
+ """
+ Display the page designated by page, with its contents magnified
+ just enough to fit the rectangle specified by the coordinates
+ left, bottom, right, and top entirely within the window
+ both horizontally and vertically.
+
+ If the required horizontal and vertical magnification factors are
+ different, use the smaller of the two, centering the rectangle within
+ the window in the other dimension.
+
+ A null value for any of the parameters may result in unpredictable
+ behavior.
+
+ Args:
+ left:
+ bottom:
+ right:
+ top:
+
+ Returns:
+ The created fit object.
+ """
+ return Fit(fit_type="/FitR", fit_args=(left, bottom, right, top))
+
+ @classmethod
+ def fit_box(cls) -> "Fit":
+ """
+ Display the page designated by page, with its contents magnified just
+ enough to fit its bounding box entirely within the window both
+ horizontally and vertically.
+
+ If the required horizontal and vertical magnification factors are
+ different, use the smaller of the two, centering the bounding box
+ within the window in the other dimension.
+ """
+ return Fit(fit_type="/FitB")
+
+ @classmethod
+ def fit_box_horizontally(cls, top: Optional[float] = None) -> "Fit":
+ """
+ Display the page designated by page, with the vertical coordinate top
+ positioned at the top edge of the window and the contents of the page
+ magnified just enough to fit the entire width of its bounding box
+ within the window.
+
+ A null value for top specifies that the current value of that parameter
+ is to be retained unchanged.
+
+ Args:
+ top:
+
+ Returns:
+ The created fit object.
+ """
+ return Fit(fit_type="/FitBH", fit_args=(top,))
+
+ @classmethod
+ def fit_box_vertically(cls, left: Optional[float] = None) -> "Fit":
+ """
+ Display the page designated by page, with the horizontal coordinate
+ left positioned at the left edge of the window and the contents of the
+ page magnified just enough to fit the entire height of its bounding box
+ within the window.
+
+ A null value for left specifies that the current value of that
+ parameter is to be retained unchanged.
+
+ Args:
+ left:
+
+ Returns:
+ The created fit object.
+ """
+ return Fit(fit_type="/FitBV", fit_args=(left,))
+
+ def __str__(self) -> str:
+ if not self.fit_args:
+ return f"Fit({self.fit_type})"
+ return f"Fit({self.fit_type}, {self.fit_args})"
+
+
+DEFAULT_FIT = Fit.fit()
diff --git a/.venv/lib/python3.12/site-packages/pypdf/generic/_image_inline.py b/.venv/lib/python3.12/site-packages/pypdf/generic/_image_inline.py
new file mode 100644
index 00000000..41826ac3
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/pypdf/generic/_image_inline.py
@@ -0,0 +1,235 @@
+# Copyright (c) 2024, pypdf contributors
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
+#
+# * Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
+# * Redistributions in binary form must reproduce the above copyright notice,
+# this list of conditions and the following disclaimer in the documentation
+# and/or other materials provided with the distribution.
+# * The name of the author may not be used to endorse or promote products
+# derived from this software without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+# POSSIBILITY OF SUCH DAMAGE.
+
+import logging
+from io import BytesIO
+
+from .._utils import (
+ WHITESPACES,
+ StreamType,
+ read_non_whitespace,
+)
+from ..errors import PdfReadError
+
+logger = logging.getLogger(__name__)
+
+BUFFER_SIZE = 8192
+
+
+def extract_inline_AHx(stream: StreamType) -> bytes:
+ """
+ Extract HexEncoded Stream from Inline Image.
+ the stream will be moved onto the EI
+ """
+ data_out: bytes = b""
+ # Read data until delimiter > and EI as backup
+ # ignoring backup.
+ while True:
+ data_buffered = read_non_whitespace(stream) + stream.read(BUFFER_SIZE)
+ if not data_buffered:
+ raise PdfReadError("Unexpected end of stream")
+ pos_tok = data_buffered.find(b">")
+ if pos_tok >= 0: # found >
+ data_out += data_buffered[: (pos_tok + 1)]
+ stream.seek(-len(data_buffered) + pos_tok + 1, 1)
+ break
+ pos_ei = data_buffered.find(b"EI")
+ if pos_ei >= 0: # found EI
+ stream.seek(-len(data_buffered) + pos_ei - 1, 1)
+ c = stream.read(1)
+ while c in WHITESPACES:
+ stream.seek(-2, 1)
+ c = stream.read(1)
+ pos_ei -= 1
+ data_out += data_buffered[:pos_ei]
+ break
+ elif len(data_buffered) == 2:
+ data_out += data_buffered
+ raise PdfReadError("Unexpected end of stream")
+ else: # > nor EI found
+ data_out += data_buffered[:-2]
+ stream.seek(-2, 1)
+
+ ei_tok = read_non_whitespace(stream)
+ ei_tok += stream.read(2)
+ stream.seek(-3, 1)
+ if ei_tok[0:2] != b"EI" or not (ei_tok[2:3] == b"" or ei_tok[2:3] in WHITESPACES):
+ raise PdfReadError("EI stream not found")
+ return data_out
+
+
+def extract_inline_A85(stream: StreamType) -> bytes:
+ """
+ Extract A85 Stream from Inline Image.
+ the stream will be moved onto the EI
+ """
+ data_out: bytes = b""
+ # Read data up to delimiter ~>
+ # see §3.3.2 from PDF ref 1.7
+ while True:
+ data_buffered = read_non_whitespace(stream) + stream.read(BUFFER_SIZE)
+ if not data_buffered:
+ raise PdfReadError("Unexpected end of stream")
+ pos_tok = data_buffered.find(b"~>")
+ if pos_tok >= 0: # found!
+ data_out += data_buffered[: pos_tok + 2]
+ stream.seek(-len(data_buffered) + pos_tok + 2, 1)
+ break
+ elif len(data_buffered) == 2: # end of buffer
+ data_out += data_buffered
+ raise PdfReadError("Unexpected end of stream")
+ data_out += data_buffered[
+ :-2
+ ] # back by one char in case of in the middle of ~>
+ stream.seek(-2, 1)
+
+ ei_tok = read_non_whitespace(stream)
+ ei_tok += stream.read(2)
+ stream.seek(-3, 1)
+ if ei_tok[0:2] != b"EI" or not (ei_tok[2:3] == b"" or ei_tok[2:3] in WHITESPACES):
+ raise PdfReadError("EI stream not found")
+ return data_out
+
+
+def extract_inline_RL(stream: StreamType) -> bytes:
+ """
+ Extract RL Stream from Inline Image.
+ the stream will be moved onto the EI
+ """
+ data_out: bytes = b""
+ # Read data up to delimiter ~>
+ # see §3.3.4 from PDF ref 1.7
+ while True:
+ data_buffered = stream.read(BUFFER_SIZE)
+ if not data_buffered:
+ raise PdfReadError("Unexpected end of stream")
+ pos_tok = data_buffered.find(b"\x80")
+ if pos_tok >= 0: # found
+ data_out += data_buffered[: pos_tok + 1]
+ stream.seek(-len(data_buffered) + pos_tok + 1, 1)
+ break
+ data_out += data_buffered
+
+ ei_tok = read_non_whitespace(stream)
+ ei_tok += stream.read(2)
+ stream.seek(-3, 1)
+ if ei_tok[0:2] != b"EI" or not (ei_tok[2:3] == b"" or ei_tok[2:3] in WHITESPACES):
+ raise PdfReadError("EI stream not found")
+ return data_out
+
+
+def extract_inline_DCT(stream: StreamType) -> bytes:
+ """
+ Extract DCT (JPEG) Stream from Inline Image.
+ the stream will be moved onto the EI
+ """
+ data_out: bytes = b""
+ # Read Blocks of data (ID/Size/data) up to ID=FF/D9
+ # see https://www.digicamsoft.com/itu/itu-t81-36.html
+ notfirst = False
+ while True:
+ c = stream.read(1)
+ if notfirst or (c == b"\xff"):
+ data_out += c
+ if c != b"\xff":
+ continue
+ else:
+ notfirst = True
+ c = stream.read(1)
+ data_out += c
+ if c == b"\xff":
+ stream.seek(-1, 1) # pragma: no cover
+ elif c == b"\x00": # stuffing
+ pass
+ elif c == b"\xd9": # end
+ break
+ elif c in (
+ b"\xc0\xc1\xc2\xc3\xc4\xc5\xc6\xc7\xc9\xca\xcb\xcc\xcd\xce\xcf"
+ b"\xda\xdb\xdc\xdd\xde\xdf"
+ b"\xe0\xe1\xe2\xe3\xe4\xe5\xe6\xe7\xe8\xe9\xea\xeb\xec\xed\xee\xef\xfe"
+ ):
+ c = stream.read(2)
+ data_out += c
+ sz = c[0] * 256 + c[1]
+ data_out += stream.read(sz - 2)
+ # else: pass
+
+ ei_tok = read_non_whitespace(stream)
+ ei_tok += stream.read(2)
+ stream.seek(-3, 1)
+ if ei_tok[0:2] != b"EI" or not (ei_tok[2:3] == b"" or ei_tok[2:3] in WHITESPACES):
+ raise PdfReadError("EI stream not found")
+ return data_out
+
+
+def extract_inline_default(stream: StreamType) -> bytes:
+ """
+ Legacy method
+ used by default
+ """
+ stream_out = BytesIO()
+ # Read the inline image, while checking for EI (End Image) operator.
+ while True:
+ data_buffered = stream.read(BUFFER_SIZE)
+ if not data_buffered:
+ raise PdfReadError("Unexpected end of stream")
+ pos_ei = data_buffered.find(
+ b"E"
+ ) # we can not look straight for "EI" because it may not have been loaded in the buffer
+
+ if pos_ei == -1:
+ stream_out.write(data_buffered)
+ else:
+ # Write out everything including E (the one from EI to be removed).
+ stream_out.write(data_buffered[0 : pos_ei + 1])
+ sav_pos_ei = stream_out.tell() - 1
+ # Seek back in the stream to read the E next.
+ stream.seek(pos_ei + 1 - len(data_buffered), 1)
+ saved_pos = stream.tell()
+ # Check for End Image
+ tok2 = stream.read(1) # I of "EI"
+ if tok2 != b"I":
+ stream.seek(saved_pos, 0)
+ continue
+ tok3 = stream.read(1) # possible space after "EI"
+ if tok3 not in WHITESPACES:
+ stream.seek(saved_pos, 0)
+ continue
+ while tok3 in WHITESPACES:
+ tok3 = stream.read(1)
+ if data_buffered[pos_ei - 1 : pos_ei] not in WHITESPACES and tok3 not in {
+ b"Q",
+ b"E",
+ }: # for Q ou EMC
+ stream.seek(saved_pos, 0)
+ continue
+ # Data contains [\s]EI[\s](Q|EMC): 4 chars are sufficients
+ # remove E(I) wrongly inserted earlier
+ stream_out.truncate(sav_pos_ei)
+ break
+
+ return stream_out.getvalue()
diff --git a/.venv/lib/python3.12/site-packages/pypdf/generic/_outline.py b/.venv/lib/python3.12/site-packages/pypdf/generic/_outline.py
new file mode 100644
index 00000000..4d6a47da
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/pypdf/generic/_outline.py
@@ -0,0 +1,33 @@
+from typing import Union
+
+from .._utils import StreamType, deprecate_no_replacement
+from ._base import NameObject
+from ._data_structures import Destination
+
+
+class OutlineItem(Destination):
+ def write_to_stream(
+ self, stream: StreamType, encryption_key: Union[None, str, bytes] = None
+ ) -> None:
+ if encryption_key is not None: # deprecated
+ deprecate_no_replacement(
+ "the encryption_key parameter of write_to_stream", "5.0.0"
+ )
+ stream.write(b"<<\n")
+ for key in [
+ NameObject(x)
+ for x in ["/Title", "/Parent", "/First", "/Last", "/Next", "/Prev"]
+ if x in self
+ ]:
+ key.write_to_stream(stream)
+ stream.write(b" ")
+ value = self.raw_get(key)
+ value.write_to_stream(stream)
+ stream.write(b"\n")
+ key = NameObject("/Dest")
+ key.write_to_stream(stream)
+ stream.write(b" ")
+ value = self.dest_array
+ value.write_to_stream(stream)
+ stream.write(b"\n")
+ stream.write(b">>")
diff --git a/.venv/lib/python3.12/site-packages/pypdf/generic/_rectangle.py b/.venv/lib/python3.12/site-packages/pypdf/generic/_rectangle.py
new file mode 100644
index 00000000..690b5217
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/pypdf/generic/_rectangle.py
@@ -0,0 +1,132 @@
+from typing import Any, Tuple, Union
+
+from ._base import FloatObject, NumberObject
+from ._data_structures import ArrayObject
+
+
+class RectangleObject(ArrayObject):
+ """
+ This class is used to represent *page boxes* in pypdf.
+
+ These boxes include:
+
+ * :attr:`artbox <pypdf._page.PageObject.artbox>`
+ * :attr:`bleedbox <pypdf._page.PageObject.bleedbox>`
+ * :attr:`cropbox <pypdf._page.PageObject.cropbox>`
+ * :attr:`mediabox <pypdf._page.PageObject.mediabox>`
+ * :attr:`trimbox <pypdf._page.PageObject.trimbox>`
+ """
+
+ def __init__(
+ self, arr: Union["RectangleObject", Tuple[float, float, float, float]]
+ ) -> None:
+ # must have four points
+ assert len(arr) == 4
+ # automatically convert arr[x] into NumberObject(arr[x]) if necessary
+ ArrayObject.__init__(self, [self._ensure_is_number(x) for x in arr]) # type: ignore
+
+ def _ensure_is_number(self, value: Any) -> Union[FloatObject, NumberObject]:
+ if not isinstance(value, (NumberObject, FloatObject)):
+ value = FloatObject(value)
+ return value
+
+ def scale(self, sx: float, sy: float) -> "RectangleObject":
+ return RectangleObject(
+ (
+ float(self.left) * sx,
+ float(self.bottom) * sy,
+ float(self.right) * sx,
+ float(self.top) * sy,
+ )
+ )
+
+ def __repr__(self) -> str:
+ return f"RectangleObject({list(self)!r})"
+
+ @property
+ def left(self) -> FloatObject:
+ return self[0]
+
+ @left.setter
+ def left(self, f: float) -> None:
+ self[0] = FloatObject(f)
+
+ @property
+ def bottom(self) -> FloatObject:
+ return self[1]
+
+ @bottom.setter
+ def bottom(self, f: float) -> None:
+ self[1] = FloatObject(f)
+
+ @property
+ def right(self) -> FloatObject:
+ return self[2]
+
+ @right.setter
+ def right(self, f: float) -> None:
+ self[2] = FloatObject(f)
+
+ @property
+ def top(self) -> FloatObject:
+ return self[3]
+
+ @top.setter
+ def top(self, f: float) -> None:
+ self[3] = FloatObject(f)
+
+ @property
+ def lower_left(self) -> Tuple[float, float]:
+ """
+ Property to read and modify the lower left coordinate of this box
+ in (x,y) form.
+ """
+ return self.left, self.bottom
+
+ @lower_left.setter
+ def lower_left(self, value: Tuple[float, float]) -> None:
+ self[0], self[1] = (self._ensure_is_number(x) for x in value)
+
+ @property
+ def lower_right(self) -> Tuple[float, float]:
+ """
+ Property to read and modify the lower right coordinate of this box
+ in (x,y) form.
+ """
+ return self.right, self.bottom
+
+ @lower_right.setter
+ def lower_right(self, value: Tuple[float, float]) -> None:
+ self[2], self[1] = (self._ensure_is_number(x) for x in value)
+
+ @property
+ def upper_left(self) -> Tuple[float, float]:
+ """
+ Property to read and modify the upper left coordinate of this box
+ in (x,y) form.
+ """
+ return self.left, self.top
+
+ @upper_left.setter
+ def upper_left(self, value: Tuple[float, float]) -> None:
+ self[0], self[3] = (self._ensure_is_number(x) for x in value)
+
+ @property
+ def upper_right(self) -> Tuple[float, float]:
+ """
+ Property to read and modify the upper right coordinate of this box
+ in (x,y) form.
+ """
+ return self.right, self.top
+
+ @upper_right.setter
+ def upper_right(self, value: Tuple[float, float]) -> None:
+ self[2], self[3] = (self._ensure_is_number(x) for x in value)
+
+ @property
+ def width(self) -> float:
+ return self.right - self.left
+
+ @property
+ def height(self) -> float:
+ return self.top - self.bottom
diff --git a/.venv/lib/python3.12/site-packages/pypdf/generic/_utils.py b/.venv/lib/python3.12/site-packages/pypdf/generic/_utils.py
new file mode 100644
index 00000000..fdcdc333
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/pypdf/generic/_utils.py
@@ -0,0 +1,180 @@
+import codecs
+from typing import Dict, List, Tuple, Union
+
+from .._codecs import _pdfdoc_encoding
+from .._utils import StreamType, b_, logger_warning, read_non_whitespace
+from ..errors import STREAM_TRUNCATED_PREMATURELY, PdfStreamError
+from ._base import ByteStringObject, TextStringObject
+
+
+def hex_to_rgb(value: str) -> Tuple[float, float, float]:
+ return tuple(int(value.lstrip("#")[i : i + 2], 16) / 255.0 for i in (0, 2, 4)) # type: ignore
+
+
+def read_hex_string_from_stream(
+ stream: StreamType,
+ forced_encoding: Union[None, str, List[str], Dict[int, str]] = None,
+) -> Union["TextStringObject", "ByteStringObject"]:
+ stream.read(1)
+ txt = ""
+ x = b""
+ while True:
+ tok = read_non_whitespace(stream)
+ if not tok:
+ raise PdfStreamError(STREAM_TRUNCATED_PREMATURELY)
+ if tok == b">":
+ break
+ x += tok
+ if len(x) == 2:
+ txt += chr(int(x, base=16))
+ x = b""
+ if len(x) == 1:
+ x += b"0"
+ if len(x) == 2:
+ txt += chr(int(x, base=16))
+ return create_string_object(b_(txt), forced_encoding)
+
+
+def read_string_from_stream(
+ stream: StreamType,
+ forced_encoding: Union[None, str, List[str], Dict[int, str]] = None,
+) -> Union["TextStringObject", "ByteStringObject"]:
+ tok = stream.read(1)
+ parens = 1
+ txt = []
+ while True:
+ tok = stream.read(1)
+ if not tok:
+ raise PdfStreamError(STREAM_TRUNCATED_PREMATURELY)
+ if tok == b"(":
+ parens += 1
+ elif tok == b")":
+ parens -= 1
+ if parens == 0:
+ break
+ elif tok == b"\\":
+ tok = stream.read(1)
+ escape_dict = {
+ b"n": b"\n",
+ b"r": b"\r",
+ b"t": b"\t",
+ b"b": b"\b",
+ b"f": b"\f",
+ b"c": rb"\c",
+ b"(": b"(",
+ b")": b")",
+ b"/": b"/",
+ b"\\": b"\\",
+ b" ": b" ",
+ b"%": b"%",
+ b"<": b"<",
+ b">": b">",
+ b"[": b"[",
+ b"]": b"]",
+ b"#": b"#",
+ b"_": b"_",
+ b"&": b"&",
+ b"$": b"$",
+ }
+ try:
+ tok = escape_dict[tok]
+ except KeyError:
+ if b"0" <= tok <= b"7":
+ # "The number ddd may consist of one, two, or three
+ # octal digits; high-order overflow shall be ignored.
+ # Three octal digits shall be used, with leading zeros
+ # as needed, if the next character of the string is also
+ # a digit." (PDF reference 7.3.4.2, p 16)
+ for _ in range(2):
+ ntok = stream.read(1)
+ if b"0" <= ntok <= b"7":
+ tok += ntok
+ else:
+ stream.seek(-1, 1) # ntok has to be analyzed
+ break
+ tok = b_(chr(int(tok, base=8)))
+ elif tok in b"\n\r":
+ # This case is hit when a backslash followed by a line
+ # break occurs. If it's a multi-char EOL, consume the
+ # second character:
+ tok = stream.read(1)
+ if tok not in b"\n\r":
+ stream.seek(-1, 1)
+ # Then don't add anything to the actual string, since this
+ # line break was escaped:
+ tok = b""
+ else:
+ msg = f"Unexpected escaped string: {tok.decode('utf-8','ignore')}"
+ logger_warning(msg, __name__)
+ txt.append(tok)
+ return create_string_object(b"".join(txt), forced_encoding)
+
+
+def create_string_object(
+ string: Union[str, bytes],
+ forced_encoding: Union[None, str, List[str], Dict[int, str]] = None,
+) -> Union[TextStringObject, ByteStringObject]:
+ """
+ Create a ByteStringObject or a TextStringObject from a string to represent the string.
+
+ Args:
+ string: The data being used
+ forced_encoding: Typically None, or an encoding string
+
+ Returns:
+ A ByteStringObject
+
+ Raises:
+ TypeError: If string is not of type str or bytes.
+ """
+ if isinstance(string, str):
+ return TextStringObject(string)
+ elif isinstance(string, bytes):
+ if isinstance(forced_encoding, (list, dict)):
+ out = ""
+ for x in string:
+ try:
+ out += forced_encoding[x]
+ except Exception:
+ out += bytes((x,)).decode("charmap")
+ return TextStringObject(out)
+ elif isinstance(forced_encoding, str):
+ if forced_encoding == "bytes":
+ return ByteStringObject(string)
+ return TextStringObject(string.decode(forced_encoding))
+ else:
+ try:
+ if string.startswith((codecs.BOM_UTF16_BE, codecs.BOM_UTF16_LE)):
+ retval = TextStringObject(string.decode("utf-16"))
+ retval.autodetect_utf16 = True
+ retval.utf16_bom = string[:2]
+ return retval
+ else:
+ # This is probably a big performance hit here, but we need
+ # to convert string objects into the text/unicode-aware
+ # version if possible... and the only way to check if that's
+ # possible is to try.
+ # Some strings are strings, some are just byte arrays.
+ retval = TextStringObject(decode_pdfdocencoding(string))
+ retval.autodetect_pdfdocencoding = True
+ return retval
+ except UnicodeDecodeError:
+ return ByteStringObject(string)
+ else:
+ raise TypeError("create_string_object should have str or unicode arg")
+
+
+def decode_pdfdocencoding(byte_array: bytes) -> str:
+ retval = ""
+ for b in byte_array:
+ c = _pdfdoc_encoding[b]
+ if c == "\u0000":
+ raise UnicodeDecodeError(
+ "pdfdocencoding",
+ bytearray(b),
+ -1,
+ -1,
+ "does not exist in translation table",
+ )
+ retval += c
+ return retval
diff --git a/.venv/lib/python3.12/site-packages/pypdf/generic/_viewerpref.py b/.venv/lib/python3.12/site-packages/pypdf/generic/_viewerpref.py
new file mode 100644
index 00000000..a12f2d44
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/pypdf/generic/_viewerpref.py
@@ -0,0 +1,164 @@
+# Copyright (c) 2023, Pubpub-ZZ
+#
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
+#
+# * Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
+# * Redistributions in binary form must reproduce the above copyright notice,
+# this list of conditions and the following disclaimer in the documentation
+# and/or other materials provided with the distribution.
+# * The name of the author may not be used to endorse or promote products
+# derived from this software without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+# POSSIBILITY OF SUCH DAMAGE.
+
+from typing import (
+ Any,
+ List,
+ Optional,
+)
+
+from ._base import BooleanObject, NameObject, NumberObject
+from ._data_structures import ArrayObject, DictionaryObject
+
+f_obj = BooleanObject(False)
+
+
+class ViewerPreferences(DictionaryObject):
+ def _get_bool(self, key: str, deft: Optional[BooleanObject]) -> BooleanObject:
+ return self.get(key, deft)
+
+ def _set_bool(self, key: str, v: bool) -> None:
+ self[NameObject(key)] = BooleanObject(v is True)
+
+ def _get_name(self, key: str, deft: Optional[NameObject]) -> Optional[NameObject]:
+ return self.get(key, deft)
+
+ def _set_name(self, key: str, lst: List[str], v: NameObject) -> None:
+ if v[0] != "/":
+ raise ValueError(f"{v} is not starting with '/'")
+ if lst != [] and v not in lst:
+ raise ValueError(f"{v} is not par of acceptable values")
+ self[NameObject(key)] = NameObject(v)
+
+ def _get_arr(self, key: str, deft: Optional[List[Any]]) -> NumberObject:
+ return self.get(key, None if deft is None else ArrayObject(deft))
+
+ def _set_arr(self, key: str, v: Optional[ArrayObject]) -> None:
+ if v is None:
+ try:
+ del self[NameObject(key)]
+ except KeyError:
+ pass
+ return
+ if not isinstance(v, ArrayObject):
+ raise ValueError("ArrayObject is expected")
+ self[NameObject(key)] = v
+
+ def _get_int(self, key: str, deft: Optional[NumberObject]) -> NumberObject:
+ return self.get(key, deft)
+
+ def _set_int(self, key: str, v: int) -> None:
+ self[NameObject(key)] = NumberObject(v)
+
+ @property
+ def PRINT_SCALING(self) -> NameObject:
+ return NameObject("/PrintScaling")
+
+ def __new__(cls: Any, value: Any = None) -> "ViewerPreferences":
+ def _add_prop_bool(key: str, deft: Optional[BooleanObject]) -> property:
+ return property(
+ lambda self: self._get_bool(key, deft),
+ lambda self, v: self._set_bool(key, v),
+ None,
+ f"""
+ Returns/Modify the status of {key}, Returns {deft} if not defined
+ """,
+ )
+
+ def _add_prop_name(
+ key: str, lst: List[str], deft: Optional[NameObject]
+ ) -> property:
+ return property(
+ lambda self: self._get_name(key, deft),
+ lambda self, v: self._set_name(key, lst, v),
+ None,
+ f"""
+ Returns/Modify the status of {key}, Returns {deft} if not defined.
+ Acceptable values: {lst}
+ """,
+ )
+
+ def _add_prop_arr(key: str, deft: Optional[ArrayObject]) -> property:
+ return property(
+ lambda self: self._get_arr(key, deft),
+ lambda self, v: self._set_arr(key, v),
+ None,
+ f"""
+ Returns/Modify the status of {key}, Returns {deft} if not defined
+ """,
+ )
+
+ def _add_prop_int(key: str, deft: Optional[int]) -> property:
+ return property(
+ lambda self: self._get_int(key, deft),
+ lambda self, v: self._set_int(key, v),
+ None,
+ f"""
+ Returns/Modify the status of {key}, Returns {deft} if not defined
+ """,
+ )
+
+ cls.hide_toolbar = _add_prop_bool("/HideToolbar", f_obj)
+ cls.hide_menubar = _add_prop_bool("/HideMenubar", f_obj)
+ cls.hide_windowui = _add_prop_bool("/HideWindowUI", f_obj)
+ cls.fit_window = _add_prop_bool("/FitWindow", f_obj)
+ cls.center_window = _add_prop_bool("/CenterWindow", f_obj)
+ cls.display_doctitle = _add_prop_bool("/DisplayDocTitle", f_obj)
+
+ cls.non_fullscreen_pagemode = _add_prop_name(
+ "/NonFullScreenPageMode",
+ ["/UseNone", "/UseOutlines", "/UseThumbs", "/UseOC"],
+ NameObject("/UseNone"),
+ )
+ cls.direction = _add_prop_name(
+ "/Direction", ["/L2R", "/R2L"], NameObject("/L2R")
+ )
+ cls.view_area = _add_prop_name("/ViewArea", [], None)
+ cls.view_clip = _add_prop_name("/ViewClip", [], None)
+ cls.print_area = _add_prop_name("/PrintArea", [], None)
+ cls.print_clip = _add_prop_name("/PrintClip", [], None)
+ cls.print_scaling = _add_prop_name("/PrintScaling", [], None)
+ cls.duplex = _add_prop_name(
+ "/Duplex", ["/Simplex", "/DuplexFlipShortEdge", "/DuplexFlipLongEdge"], None
+ )
+ cls.pick_tray_by_pdfsize = _add_prop_bool("/PickTrayByPDFSize", None)
+ cls.print_pagerange = _add_prop_arr("/PrintPageRange", None)
+ cls.num_copies = _add_prop_int("/NumCopies", None)
+
+ cls.enforce = _add_prop_arr("/Enforce", ArrayObject())
+
+ return DictionaryObject.__new__(cls)
+
+ def __init__(self, obj: Optional[DictionaryObject] = None) -> None:
+ super().__init__(self)
+ if obj is not None:
+ self.update(obj.items())
+ try:
+ self.indirect_reference = obj.indirect_reference # type: ignore
+ except AttributeError:
+ pass
diff --git a/.venv/lib/python3.12/site-packages/pypdf/pagerange.py b/.venv/lib/python3.12/site-packages/pypdf/pagerange.py
new file mode 100644
index 00000000..47a72c72
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/pypdf/pagerange.py
@@ -0,0 +1,192 @@
+"""
+Representation and utils for ranges of PDF file pages.
+
+Copyright (c) 2014, Steve Witham <switham_github@mac-guyver.com>.
+All rights reserved. This software is available under a BSD license;
+see https://github.com/py-pdf/pypdf/blob/main/LICENSE
+"""
+
+import re
+from typing import Any, List, Tuple, Union
+
+from .errors import ParseError
+
+_INT_RE = r"(0|-?[1-9]\d*)" # A decimal int, don't allow "-0".
+PAGE_RANGE_RE = f"^({_INT_RE}|({_INT_RE}?(:{_INT_RE}?(:{_INT_RE}?)?)))$"
+# groups: 12 34 5 6 7 8
+
+
+class PageRange:
+ """
+ A slice-like representation of a range of page indices.
+
+ For example, page numbers, only starting at zero.
+
+ The syntax is like what you would put between brackets [ ].
+ The slice is one of the few Python types that can't be subclassed,
+ but this class converts to and from slices, and allows similar use.
+
+ - PageRange(str) parses a string representing a page range.
+ - PageRange(slice) directly "imports" a slice.
+ - to_slice() gives the equivalent slice.
+ - str() and repr() allow printing.
+ - indices(n) is like slice.indices(n).
+ """
+
+ def __init__(self, arg: Union[slice, "PageRange", str]) -> None:
+ """
+ Initialize with either a slice -- giving the equivalent page range,
+ or a PageRange object -- making a copy,
+ or a string like
+ "int", "[int]:[int]" or "[int]:[int]:[int]",
+ where the brackets indicate optional ints.
+ Remember, page indices start with zero.
+ Page range expression examples:
+
+ : all pages. -1 last page.
+ 22 just the 23rd page. :-1 all but the last page.
+ 0:3 the first three pages. -2 second-to-last page.
+ :3 the first three pages. -2: last two pages.
+ 5: from the sixth page onward. -3:-1 third & second to last.
+ The third, "stride" or "step" number is also recognized.
+ ::2 0 2 4 ... to the end. 3:0:-1 3 2 1 but not 0.
+ 1:10:2 1 3 5 7 9 2::-1 2 1 0.
+ ::-1 all pages in reverse order.
+ Note the difference between this notation and arguments to slice():
+ slice(3) means the first three pages;
+ PageRange("3") means the range of only the fourth page.
+ However PageRange(slice(3)) means the first three pages.
+ """
+ if isinstance(arg, slice):
+ self._slice = arg
+ return
+
+ if isinstance(arg, PageRange):
+ self._slice = arg.to_slice()
+ return
+
+ m = isinstance(arg, str) and re.match(PAGE_RANGE_RE, arg)
+ if not m:
+ raise ParseError(arg)
+ elif m.group(2):
+ # Special case: just an int means a range of one page.
+ start = int(m.group(2))
+ stop = start + 1 if start != -1 else None
+ self._slice = slice(start, stop)
+ else:
+ self._slice = slice(*[int(g) if g else None for g in m.group(4, 6, 8)])
+
+ @staticmethod
+ def valid(input: Any) -> bool:
+ """
+ True if input is a valid initializer for a PageRange.
+
+ Args:
+ input: A possible PageRange string or a PageRange object.
+
+ Returns:
+ True, if the ``input`` is a valid PageRange.
+ """
+ return isinstance(input, (slice, PageRange)) or (
+ isinstance(input, str) and bool(re.match(PAGE_RANGE_RE, input))
+ )
+
+ def to_slice(self) -> slice:
+ """Return the slice equivalent of this page range."""
+ return self._slice
+
+ def __str__(self) -> str:
+ """A string like "1:2:3"."""
+ s = self._slice
+ indices: Union[Tuple[int, int], Tuple[int, int, int]]
+ if s.step is None:
+ if s.start is not None and s.stop == s.start + 1:
+ return str(s.start)
+
+ indices = s.start, s.stop
+ else:
+ indices = s.start, s.stop, s.step
+ return ":".join("" if i is None else str(i) for i in indices)
+
+ def __repr__(self) -> str:
+ """A string like "PageRange('1:2:3')"."""
+ return "PageRange(" + repr(str(self)) + ")"
+
+ def indices(self, n: int) -> Tuple[int, int, int]:
+ """
+ Assuming a sequence of length n, calculate the start and stop indices,
+ and the stride length of the PageRange.
+
+ See help(slice.indices).
+
+ Args:
+ n: the length of the list of pages to choose from.
+
+ Returns:
+ Arguments for range().
+ """
+ return self._slice.indices(n)
+
+ def __eq__(self, other: object) -> bool:
+ if not isinstance(other, PageRange):
+ return False
+ return self._slice == other._slice
+
+ def __add__(self, other: "PageRange") -> "PageRange":
+ if not isinstance(other, PageRange):
+ raise TypeError(f"Can't add PageRange and {type(other)}")
+ if self._slice.step is not None or other._slice.step is not None:
+ raise ValueError("Can't add PageRange with stride")
+ a = self._slice.start, self._slice.stop
+ b = other._slice.start, other._slice.stop
+
+ if a[0] > b[0]:
+ a, b = b, a
+
+ # Now a[0] is the smallest
+ if b[0] > a[1]:
+ # There is a gap between a and b.
+ raise ValueError("Can't add PageRanges with gap")
+ return PageRange(slice(a[0], max(a[1], b[1])))
+
+
+PAGE_RANGE_ALL = PageRange(":") # The range of all pages.
+
+
+def parse_filename_page_ranges(
+ args: List[Union[str, PageRange, None]]
+) -> List[Tuple[str, PageRange]]:
+ """
+ Given a list of filenames and page ranges, return a list of (filename, page_range) pairs.
+
+ Args:
+ args: A list where the first element is a filename. The other elements are
+ filenames, page-range expressions, slice objects, or PageRange objects.
+ A filename not followed by a page range indicates all pages of the file.
+
+ Returns:
+ A list of (filename, page_range) pairs.
+ """
+ pairs: List[Tuple[str, PageRange]] = []
+ pdf_filename = None
+ did_page_range = False
+ for arg in args + [None]:
+ if PageRange.valid(arg):
+ if not pdf_filename:
+ raise ValueError(
+ "The first argument must be a filename, not a page range."
+ )
+
+ pairs.append((pdf_filename, PageRange(arg)))
+ did_page_range = True
+ else:
+ # New filename or end of list--do all of the previous file?
+ if pdf_filename and not did_page_range:
+ pairs.append((pdf_filename, PAGE_RANGE_ALL))
+
+ pdf_filename = arg
+ did_page_range = False
+ return pairs
+
+
+PageRangeSpec = Union[str, PageRange, Tuple[int, int], Tuple[int, int, int], List[int]]
diff --git a/.venv/lib/python3.12/site-packages/pypdf/papersizes.py b/.venv/lib/python3.12/site-packages/pypdf/papersizes.py
new file mode 100644
index 00000000..2d83e1d5
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/pypdf/papersizes.py
@@ -0,0 +1,51 @@
+"""Helper to get paper sizes."""
+
+from typing import NamedTuple
+
+
+class Dimensions(NamedTuple):
+ width: int
+ height: int
+
+
+class PaperSize:
+ """(width, height) of the paper in portrait mode in pixels at 72 ppi."""
+
+ # Notes how to calculate it:
+ # 1. Get the size of the paper in mm
+ # 2. Convert it to inches (25.4 millimeters are equal to 1 inches)
+ # 3. Convert it to pixels ad 72dpi (1 inch is equal to 72 pixels)
+
+ # All Din-A paper sizes follow this pattern:
+ # 2xA(n-1) = A(n)
+ # So the height of the next bigger one is the width of the smaller one
+ # The ratio is always approximately the ratio 1:2**0.5
+ # Additionally, A0 is defined to have an area of 1 m**2
+ # Be aware of rounding issues!
+ A0 = Dimensions(2384, 3370) # 841mm x 1189mm
+ A1 = Dimensions(1684, 2384)
+ A2 = Dimensions(1191, 1684)
+ A3 = Dimensions(842, 1191)
+ A4 = Dimensions(
+ 595, 842
+ ) # Printer paper, documents - this is by far the most common
+ A5 = Dimensions(420, 595) # Paperback books
+ A6 = Dimensions(298, 420) # Postcards
+ A7 = Dimensions(210, 298)
+ A8 = Dimensions(147, 210)
+
+ # Envelopes
+ C4 = Dimensions(649, 918)
+
+
+_din_a = (
+ PaperSize.A0,
+ PaperSize.A1,
+ PaperSize.A2,
+ PaperSize.A3,
+ PaperSize.A4,
+ PaperSize.A5,
+ PaperSize.A6,
+ PaperSize.A7,
+ PaperSize.A8,
+)
diff --git a/.venv/lib/python3.12/site-packages/pypdf/py.typed b/.venv/lib/python3.12/site-packages/pypdf/py.typed
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/pypdf/py.typed
diff --git a/.venv/lib/python3.12/site-packages/pypdf/types.py b/.venv/lib/python3.12/site-packages/pypdf/types.py
new file mode 100644
index 00000000..b8fbab92
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/pypdf/types.py
@@ -0,0 +1,83 @@
+"""Helpers for working with PDF types."""
+
+import sys
+from typing import List, Union
+
+if sys.version_info[:2] >= (3, 8):
+ # Python 3.8+: https://peps.python.org/pep-0586
+ from typing import Literal
+else:
+ from typing_extensions import Literal
+
+if sys.version_info[:2] >= (3, 10):
+ # Python 3.10+: https://www.python.org/dev/peps/pep-0484
+ from typing import TypeAlias
+else:
+ from typing_extensions import TypeAlias
+
+from .generic._base import NameObject, NullObject, NumberObject
+from .generic._data_structures import ArrayObject, Destination
+from .generic._outline import OutlineItem
+
+BorderArrayType: TypeAlias = List[Union[NameObject, NumberObject, ArrayObject]]
+OutlineItemType: TypeAlias = Union[OutlineItem, Destination]
+FitType: TypeAlias = Literal[
+ "/XYZ", "/Fit", "/FitH", "/FitV", "/FitR", "/FitB", "/FitBH", "/FitBV"
+]
+# Those go with the FitType: They specify values for the fit
+ZoomArgType: TypeAlias = Union[NumberObject, NullObject, float]
+ZoomArgsType: TypeAlias = List[ZoomArgType]
+
+# Recursive types like the following are not yet supported by mypy:
+# OutlineType = List[Union[Destination, "OutlineType"]]
+# See https://github.com/python/mypy/issues/731
+# Hence use this for the moment:
+OutlineType = List[Union[Destination, List[Union[Destination, List[Destination]]]]]
+
+LayoutType: TypeAlias = Literal[
+ "/NoLayout",
+ "/SinglePage",
+ "/OneColumn",
+ "/TwoColumnLeft",
+ "/TwoColumnRight",
+ "/TwoPageLeft",
+ "/TwoPageRight",
+]
+PagemodeType: TypeAlias = Literal[
+ "/UseNone",
+ "/UseOutlines",
+ "/UseThumbs",
+ "/FullScreen",
+ "/UseOC",
+ "/UseAttachments",
+]
+AnnotationSubtype: TypeAlias = Literal[
+ "/Text",
+ "/Link",
+ "/FreeText",
+ "/Line",
+ "/Square",
+ "/Circle",
+ "/Polygon",
+ "/PolyLine",
+ "/Highlight",
+ "/Underline",
+ "/Squiggly",
+ "/StrikeOut",
+ "/Caret",
+ "/Stamp",
+ "/Ink",
+ "/Popup",
+ "/FileAttachment",
+ "/Sound",
+ "/Movie",
+ "/Screen",
+ "/Widget",
+ "/PrinterMark",
+ "/TrapNet",
+ "/Watermark",
+ "/3D",
+ "/Redact",
+ "/Projection",
+ "/RichMedia",
+]
diff --git a/.venv/lib/python3.12/site-packages/pypdf/xmp.py b/.venv/lib/python3.12/site-packages/pypdf/xmp.py
new file mode 100644
index 00000000..df55c905
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/pypdf/xmp.py
@@ -0,0 +1,392 @@
+"""
+Anything related to Extensible Metadata Platform (XMP) metadata.
+
+https://en.wikipedia.org/wiki/Extensible_Metadata_Platform
+"""
+
+import datetime
+import decimal
+import re
+from typing import (
+ Any,
+ Callable,
+ Dict,
+ Iterator,
+ List,
+ Optional,
+ TypeVar,
+ Union,
+)
+from xml.dom.minidom import Document, parseString
+from xml.dom.minidom import Element as XmlElement
+from xml.parsers.expat import ExpatError
+
+from ._utils import StreamType, deprecate_no_replacement
+from .errors import PdfReadError
+from .generic import ContentStream, PdfObject
+
+RDF_NAMESPACE = "http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+DC_NAMESPACE = "http://purl.org/dc/elements/1.1/"
+XMP_NAMESPACE = "http://ns.adobe.com/xap/1.0/"
+PDF_NAMESPACE = "http://ns.adobe.com/pdf/1.3/"
+XMPMM_NAMESPACE = "http://ns.adobe.com/xap/1.0/mm/"
+
+# What is the PDFX namespace, you might ask?
+# It's documented here: https://github.com/adobe/xmp-docs/raw/master/XMPSpecifications/XMPSpecificationPart3.pdf
+# This namespace is used to place "custom metadata"
+# properties, which are arbitrary metadata properties with no semantic or
+# documented meaning.
+#
+# Elements in the namespace are key/value-style storage,
+# where the element name is the key and the content is the value. The keys
+# are transformed into valid XML identifiers by substituting an invalid
+# identifier character with \u2182 followed by the unicode hex ID of the
+# original character. A key like "my car" is therefore "my\u21820020car".
+#
+# \u2182 is the unicode character \u{ROMAN NUMERAL TEN THOUSAND}
+#
+# The pdfx namespace should be avoided.
+# A custom data schema and sensical XML elements could be used instead, as is
+# suggested by Adobe's own documentation on XMP under "Extensibility of
+# Schemas".
+PDFX_NAMESPACE = "http://ns.adobe.com/pdfx/1.3/"
+
+iso8601 = re.compile(
+ """
+ (?P<year>[0-9]{4})
+ (-
+ (?P<month>[0-9]{2})
+ (-
+ (?P<day>[0-9]+)
+ (T
+ (?P<hour>[0-9]{2}):
+ (?P<minute>[0-9]{2})
+ (:(?P<second>[0-9]{2}(.[0-9]+)?))?
+ (?P<tzd>Z|[-+][0-9]{2}:[0-9]{2})
+ )?
+ )?
+ )?
+ """,
+ re.VERBOSE,
+)
+
+
+K = TypeVar("K")
+
+
+def _identity(value: K) -> K:
+ return value
+
+
+def _converter_date(value: str) -> datetime.datetime:
+ matches = iso8601.match(value)
+ if matches is None:
+ raise ValueError(f"Invalid date format: {value}")
+ year = int(matches.group("year"))
+ month = int(matches.group("month") or "1")
+ day = int(matches.group("day") or "1")
+ hour = int(matches.group("hour") or "0")
+ minute = int(matches.group("minute") or "0")
+ second = decimal.Decimal(matches.group("second") or "0")
+ seconds_dec = second.to_integral(decimal.ROUND_FLOOR)
+ milliseconds_dec = (second - seconds_dec) * 1_000_000
+
+ seconds = int(seconds_dec)
+ milliseconds = int(milliseconds_dec)
+
+ tzd = matches.group("tzd") or "Z"
+ dt = datetime.datetime(year, month, day, hour, minute, seconds, milliseconds)
+ if tzd != "Z":
+ tzd_hours, tzd_minutes = (int(x) for x in tzd.split(":"))
+ tzd_hours *= -1
+ if tzd_hours < 0:
+ tzd_minutes *= -1
+ dt = dt + datetime.timedelta(hours=tzd_hours, minutes=tzd_minutes)
+ return dt
+
+
+def _getter_bag(
+ namespace: str, name: str
+) -> Callable[["XmpInformation"], Optional[List[str]]]:
+ def get(self: "XmpInformation") -> Optional[List[str]]:
+ cached = self.cache.get(namespace, {}).get(name)
+ if cached:
+ return cached
+ retval = []
+ for element in self.get_element("", namespace, name):
+ bags = element.getElementsByTagNameNS(RDF_NAMESPACE, "Bag")
+ if len(bags):
+ for bag in bags:
+ for item in bag.getElementsByTagNameNS(RDF_NAMESPACE, "li"):
+ value = self._get_text(item)
+ retval.append(value)
+ ns_cache = self.cache.setdefault(namespace, {})
+ ns_cache[name] = retval
+ return retval
+
+ return get
+
+
+def _getter_seq(
+ namespace: str, name: str, converter: Callable[[Any], Any] = _identity
+) -> Callable[["XmpInformation"], Optional[List[Any]]]:
+ def get(self: "XmpInformation") -> Optional[List[Any]]:
+ cached = self.cache.get(namespace, {}).get(name)
+ if cached:
+ return cached
+ retval = []
+ for element in self.get_element("", namespace, name):
+ seqs = element.getElementsByTagNameNS(RDF_NAMESPACE, "Seq")
+ if len(seqs):
+ for seq in seqs:
+ for item in seq.getElementsByTagNameNS(RDF_NAMESPACE, "li"):
+ value = self._get_text(item)
+ value = converter(value)
+ retval.append(value)
+ else:
+ value = converter(self._get_text(element))
+ retval.append(value)
+ ns_cache = self.cache.setdefault(namespace, {})
+ ns_cache[name] = retval
+ return retval
+
+ return get
+
+
+def _getter_langalt(
+ namespace: str, name: str
+) -> Callable[["XmpInformation"], Optional[Dict[Any, Any]]]:
+ def get(self: "XmpInformation") -> Optional[Dict[Any, Any]]:
+ cached = self.cache.get(namespace, {}).get(name)
+ if cached:
+ return cached
+ retval = {}
+ for element in self.get_element("", namespace, name):
+ alts = element.getElementsByTagNameNS(RDF_NAMESPACE, "Alt")
+ if len(alts):
+ for alt in alts:
+ for item in alt.getElementsByTagNameNS(RDF_NAMESPACE, "li"):
+ value = self._get_text(item)
+ retval[item.getAttribute("xml:lang")] = value
+ else:
+ retval["x-default"] = self._get_text(element)
+ ns_cache = self.cache.setdefault(namespace, {})
+ ns_cache[name] = retval
+ return retval
+
+ return get
+
+
+def _getter_single(
+ namespace: str, name: str, converter: Callable[[str], Any] = _identity
+) -> Callable[["XmpInformation"], Optional[Any]]:
+ def get(self: "XmpInformation") -> Optional[Any]:
+ cached = self.cache.get(namespace, {}).get(name)
+ if cached:
+ return cached
+ value = None
+ for element in self.get_element("", namespace, name):
+ if element.nodeType == element.ATTRIBUTE_NODE:
+ value = element.nodeValue
+ else:
+ value = self._get_text(element)
+ break
+ if value is not None:
+ value = converter(value)
+ ns_cache = self.cache.setdefault(namespace, {})
+ ns_cache[name] = value
+ return value
+
+ return get
+
+
+class XmpInformation(PdfObject):
+ """
+ An object that represents Extensible Metadata Platform (XMP) metadata.
+ Usually accessed by :py:attr:`xmp_metadata()<pypdf.PdfReader.xmp_metadata>`.
+
+ Raises:
+ PdfReadError: if XML is invalid
+ """
+
+ def __init__(self, stream: ContentStream) -> None:
+ self.stream = stream
+ try:
+ data = self.stream.get_data()
+ doc_root: Document = parseString(data) # noqa: S318
+ except ExpatError as e:
+ raise PdfReadError(f"XML in XmpInformation was invalid: {e}")
+ self.rdf_root: XmlElement = doc_root.getElementsByTagNameNS(
+ RDF_NAMESPACE, "RDF"
+ )[0]
+ self.cache: Dict[Any, Any] = {}
+
+ def write_to_stream(
+ self, stream: StreamType, encryption_key: Union[None, str, bytes] = None
+ ) -> None:
+ if encryption_key is not None: # deprecated
+ deprecate_no_replacement(
+ "the encryption_key parameter of write_to_stream", "5.0.0"
+ )
+ self.stream.write_to_stream(stream)
+
+ def get_element(self, about_uri: str, namespace: str, name: str) -> Iterator[Any]:
+ for desc in self.rdf_root.getElementsByTagNameNS(RDF_NAMESPACE, "Description"):
+ if desc.getAttributeNS(RDF_NAMESPACE, "about") == about_uri:
+ attr = desc.getAttributeNodeNS(namespace, name)
+ if attr is not None:
+ yield attr
+ yield from desc.getElementsByTagNameNS(namespace, name)
+
+ def get_nodes_in_namespace(self, about_uri: str, namespace: str) -> Iterator[Any]:
+ for desc in self.rdf_root.getElementsByTagNameNS(RDF_NAMESPACE, "Description"):
+ if desc.getAttributeNS(RDF_NAMESPACE, "about") == about_uri:
+ for i in range(desc.attributes.length):
+ attr = desc.attributes.item(i)
+ if attr.namespaceURI == namespace:
+ yield attr
+ for child in desc.childNodes:
+ if child.namespaceURI == namespace:
+ yield child
+
+ def _get_text(self, element: XmlElement) -> str:
+ text = ""
+ for child in element.childNodes:
+ if child.nodeType == child.TEXT_NODE:
+ text += child.data
+ return text
+
+ dc_contributor = property(_getter_bag(DC_NAMESPACE, "contributor"))
+ """
+ Contributors to the resource (other than the authors).
+
+ An unsorted array of names.
+ """
+
+ dc_coverage = property(_getter_single(DC_NAMESPACE, "coverage"))
+ """Text describing the extent or scope of the resource."""
+
+ dc_creator = property(_getter_seq(DC_NAMESPACE, "creator"))
+ """A sorted array of names of the authors of the resource, listed in order
+ of precedence."""
+
+ dc_date = property(_getter_seq(DC_NAMESPACE, "date", _converter_date))
+ """
+ A sorted array of dates (datetime.datetime instances) of significance to
+ the resource.
+
+ The dates and times are in UTC.
+ """
+
+ dc_description = property(_getter_langalt(DC_NAMESPACE, "description"))
+ """A language-keyed dictionary of textual descriptions of the content of the
+ resource."""
+
+ dc_format = property(_getter_single(DC_NAMESPACE, "format"))
+ """The mime-type of the resource."""
+
+ dc_identifier = property(_getter_single(DC_NAMESPACE, "identifier"))
+ """Unique identifier of the resource."""
+
+ dc_language = property(_getter_bag(DC_NAMESPACE, "language"))
+ """An unordered array specifying the languages used in the resource."""
+
+ dc_publisher = property(_getter_bag(DC_NAMESPACE, "publisher"))
+ """An unordered array of publisher names."""
+
+ dc_relation = property(_getter_bag(DC_NAMESPACE, "relation"))
+ """An unordered array of text descriptions of relationships to other
+ documents."""
+
+ dc_rights = property(_getter_langalt(DC_NAMESPACE, "rights"))
+ """A language-keyed dictionary of textual descriptions of the rights the
+ user has to this resource."""
+
+ dc_source = property(_getter_single(DC_NAMESPACE, "source"))
+ """Unique identifier of the work from which this resource was derived."""
+
+ dc_subject = property(_getter_bag(DC_NAMESPACE, "subject"))
+ """An unordered array of descriptive phrases or keywrods that specify the
+ topic of the content of the resource."""
+
+ dc_title = property(_getter_langalt(DC_NAMESPACE, "title"))
+ """A language-keyed dictionary of the title of the resource."""
+
+ dc_type = property(_getter_bag(DC_NAMESPACE, "type"))
+ """An unordered array of textual descriptions of the document type."""
+
+ pdf_keywords = property(_getter_single(PDF_NAMESPACE, "Keywords"))
+ """An unformatted text string representing document keywords."""
+
+ pdf_pdfversion = property(_getter_single(PDF_NAMESPACE, "PDFVersion"))
+ """The PDF file version, for example 1.0 or 1.3."""
+
+ pdf_producer = property(_getter_single(PDF_NAMESPACE, "Producer"))
+ """The name of the tool that created the PDF document."""
+
+ xmp_create_date = property(
+ _getter_single(XMP_NAMESPACE, "CreateDate", _converter_date)
+ )
+ """
+ The date and time the resource was originally created.
+
+ The date and time are returned as a UTC datetime.datetime object.
+ """
+
+ xmp_modify_date = property(
+ _getter_single(XMP_NAMESPACE, "ModifyDate", _converter_date)
+ )
+ """
+ The date and time the resource was last modified.
+
+ The date and time are returned as a UTC datetime.datetime object.
+ """
+
+ xmp_metadata_date = property(
+ _getter_single(XMP_NAMESPACE, "MetadataDate", _converter_date)
+ )
+ """
+ The date and time that any metadata for this resource was last changed.
+
+ The date and time are returned as a UTC datetime.datetime object.
+ """
+
+ xmp_creator_tool = property(_getter_single(XMP_NAMESPACE, "CreatorTool"))
+ """The name of the first known tool used to create the resource."""
+
+ xmpmm_document_id = property(_getter_single(XMPMM_NAMESPACE, "DocumentID"))
+ """The common identifier for all versions and renditions of this resource."""
+
+ xmpmm_instance_id = property(_getter_single(XMPMM_NAMESPACE, "InstanceID"))
+ """An identifier for a specific incarnation of a document, updated each
+ time a file is saved."""
+
+ @property
+ def custom_properties(self) -> Dict[Any, Any]:
+ """
+ Retrieve custom metadata properties defined in the undocumented pdfx
+ metadata schema.
+
+ Returns:
+ A dictionary of key/value items for custom metadata properties.
+ """
+ if not hasattr(self, "_custom_properties"):
+ self._custom_properties = {}
+ for node in self.get_nodes_in_namespace("", PDFX_NAMESPACE):
+ key = node.localName
+ while True:
+ # see documentation about PDFX_NAMESPACE earlier in file
+ idx = key.find("\u2182")
+ if idx == -1:
+ break
+ key = (
+ key[:idx]
+ + chr(int(key[idx + 1 : idx + 5], base=16))
+ + key[idx + 5 :]
+ )
+ if node.nodeType == node.ATTRIBUTE_NODE:
+ value = node.nodeValue
+ else:
+ value = self._get_text(node)
+ self._custom_properties[key] = value
+ return self._custom_properties