diff options
author | S. Solomon Darnell | 2025-03-28 21:52:21 -0500 |
---|---|---|
committer | S. Solomon Darnell | 2025-03-28 21:52:21 -0500 |
commit | 4a52a71956a8d46fcb7294ac71734504bb09bcc2 (patch) | |
tree | ee3dc5af3b6313e921cd920906356f5d4febc4ed /.venv/lib/python3.12/site-packages/pypdf | |
parent | cc961e04ba734dd72309fb548a2f97d67d578813 (diff) | |
download | gn-ai-master.tar.gz |
Diffstat (limited to '.venv/lib/python3.12/site-packages/pypdf')
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 |