Admidio API
  • Package
  • Class
  • Tree
  • Deprecated
  • Todo

Packages

  • com
    • tecnick
      • tcpdf
  • None
  • PHPMailer
    • easypeasyics
  • PHPWavUtils
  • Securimage
    • classes

Classes

  • AutoLogin
  • Component
  • ComponentUpdate
  • ConditionParser
  • Database
  • Datamatrix
  • DateTimeExtended
  • EasyPeasyICS
  • Email
  • Folder
  • FormValidation
  • FunctionClass
  • Htaccess
  • HtmlDiv
  • HtmlElement
  • HtmlForm
  • HtmlFormBasic
  • HtmlFormInstallation
  • HtmlList
  • HtmlNavbar
  • HtmlPage
  • HtmlTable
  • HtmlTableBasic
  • Image
  • Inventory
  • InventoryFields
  • Language
  • LanguageData
  • ListConfiguration
  • Menu
  • Message
  • ModuleAnnouncements
  • ModuleDates
  • ModuleLists
  • ModuleMenu
  • ModuleMessages
  • Modules
  • ModuleWeblinks
  • MyFiles
  • Navigation
  • ntlm_sasl_client_class
  • Organization
  • Participants
  • PasswordHash
  • PasswordHashing
  • PDF417
  • PHPMailer
  • PHPMailerOAuth
  • PHPMailerOAuthGoogle
  • POP3
  • ProfileFields
  • QRcode
  • RoleDependency
  • RolesRights
  • RSSfeed
  • Securimage
  • Securimage_Color
  • Session
  • SMTP
  • SystemMail
  • TableAccess
  • TableAnnouncement
  • TableCategory
  • TableDate
  • TableFile
  • TableFolder
  • TableGuestbook
  • TableGuestbookComment
  • TableInventory
  • TableInventoryField
  • TableLists
  • TableMembers
  • TableMessage
  • TablePhotos
  • TableRoles
  • TableRooms
  • TableText
  • TableUserField
  • TableUsers
  • TableWeblink
  • TCPDF
  • TCPDF2DBarcode
  • TCPDF_COLORS
  • TCPDF_FILTERS
  • TCPDF_FONT_DATA
  • TCPDF_FONTS
  • TCPDF_IMAGES
  • TCPDF_IMPORT
  • TCPDF_PARSER
  • TCPDF_STATIC
  • TCPDFBarcode
  • UploadHandlerDownload
  • UploadHandlerPhoto
  • User
  • UserRegistration
  • WavFile

Exceptions

  • AdmException
  • phpmailerException
  • WavFileException
  • WavFormatException

Functions

  • __autoload
  • admFuncAutoload
  • admFuncGeneratePagination
  • admFuncGetBytesFromSize
  • admFuncGetDirectoryEntries
  • admFuncMaxUploadSize
  • admFuncProcessableImageSize
  • admFuncShowCreateChangeInfoById
  • admFuncShowCreateChangeInfoByName
  • admFuncVariableIsValid
  • admReadTemplateFile
  • admStrIsValidFileName
  • admStrStripTagsSpecial
  • admStrToLower
  • admStrToUpper
  • bzip2Version
  • checkDatabaseVersion
  • checkPhpVersion
  • EmailAttachment
  • FileSizeNiceDisplay
  • FormattedTimeRemaining
  • FunctionIsDisabled
  • getFormerRolesFromDatabase
  • getFutureRolesFromDatabase
  • getmicrotime
  • getRoleMemberships
  • getRolesFromDatabase
  • gzipVersion
  • hasRole
  • hl_attrval
  • hl_bal
  • hl_cmtcd
  • hl_ent
  • hl_prot
  • hl_regex
  • hl_spec
  • hl_tag
  • hl_tag2
  • hl_tidy
  • hl_version
  • htmLawed
  • HTMLFilter
  • isGroupLeader
  • isMember
  • kses
  • kses_hook
  • MySQLdumpVersion
  • OutputInformation
  • PHPMailerAutoload
  • SafeExec
  • showNotice
  • str_split
  • strAddSlashesDeep
  • strNextLetter
  • strStripSlashesDeep
  • strStripTags
  • strValidCharacters
  • tln_body2div
  • tln_casenormalize
  • tln_deent
  • tln_defang
  • tln_findnxreg
  • tln_findnxstr
  • tln_fixatts
  • tln_fixstyle
  • tln_fixurl
  • tln_getnxtag
  • tln_sanitize
  • tln_skipspace
  • tln_tagprint
  • tln_unspace
  • version_compare_replacement
  • version_compare_replacement_sub
    1     2     3     4     5     6     7     8     9    10    11    12    13    14    15    16    17    18    19    20    21    22    23    24    25    26    27    28    29    30    31    32    33    34    35    36    37    38    39    40    41    42    43    44    45    46    47    48    49    50    51    52    53    54    55    56    57    58    59    60    61    62    63    64    65    66    67    68    69    70    71    72    73    74    75    76    77    78    79    80    81    82    83    84    85    86    87    88    89    90    91    92    93    94    95    96    97    98    99   100   101   102   103   104   105   106   107   108   109   110   111   112   113   114   115   116   117   118   119   120   121   122   123   124   125   126   127   128   129   130   131   132   133   134   135   136   137   138   139   140   141   142   143   144   145   146   147   148   149   150   151   152   153   154   155   156   157   158   159   160   161   162   163   164   165   166   167   168   169   170   171   172   173   174   175   176   177   178   179   180   181   182   183   184   185   186   187   188   189   190   191   192   193   194   195   196   197   198   199   200   201   202   203   204   205   206   207   208   209   210   211   212   213   214   215   216   217   218   219   220   221   222   223   224   225   226   227   228   229   230   231   232   233   234   235   236   237   238   239   240   241   242   243   244   245   246   247   248   249   250   251   252   253   254   255   256   257   258   259   260   261   262   263   264   265   266   267   268   269   270   271   272   273   274   275   276   277   278   279   280   281   282   283   284   285   286   287   288   289   290   291   292   293   294   295   296   297   298   299   300   301   302   303   304   305   306   307   308   309   310   311   312   313   314   315   316   317   318   319   320   321   322   323   324   325   326   327   328   329   330   331   332   333   334   335   336   337   338   339   340   341   342   343   344   345   346   347   348   349   350   351   352   353   354   355   356   357   358   359   360   361   362   363   364   365   366   367   368   369   370   371   372   373   374   375   376   377   378   379   380   381   382   383   384   385   386   387   388   389   390   391   392   393   394   395   396   397   398   399   400   401   402   403   404   405   406   407   408   409   410   411   412   413   414   415   416   417   418   419   420   421   422   423   424   425   426   427   428   429   430   431   432   433   434   435   436   437   438   439   440   441   442   443   444   445   446   447   448   449   450   451   452   453   454   455   456   457   458   459   460   461   462   463   464   465   466   467   468   469   470   471   472   473   474   475   476   477   478   479   480   481   482   483   484   485   486   487   488   489   490   491   492   493   494   495   496   497   498   499   500   501   502   503   504   505   506   507   508   509   510   511   512   513   514   515   516   517   518   519   520   521   522   523   524   525   526   527   528   529   530   531   532   533   534   535   536   537   538   539   540   541   542   543   544   545   546   547   548   549   550   551   552   553   554   555   556   557   558   559   560   561   562   563   564   565   566   567   568   569   570   571   572   573   574   575   576   577   578   579   580   581   582   583   584   585   586   587   588   589   590   591   592   593   594   595   596   597   598   599   600   601   602   603   604   605   606   607   608   609   610   611   612   613   614   615   616   617   618   619   620   621   622   623   624   625   626   627   628   629   630   631   632   633   634   635   636   637   638   639   640   641   642   643   644   645   646   647   648   649   650   651   652   653   654   655   656   657   658   659   660   661   662   663   664   665   666   667   668   669   670   671   672   673   674   675   676   677   678   679   680   681   682   683   684   685   686   687   688   689   690   691   692   693   694   695   696   697   698   699   700   701   702   703   704   705   706   707   708   709   710   711   712   713   714   715   716   717   718   719   720   721   722   723   724   725   726   727   728   729   730   731   732   733   734   735   736   737   738   739   740   741   742   743   744   745   746   747   748   749   750   751   752   753   754   755   756   757   758   759   760   761   762   763   764   765   766   767   768   769   770   771   772   773   774   775   776   777   778   779   780   781   782   783   784   785   786   787   788   789   790   791   792   793   794   795   796   797   798   799   800   801   802   803   804   805   806   807   808   809   810   811   812   813   814   815   816   817   818   819   820   821   822   823   824   825   826   827   828   829   830   831   832   833   834   835   836   837   838   839   840   841   842   843   844   845   846   847   848   849   850   851   852   853   854   855   856   857   858   859   860   861   862   863   864   865   866   867   868   869   870   871   872   873   874   875   876   877   878   879   880   881   882   883   884   885   886   887   888   889   890   891   892   893   894   895   896   897   898   899   900   901   902   903   904   905   906   907   908   909   910   911   912   913   914   915   916   917   918   919   920   921   922   923   924   925   926   927   928   929   930   931   932   933   934   935   936   937   938   939   940   941   942   943   944   945   946   947   948   949   950   951   952   953   954   955   956   957   958   959   960   961   962   963   964   965   966   967   968   969   970   971   972   973   974   975   976   977   978   979   980   981   982   983   984   985   986   987   988   989   990   991   992   993   994   995   996   997   998   999  1000  1001  1002  1003  1004  1005  1006  1007  1008  1009  1010  1011  1012  1013  1014  1015  1016  1017  1018  1019  1020  1021  1022  1023  1024  1025  1026  1027  1028  1029  1030  1031  1032  1033  1034  1035  1036  1037  1038  1039  1040  1041  1042  1043  1044  1045  1046  1047  1048  1049  1050  1051  1052  1053  1054  1055  1056  1057  1058  1059  1060  1061  1062  1063  1064  1065  1066  1067  1068  1069  1070  1071  1072  1073  1074  1075  1076  1077  1078  1079  1080  1081  1082  1083  1084  1085  1086  1087  1088  1089  1090  1091  1092  1093  1094  1095  1096  1097  1098  1099  1100  1101  1102  1103  1104  1105  1106  1107  1108  1109  1110  1111  1112  1113  1114  1115  1116  1117  1118  1119  1120  1121  1122  1123  1124  1125  1126  1127  1128  1129  1130  1131  1132  1133  1134  1135  1136  1137  1138  1139  1140  1141  1142  1143  1144  1145  1146  1147  1148  1149  1150  1151  1152  1153  1154  1155  1156  1157  1158  1159  1160  1161  1162  1163  1164  1165  1166  1167  1168  1169  1170  1171  1172  1173  1174  1175  1176  1177  1178  1179  1180  1181  1182  1183  1184  1185  1186  1187  1188  1189  1190  1191  1192  1193  1194  1195  1196  1197  1198  1199  1200  1201  1202  1203  1204  1205  1206  1207  1208  1209  1210  1211  1212  1213  1214  1215  1216  1217  1218  1219  1220  1221  1222  1223  1224  1225  1226  1227  1228  1229  1230  1231  1232  1233  1234  1235  1236  1237  1238  1239  1240  1241  1242  1243  1244  1245  1246  1247  1248  1249  1250  1251  1252  1253  1254  1255  1256  1257  1258  1259  1260  1261  1262  1263  1264  1265  1266  1267  1268  1269  1270  1271  1272  1273  1274  1275  1276  1277  1278  1279  1280  1281  1282  1283  1284  1285  1286  1287  1288  1289  1290  1291  1292  1293  1294  1295  1296  1297  1298  1299  1300  1301  1302  1303  1304  1305  1306  1307  1308  1309  1310  1311  1312  1313  1314  1315  1316  1317  1318  1319  1320  1321  1322  1323  1324  1325  1326  1327  1328  1329  1330  1331  1332  1333  1334  1335  1336  1337  1338  1339  1340  1341  1342  1343  1344  1345  1346  1347  1348  1349  1350  1351  1352  1353  1354  1355  1356  1357  1358  1359  1360  1361  1362  1363  1364  1365  1366  1367  1368  1369  1370  1371  1372  1373  1374  1375  1376  1377  1378  1379  1380  1381  1382  1383  1384  1385  1386  1387  1388  1389  1390  1391  1392  1393  1394  1395  1396  1397  1398  1399  1400  1401  1402  1403  1404  1405  1406  1407  1408  1409  1410  1411  1412  1413  1414  1415  1416  1417  1418  1419  1420  1421  1422  1423  1424  1425  1426  1427  1428  1429  1430  1431  1432  1433  1434  1435  1436  1437  1438  1439  1440  1441  1442  1443  1444  1445  1446  1447  1448  1449  1450  1451  1452  1453  1454  1455  1456  1457  1458  1459  1460  1461  1462  1463  1464  1465  1466  1467  1468  1469  1470  1471  1472  1473  1474  1475  1476  1477  1478  1479  1480  1481  1482  1483  1484  1485  1486  1487  1488  1489  1490  1491  1492  1493  1494  1495  1496  1497  1498  1499  1500  1501  1502  1503  1504  1505  1506  1507  1508  1509  1510  1511  1512  1513  1514  1515  1516  1517  1518  1519  1520  1521  1522  1523  1524  1525  1526  1527  1528  1529  1530  1531  1532  1533  1534  1535  1536  1537  1538  1539  1540  1541  1542  1543  1544  1545  1546  1547  1548  1549  1550  1551  1552  1553  1554  1555  1556  1557  1558  1559  1560  1561  1562  1563  1564  1565  1566  1567  1568  1569  1570  1571  1572  1573  1574  1575  1576  1577  1578  1579  1580  1581  1582  1583  1584  1585  1586  1587  1588  1589  1590  1591  1592  1593  1594  1595  1596  1597  1598  1599  1600  1601  1602  1603  1604  1605  1606  1607  1608  1609  1610  1611  1612  1613  1614  1615  1616  1617  1618  1619  1620  1621  1622  1623  1624  1625  1626  1627  1628  1629  1630  1631  1632  1633  1634  1635  1636  1637  1638  1639  1640  1641  1642  1643  1644  1645  1646  1647  1648  1649  1650  1651  1652  1653  1654  1655  1656  1657  1658  1659  1660  1661  1662  1663  1664  1665  1666  1667  1668  1669  1670  1671  1672  1673  1674  1675  1676  1677  1678  1679  1680  1681  1682  1683  1684  1685  1686  1687  1688  1689  1690  1691  1692  1693  1694  1695  1696  1697  1698  1699  1700  1701  1702  1703  1704  1705  1706  1707  1708  1709  1710  1711  1712  1713  1714  1715  1716  1717  1718  1719  1720  1721  1722  1723  1724  1725  1726  1727  1728  1729  1730  1731  1732  1733  1734  1735  1736  1737  1738  1739  1740  1741  1742  1743  1744  1745  1746  1747  1748  1749  1750  1751  1752  1753  1754  1755  1756  1757  1758  1759  1760  1761  1762  1763  1764  1765  1766  1767  1768  1769  1770  1771  1772  1773  1774  1775  1776  1777  1778  1779  1780  1781  1782  1783  1784  1785  1786  1787  1788  1789  1790  1791  1792  1793  1794  1795  1796  1797  1798  1799  1800  1801  1802  1803  1804  1805  1806  1807  1808  1809  1810  1811  1812  1813  1814  1815  1816  1817  1818  1819  1820  1821  1822  1823  1824  1825  1826  1827  1828  1829  1830  1831  1832  1833  1834  1835  1836  1837  1838  1839  1840  1841  1842  1843  1844  1845  1846  1847  1848  1849  1850  1851  1852  1853  1854  1855  1856  1857  1858  1859  1860  1861  1862  1863  1864  1865  1866  1867  1868  1869  1870  1871  1872  1873  1874  1875  1876  1877  1878  1879  1880  1881  1882  1883  1884  1885  1886  1887  1888  1889  1890  1891  1892  1893  1894  1895  1896  1897  1898  1899  1900  1901  1902  1903  1904  1905  1906  1907  1908  1909  1910  1911  1912  1913  1914  1915  1916  1917  1918  1919  1920  1921  1922  1923  1924  1925  1926  1927  1928  1929  1930  1931  1932  1933  1934  1935  1936  1937  1938  1939  1940  1941  1942  1943  1944  1945  1946  1947  1948  1949  1950  1951  1952  1953  1954  1955  1956  1957  1958  1959  1960  1961  1962  1963  1964  1965  1966  1967  1968  1969  1970  1971  1972  1973  1974  1975  1976  1977  1978  1979  1980  1981  1982  1983  1984  1985  1986  1987  1988  1989  1990  1991  1992  1993  1994  1995  1996  1997  1998  1999  2000  2001  2002  2003  2004  2005  2006  2007  2008  2009  2010  2011  2012  2013  2014  2015  2016  2017  2018  2019  2020  2021  2022  2023  2024  2025  2026  2027  2028  2029  2030  2031  2032  2033  2034  2035  2036  2037  2038  2039  2040  2041  2042  2043  2044  2045  2046  2047  2048  2049  2050  2051  2052  2053  2054  2055  2056  2057  2058  2059  2060  2061  2062  2063  2064  2065  2066  2067  2068  2069  2070  2071  2072  2073  2074  2075  2076  2077  2078  2079  2080  2081  2082  2083  2084  2085  2086  2087  2088  2089  2090  2091  2092  2093  2094  2095  2096  2097  2098  2099  2100  2101  2102  2103  2104  2105  2106  2107  2108  2109  2110  2111  2112  2113  2114  2115  2116  2117  2118  2119  2120  2121  2122  2123  2124  2125  2126  2127  2128  2129  2130  2131  2132  2133  2134  2135  2136  2137  2138  2139  2140  2141  2142  2143  2144  2145  2146  2147  2148  2149  2150  2151  2152  2153  2154  2155  2156  2157  2158  2159  2160  2161  2162  2163  2164  2165  2166  2167  2168  2169  2170  2171  2172  2173  2174  2175  2176  2177  2178  2179  2180  2181  2182  2183  2184  2185  2186  2187  2188  2189  2190  2191  2192  2193  2194  2195  2196  2197  2198  2199  2200  2201  2202  2203  2204  2205  2206  2207  2208  2209  2210  2211  2212  2213  2214  2215  2216  2217  2218  2219  2220  2221  2222  2223  2224  2225  2226  2227  2228  2229  2230  2231  2232  2233  2234  2235  2236  2237  2238  2239  2240  2241  2242  2243  2244  2245  2246  2247  2248  2249  2250  2251  2252  2253  2254  2255  2256  2257  2258  2259  2260  2261  2262  2263  2264  2265  2266  2267  2268  2269  2270  2271  2272  2273  2274  2275  2276  2277  2278  2279  2280  2281  2282  2283  2284  2285  2286  2287  2288  2289  2290  2291  2292  2293  2294  2295  2296  2297  2298  2299  2300  2301  2302  2303  2304  2305  2306  2307  2308  2309  2310  2311  2312  2313  2314  2315  2316  2317  2318  2319  2320  2321  2322  2323  2324  2325  2326  2327  2328  2329  2330  2331  2332  2333  2334  2335  2336  2337  2338  2339  2340  2341  2342  2343  2344  2345  2346  2347  2348  2349  2350  2351  2352  2353  2354  2355  2356  2357  2358  2359  2360  2361  2362  2363  2364  2365  2366  2367  2368  2369  2370  2371  2372  2373  2374  2375  2376  2377  2378  2379  2380  2381  2382  2383  2384  2385  2386  2387  2388  2389  2390  2391  2392  2393  2394  2395  2396  2397  2398  2399  2400  2401  2402  2403  2404  2405  2406  2407  2408  2409  2410  2411  2412  2413  2414  2415  2416  2417  2418  2419  2420  2421  2422  2423  2424  2425  2426  2427  2428  2429  2430  2431  2432  2433  2434  2435  2436  2437  2438  2439  2440  2441  2442  2443  2444  2445  2446  2447  2448  2449  2450  2451  2452  2453  2454  2455  2456  2457  2458  2459  2460  2461  2462  2463  2464  2465  2466  2467  2468  2469  2470  2471  2472  2473  2474  2475  2476  2477  2478  2479  2480  2481  2482  2483  2484  2485  2486  2487  2488  2489  2490  2491  2492  2493  2494  2495  2496  2497  2498  2499  2500  2501  2502  2503  2504  2505  2506  2507  2508  2509  2510  2511  2512  2513  2514  2515  2516  2517  2518  2519  2520  2521  2522  2523  2524  2525  2526  2527  2528  2529  2530  2531  2532  2533  2534  2535  2536  2537  2538  2539  2540  2541  2542  2543  2544  2545  2546  2547  2548  2549  2550  2551  2552  2553  2554  2555  2556  2557  2558  2559  2560  2561  2562  2563  2564  2565  2566  2567  2568  2569  2570  2571  2572  2573  2574  2575  2576  2577  2578  2579  2580  2581  2582  2583  2584  2585  2586  2587  2588  2589  2590  2591  2592  2593  2594  2595  2596  2597  2598  2599  2600  2601  2602  2603  2604  2605  2606  2607  2608  2609  2610  2611  2612  2613  2614  2615  2616  2617  2618  2619  2620  2621  2622  2623  2624  2625  2626  2627  2628  2629  2630  2631  2632  2633  2634  2635  2636  2637  2638  2639  2640  2641  2642  2643  2644  2645  2646  2647  2648  2649  2650  2651  2652  2653  2654  2655  2656  2657  2658  2659  2660  2661  2662  2663  2664  2665  2666  2667  2668  2669  2670  2671  2672  2673  2674  2675  2676  2677  2678  2679  2680  2681  2682  2683  2684  2685  2686  2687  2688  2689  2690  2691  2692  2693  2694  2695  2696  2697  2698  2699  2700  2701  2702  2703  2704  2705  2706  2707  2708  2709  2710  2711  2712  2713  2714  2715  2716  2717  2718  2719  2720  2721  2722  2723  2724  2725  2726  2727  2728  2729  2730  2731  2732  2733  2734  2735  2736  2737  2738  2739  2740  2741  2742  2743  2744  2745  2746  2747  2748  2749  2750  2751  2752  2753  2754  2755  2756  2757  2758  2759  2760  2761  2762  2763  2764  2765  2766  2767  2768  2769  2770  2771  2772  2773  2774  2775  2776  2777  2778  2779  2780  2781  2782  2783  2784  2785  2786  2787  2788  2789  2790  2791  2792  2793  2794  2795  2796  2797  2798  2799  2800  2801  2802  2803  2804  2805  2806  2807  2808  2809  2810  2811  2812  2813  2814  2815  2816  2817  2818  2819  2820  2821  2822  2823  2824  2825  2826  2827  2828  2829  2830  2831  2832  2833  2834  2835  2836  2837  2838  2839  2840  2841  2842  2843  2844  2845  2846  2847  2848  2849  2850  2851  2852  2853  2854  2855  2856  2857  2858  2859  2860  2861  2862  2863  2864  2865  2866  2867  2868  2869  2870  2871  2872  2873  2874  2875  2876  2877  2878  2879  2880  2881  2882  2883  2884  2885  2886  2887  2888  2889  2890  2891  2892  2893  2894  2895  2896  2897  2898  2899  2900  2901  2902  2903  2904  2905  2906  2907  2908  2909  2910  2911  2912  2913  2914  2915  2916  2917  2918  2919  2920  2921  2922  2923  2924  2925  2926  2927  2928  2929  2930  2931  2932  2933  2934  2935  2936  2937  2938  2939  2940  2941  2942  2943  2944  2945  2946  2947  2948  2949  2950  2951  2952  2953  2954  2955  2956  2957  2958  2959  2960  2961  2962  2963  2964  2965  2966  2967  2968  2969  2970  2971  2972  2973  2974  2975  2976  2977  2978  2979  2980  2981  2982  2983  2984  2985  2986  2987  2988  2989  2990  2991  2992  2993  2994  2995  2996  2997  2998  2999  3000  3001  3002  3003  3004  3005  3006  3007  3008  3009  3010  3011  3012  3013  3014  3015  3016  3017  3018  3019  3020  3021  3022  3023  3024  3025  3026  3027  3028  3029  3030  3031  3032  3033  3034  3035  3036  3037  3038  3039  3040  3041  3042  3043  3044  3045  3046  3047  3048  3049  3050  3051  3052  3053  3054  3055  3056  3057  3058  3059  3060  3061  3062  3063  3064  3065  3066  3067  3068  3069  3070  3071  3072  3073  3074  3075  3076  3077  3078  3079  3080  3081  3082  3083  3084  3085  3086  3087  3088  3089  3090  3091  3092  3093  3094  3095  3096  3097  3098  3099  3100  3101  3102  3103  3104  3105  3106  3107  3108  3109  3110  3111  3112  3113  3114  3115  3116  3117  3118  3119  3120  3121  3122  3123  3124  3125  3126  3127  3128  3129  3130  3131  3132  3133  3134  3135  3136  3137  3138  3139  3140  3141  3142  3143  3144  3145  3146  3147  3148  3149  3150  3151  3152  3153  3154  3155  3156  3157  3158  3159  3160  3161  3162  3163  3164  3165  3166  3167  3168  3169  3170  3171  3172  3173  3174  3175  3176  3177  3178  3179  3180  3181  3182  3183  3184  3185  3186  3187  3188  3189  3190  3191  3192  3193  3194  3195  3196  3197  3198  3199  3200  3201  3202  3203  3204  3205  3206  3207  3208  3209  3210  3211  3212  3213  3214  3215  3216  3217  3218  3219  3220  3221  3222  3223  3224  3225  3226  3227  3228  3229  3230  3231  3232  3233  3234  3235  3236  3237  3238  3239  3240  3241  3242  3243  3244  3245  3246  3247  3248  3249  3250  3251  3252  3253  3254  3255  3256  3257  3258  3259  3260  3261  3262  3263  3264  3265  3266  3267  3268  3269  3270  3271  3272  3273  3274  3275  3276  3277  3278  3279  3280  3281  3282  3283  3284  3285  3286  3287  3288  3289  3290  3291  3292  3293  3294  3295  3296  3297  3298  3299  3300  3301  3302  3303  3304  3305  3306  3307  3308  3309  3310  3311  3312  3313  3314  3315  3316  3317  3318  3319  3320  3321  3322  3323  3324  3325  3326  3327  3328  3329  3330  3331  3332  3333  3334  3335  3336  3337  3338  3339  3340  3341  3342  3343  3344  3345  3346  3347  3348  3349  3350  3351  3352  3353  3354  3355  3356  3357  3358  3359  3360  3361  3362  3363  3364  3365  3366  3367  3368  3369  3370  3371  3372  3373  3374  3375  3376  3377  3378  3379  3380  3381  3382  3383  3384  3385  3386  3387  3388  3389  3390  3391  3392  3393  3394  3395  3396  3397  3398  3399  3400  3401  3402  3403  3404  3405  3406  3407  3408  3409  3410  3411  3412  3413  3414  3415  3416  3417  3418  3419  3420  3421  3422  3423  3424  3425  3426  3427  3428  3429  3430  3431  3432  3433  3434  3435  3436  3437  3438  3439  3440  3441  3442  3443  3444  3445  3446  3447  3448  3449  3450  3451  3452  3453  3454  3455  3456  3457  3458  3459  3460  3461  3462  3463  3464  3465  3466  3467  3468  3469  3470  3471  3472  3473  3474  3475  3476  3477  3478  3479  3480  3481  3482  3483  3484  3485  3486  3487  3488  3489  3490  3491  3492  3493  3494  3495  3496  3497  3498  3499  3500  3501  3502  3503  3504  3505  3506  3507  3508  3509  3510  3511  3512  3513  3514  3515  3516  3517  3518  3519  3520  3521  3522  3523  3524  3525  3526  3527  3528  3529  3530  3531  3532  3533  3534  3535  3536  3537  3538  3539  3540  3541  3542  3543  3544  3545  3546  3547  3548  3549  3550  3551  3552  3553  3554  3555  3556  3557  3558  3559  3560  3561  3562  3563  3564  3565  3566  3567  3568  3569  3570  3571  3572  3573  3574  3575  3576  3577  3578  3579  3580  3581  3582  3583  3584  3585  3586  3587  3588  3589  3590  3591  3592  3593  3594  3595  3596  3597  3598  3599  3600  3601  3602  3603  3604  3605  3606  3607  3608  3609  3610  3611  3612  3613  3614  3615  3616  3617  3618  3619  3620  3621  3622  3623  3624  3625  3626  3627  3628  3629  3630  3631  3632  3633  3634  3635  3636  3637  3638  3639  3640  3641  3642  3643  3644  3645  3646  3647  3648  3649  3650  3651  3652  3653  3654  3655  3656  3657  3658  3659  3660  3661  3662  3663  3664  3665  3666  3667  3668  3669  3670  3671  3672  3673  3674  3675  3676  3677  3678  3679  3680  3681  3682  3683  3684  3685  3686  3687  3688  3689  3690  3691  3692  3693  3694  3695  3696  3697  3698  3699  3700  3701  3702  3703  3704  3705  3706  3707  3708  3709  3710  3711  3712  3713  3714  3715  3716  3717  3718  3719  3720  3721  3722  3723  3724  3725  3726  3727  3728  3729  3730  3731  3732  3733  3734  3735  3736  3737  3738  3739  3740  3741  3742  3743  3744  3745  3746  3747  3748  3749  3750  3751  3752  3753  3754  3755  3756  3757  3758  3759  3760  3761  3762  3763  3764  3765  3766  3767  3768  3769  3770  3771  3772  3773  3774  3775  3776  3777  3778  3779  3780  3781  3782  3783  3784  3785  3786  3787  3788  3789  3790  3791  3792  3793  3794  3795  3796  3797  3798  3799  3800  3801  3802  3803  3804  3805  3806  3807  3808  3809  3810  3811  3812  3813  3814  3815  3816  3817  3818  3819  3820  3821  3822  3823  3824  3825  3826  3827  3828  3829  3830  3831  3832  3833  3834  3835  3836  3837  3838  3839  3840  3841  3842  3843  3844  3845  3846  3847  3848  3849  3850  3851  3852  3853  3854  3855  3856  3857  3858  3859  3860  3861  3862  3863  3864  3865  3866  3867  3868  3869  3870  3871  3872  3873  3874  3875  3876  3877  3878  3879  3880  3881  3882  3883  3884  3885  3886  3887  3888  3889  3890  3891  3892  3893  3894  3895  3896  3897  3898  3899  3900  3901  3902  3903  3904  3905  3906  3907  3908  3909  3910  3911  3912  3913  3914  3915  3916  3917  3918  3919  3920  3921  3922  3923  3924  3925  3926  3927  3928  3929  3930  3931  3932  3933  3934  3935  3936  3937  3938  3939  3940  3941  3942  3943  3944  3945  3946  3947  3948  3949  3950  3951  3952  3953  3954  3955  3956  3957  3958  3959  3960  3961  3962  3963  3964  3965  3966  3967  3968  3969  3970  3971  3972  3973  3974  3975  3976  3977  3978  3979  3980  3981  3982  3983  3984  3985  3986  3987  3988  3989  3990  3991  3992  3993  3994  3995  3996  3997  3998  3999  4000  4001  4002  4003  4004  4005  4006  4007  4008  4009  4010  4011  4012  4013  4014  4015  4016  4017  4018  4019  4020  4021  4022  4023  4024  4025  4026  4027  4028  4029  4030  4031  4032  4033  4034  4035  4036  4037  4038  4039  4040  4041  4042  4043  4044  4045  4046  4047  4048  4049  4050  4051  4052  4053  4054  4055  4056  4057  4058  4059  4060  4061  4062  4063  4064  4065  4066  4067  4068  4069  4070  4071  4072  4073  4074  4075  4076  4077  4078  4079  4080  4081  4082  4083  4084  4085  4086  4087  4088  4089  4090  4091  4092  4093  4094  4095  4096  4097  4098  4099  4100  4101  4102  4103  4104  4105  4106  4107  4108  4109  4110  4111  4112  4113  4114  4115  4116  4117  4118  4119  4120  4121  4122  4123  4124  4125  4126  4127  4128  4129  4130  4131  4132  4133  4134  4135  4136  4137  4138  4139  4140  4141  4142  4143  4144  4145  4146  4147  4148  4149  4150  4151  4152  4153  4154  4155  4156  4157  4158  4159  4160  4161  4162  4163  4164  4165  4166  4167  4168  4169  4170  4171  4172  4173  4174  4175  4176  4177  4178  4179  4180  4181  4182  4183  4184  4185  4186  4187  4188  4189  4190  4191  4192  4193  4194  4195  4196  4197  4198  4199  4200  4201  4202  4203  4204  4205  4206  4207  4208  4209  4210  4211  4212  4213  4214  4215  4216  4217  4218  4219  4220  4221  4222  4223  4224  4225  4226  4227  4228  4229  4230  4231  4232  4233  4234  4235  4236  4237  4238  4239  4240  4241  4242  4243  4244  4245  4246  4247  4248  4249  4250  4251  4252  4253  4254  4255  4256  4257  4258  4259  4260  4261  4262  4263  4264  4265  4266  4267  4268  4269  4270  4271  4272  4273  4274  4275  4276  4277  4278  4279  4280  4281  4282  4283  4284  4285  4286  4287  4288  4289  4290  4291  4292  4293  4294  4295  4296  4297  4298  4299  4300  4301  4302  4303  4304  4305  4306  4307  4308  4309  4310  4311  4312  4313  4314  4315  4316  4317  4318  4319  4320  4321  4322  4323  4324  4325  4326  4327  4328  4329  4330  4331  4332  4333  4334  4335  4336  4337  4338  4339  4340  4341  4342  4343  4344  4345  4346  4347  4348  4349  4350  4351  4352  4353  4354  4355  4356  4357  4358  4359  4360  4361  4362  4363  4364  4365  4366  4367  4368  4369  4370  4371  4372  4373  4374  4375  4376  4377  4378  4379  4380  4381  4382  4383  4384  4385  4386  4387  4388  4389  4390  4391  4392  4393  4394  4395  4396  4397  4398  4399  4400  4401  4402  4403  4404  4405  4406  4407  4408  4409  4410  4411  4412  4413  4414  4415  4416  4417  4418  4419  4420  4421  4422  4423  4424  4425  4426  4427  4428  4429  4430  4431  4432  4433  4434  4435  4436  4437  4438  4439  4440  4441  4442  4443  4444  4445  4446  4447  4448  4449  4450  4451  4452  4453  4454  4455  4456  4457  4458  4459  4460  4461  4462  4463  4464  4465  4466  4467  4468  4469  4470  4471  4472  4473  4474  4475  4476  4477  4478  4479  4480  4481  4482  4483  4484  4485  4486  4487  4488  4489  4490  4491  4492  4493  4494  4495  4496  4497  4498  4499  4500  4501  4502  4503  4504  4505  4506  4507  4508  4509  4510  4511  4512  4513  4514  4515  4516  4517  4518  4519  4520  4521  4522  4523  4524  4525  4526  4527  4528  4529  4530  4531  4532  4533  4534  4535  4536  4537  4538  4539  4540  4541  4542  4543  4544  4545  4546  4547  4548  4549  4550  4551  4552  4553  4554  4555  4556  4557  4558  4559  4560  4561  4562  4563  4564  4565  4566  4567  4568  4569  4570  4571  4572  4573  4574  4575  4576  4577  4578  4579  4580  4581  4582  4583  4584  4585  4586  4587  4588  4589  4590  4591  4592  4593  4594  4595  4596  4597  4598  4599  4600  4601  4602  4603  4604  4605  4606  4607  4608  4609  4610  4611  4612  4613  4614  4615  4616  4617  4618  4619  4620  4621  4622  4623  4624  4625  4626  4627  4628  4629  4630  4631  4632  4633  4634  4635  4636  4637  4638  4639  4640  4641  4642  4643  4644  4645  4646  4647  4648  4649  4650  4651  4652  4653  4654  4655  4656  4657  4658  4659  4660  4661  4662  4663  4664  4665  4666  4667  4668  4669  4670  4671  4672  4673  4674  4675  4676  4677  4678  4679  4680  4681  4682  4683  4684  4685  4686  4687  4688  4689  4690  4691  4692  4693  4694  4695  4696  4697  4698  4699  4700  4701  4702  4703  4704  4705  4706  4707  4708  4709  4710  4711  4712  4713  4714  4715  4716  4717  4718  4719  4720  4721  4722  4723  4724  4725  4726  4727  4728  4729  4730  4731  4732  4733  4734  4735  4736  4737  4738  4739  4740  4741  4742  4743  4744  4745  4746  4747  4748  4749  4750  4751  4752  4753  4754  4755  4756  4757  4758  4759  4760  4761  4762  4763  4764  4765  4766  4767  4768  4769  4770  4771  4772  4773  4774  4775  4776  4777  4778  4779  4780  4781  4782  4783  4784  4785  4786  4787  4788  4789  4790  4791  4792  4793  4794  4795  4796  4797  4798  4799  4800  4801  4802  4803  4804  4805  4806  4807  4808  4809  4810  4811  4812  4813  4814  4815  4816  4817  4818  4819  4820  4821  4822  4823  4824  4825  4826  4827  4828  4829  4830  4831  4832  4833  4834  4835  4836  4837  4838  4839  4840  4841  4842  4843  4844  4845  4846  4847  4848  4849  4850  4851  4852  4853  4854  4855  4856  4857  4858  4859  4860  4861  4862  4863  4864  4865  4866  4867  4868  4869  4870  4871  4872  4873  4874  4875  4876  4877  4878  4879  4880  4881  4882  4883  4884  4885  4886  4887  4888  4889  4890  4891  4892  4893  4894  4895  4896  4897  4898  4899  4900  4901  4902  4903  4904  4905  4906  4907  4908  4909  4910  4911  4912  4913  4914  4915  4916  4917  4918  4919  4920  4921  4922  4923  4924  4925  4926  4927  4928  4929  4930  4931  4932  4933  4934  4935  4936  4937  4938  4939  4940  4941  4942  4943  4944  4945  4946  4947  4948  4949  4950  4951  4952  4953  4954  4955  4956  4957  4958  4959  4960  4961  4962  4963  4964  4965  4966  4967  4968  4969  4970  4971  4972  4973  4974  4975  4976  4977  4978  4979  4980  4981  4982  4983  4984  4985  4986  4987  4988  4989  4990  4991  4992  4993  4994  4995  4996  4997  4998  4999  5000  5001  5002  5003  5004  5005  5006  5007  5008  5009  5010  5011  5012  5013  5014  5015  5016  5017  5018  5019  5020  5021  5022  5023  5024  5025  5026  5027  5028  5029  5030  5031  5032  5033  5034  5035  5036  5037  5038  5039  5040  5041  5042  5043  5044  5045  5046  5047  5048  5049  5050  5051  5052  5053  5054  5055  5056  5057  5058  5059  5060  5061  5062  5063  5064  5065  5066  5067  5068  5069  5070  5071  5072  5073  5074  5075  5076  5077  5078  5079  5080  5081  5082  5083  5084  5085  5086  5087  5088  5089  5090  5091  5092  5093  5094  5095  5096  5097  5098  5099  5100  5101  5102  5103  5104  5105  5106  5107  5108  5109  5110  5111  5112  5113  5114  5115  5116  5117  5118  5119  5120  5121  5122  5123  5124  5125  5126  5127  5128  5129  5130  5131  5132  5133  5134  5135  5136  5137  5138  5139  5140  5141  5142  5143  5144  5145  5146  5147  5148  5149  5150  5151  5152  5153  5154  5155  5156  5157  5158  5159  5160  5161  5162  5163  5164  5165  5166  5167  5168  5169  5170  5171  5172  5173  5174  5175  5176  5177  5178  5179  5180  5181  5182  5183  5184  5185  5186  5187  5188  5189  5190  5191  5192  5193  5194  5195  5196  5197  5198  5199  5200  5201  5202  5203  5204  5205  5206  5207  5208  5209  5210  5211  5212  5213  5214  5215  5216  5217  5218  5219  5220  5221  5222  5223  5224  5225  5226  5227  5228  5229  5230  5231  5232  5233  5234  5235  5236  5237  5238  5239  5240  5241  5242  5243  5244  5245  5246  5247  5248  5249  5250  5251  5252  5253  5254  5255  5256  5257  5258  5259  5260  5261  5262  5263  5264  5265  5266  5267  5268  5269  5270  5271  5272  5273  5274  5275  5276  5277  5278  5279  5280  5281  5282  5283  5284  5285  5286  5287  5288  5289  5290  5291  5292  5293  5294  5295  5296  5297  5298  5299  5300  5301  5302  5303  5304  5305  5306  5307  5308  5309  5310  5311  5312  5313  5314  5315  5316  5317  5318  5319  5320  5321  5322  5323  5324  5325  5326  5327  5328  5329  5330  5331  5332  5333  5334  5335  5336  5337  5338  5339  5340  5341  5342  5343  5344  5345  5346  5347  5348  5349  5350  5351  5352  5353  5354  5355  5356  5357  5358  5359  5360  5361  5362  5363  5364  5365  5366  5367  5368  5369  5370  5371  5372  5373  5374  5375  5376  5377  5378  5379  5380  5381  5382  5383  5384  5385  5386  5387  5388  5389  5390  5391  5392  5393  5394  5395  5396  5397  5398  5399  5400  5401  5402  5403  5404  5405  5406  5407  5408  5409  5410  5411  5412  5413  5414  5415  5416  5417  5418  5419  5420  5421  5422  5423  5424  5425  5426  5427  5428  5429  5430  5431  5432  5433  5434  5435  5436  5437  5438  5439  5440  5441  5442  5443  5444  5445  5446  5447  5448  5449  5450  5451  5452  5453  5454  5455  5456  5457  5458  5459  5460  5461  5462  5463  5464  5465  5466  5467  5468  5469  5470  5471  5472  5473  5474  5475  5476  5477  5478  5479  5480  5481  5482  5483  5484  5485  5486  5487  5488  5489  5490  5491  5492  5493  5494  5495  5496  5497  5498  5499  5500  5501  5502  5503  5504  5505  5506  5507  5508  5509  5510  5511  5512  5513  5514  5515  5516  5517  5518  5519  5520  5521  5522  5523  5524  5525  5526  5527  5528  5529  5530  5531  5532  5533  5534  5535  5536  5537  5538  5539  5540  5541  5542  5543  5544  5545  5546  5547  5548  5549  5550  5551  5552  5553  5554  5555  5556  5557  5558  5559  5560  5561  5562  5563  5564  5565  5566  5567  5568  5569  5570  5571  5572  5573  5574  5575  5576  5577  5578  5579  5580  5581  5582  5583  5584  5585  5586  5587  5588  5589  5590  5591  5592  5593  5594  5595  5596  5597  5598  5599  5600  5601  5602  5603  5604  5605  5606  5607  5608  5609  5610  5611  5612  5613  5614  5615  5616  5617  5618  5619  5620  5621  5622  5623  5624  5625  5626  5627  5628  5629  5630  5631  5632  5633  5634  5635  5636  5637  5638  5639  5640  5641  5642  5643  5644  5645  5646  5647  5648  5649  5650  5651  5652  5653  5654  5655  5656  5657  5658  5659  5660  5661  5662  5663  5664  5665  5666  5667  5668  5669  5670  5671  5672  5673  5674  5675  5676  5677  5678  5679  5680  5681  5682  5683  5684  5685  5686  5687  5688  5689  5690  5691  5692  5693  5694  5695  5696  5697  5698  5699  5700  5701  5702  5703  5704  5705  5706  5707  5708  5709  5710  5711  5712  5713  5714  5715  5716  5717  5718  5719  5720  5721  5722  5723  5724  5725  5726  5727  5728  5729  5730  5731  5732  5733  5734  5735  5736  5737  5738  5739  5740  5741  5742  5743  5744  5745  5746  5747  5748  5749  5750  5751  5752  5753  5754  5755  5756  5757  5758  5759  5760  5761  5762  5763  5764  5765  5766  5767  5768  5769  5770  5771  5772  5773  5774  5775  5776  5777  5778  5779  5780  5781  5782  5783  5784  5785  5786  5787  5788  5789  5790  5791  5792  5793  5794  5795  5796  5797  5798  5799  5800  5801  5802  5803  5804  5805  5806  5807  5808  5809  5810  5811  5812  5813  5814  5815  5816  5817  5818  5819  5820  5821  5822  5823  5824  5825  5826  5827  5828  5829  5830  5831  5832  5833  5834  5835  5836  5837  5838  5839  5840  5841  5842  5843  5844  5845  5846  5847  5848  5849  5850  5851  5852  5853  5854  5855  5856  5857  5858  5859  5860  5861  5862  5863  5864  5865  5866  5867  5868  5869  5870  5871  5872  5873  5874  5875  5876  5877  5878  5879  5880  5881  5882  5883  5884  5885  5886  5887  5888  5889  5890  5891  5892  5893  5894  5895  5896  5897  5898  5899  5900  5901  5902  5903  5904  5905  5906  5907  5908  5909  5910  5911  5912  5913  5914  5915  5916  5917  5918  5919  5920  5921  5922  5923  5924  5925  5926  5927  5928  5929  5930  5931  5932  5933  5934  5935  5936  5937  5938  5939  5940  5941  5942  5943  5944  5945  5946  5947  5948  5949  5950  5951  5952  5953  5954  5955  5956  5957  5958  5959  5960  5961  5962  5963  5964  5965  5966  5967  5968  5969  5970  5971  5972  5973  5974  5975  5976  5977  5978  5979  5980  5981  5982  5983  5984  5985  5986  5987  5988  5989  5990  5991  5992  5993  5994  5995  5996  5997  5998  5999  6000  6001  6002  6003  6004  6005  6006  6007  6008  6009  6010  6011  6012  6013  6014  6015  6016  6017  6018  6019  6020  6021  6022  6023  6024  6025  6026  6027  6028  6029  6030  6031  6032  6033  6034  6035  6036  6037  6038  6039  6040  6041  6042  6043  6044  6045  6046  6047  6048  6049  6050  6051  6052  6053  6054  6055  6056  6057  6058  6059  6060  6061  6062  6063  6064  6065  6066  6067  6068  6069  6070  6071  6072  6073  6074  6075  6076  6077  6078  6079  6080  6081  6082  6083  6084  6085  6086  6087  6088  6089  6090  6091  6092  6093  6094  6095  6096  6097  6098  6099  6100  6101  6102  6103  6104  6105  6106  6107  6108  6109  6110  6111  6112  6113  6114  6115  6116  6117  6118  6119  6120  6121  6122  6123  6124  6125  6126  6127  6128  6129  6130  6131  6132  6133  6134  6135  6136  6137  6138  6139  6140  6141  6142  6143  6144  6145  6146  6147  6148  6149  6150  6151  6152  6153  6154  6155  6156  6157  6158  6159  6160  6161  6162  6163  6164  6165  6166  6167  6168  6169  6170  6171  6172  6173  6174  6175  6176  6177  6178  6179  6180  6181  6182  6183  6184  6185  6186  6187  6188  6189  6190  6191  6192  6193  6194  6195  6196  6197  6198  6199  6200  6201  6202  6203  6204  6205  6206  6207  6208  6209  6210  6211  6212  6213  6214  6215  6216  6217  6218  6219  6220  6221  6222  6223  6224  6225  6226  6227  6228  6229  6230  6231  6232  6233  6234  6235  6236  6237  6238  6239  6240  6241  6242  6243  6244  6245  6246  6247  6248  6249  6250  6251  6252  6253  6254  6255  6256  6257  6258  6259  6260  6261  6262  6263  6264  6265  6266  6267  6268  6269  6270  6271  6272  6273  6274  6275  6276  6277  6278  6279  6280  6281  6282  6283  6284  6285  6286  6287  6288  6289  6290  6291  6292  6293  6294  6295  6296  6297  6298  6299  6300  6301  6302  6303  6304  6305  6306  6307  6308  6309  6310  6311  6312  6313  6314  6315  6316  6317  6318  6319  6320  6321  6322  6323  6324  6325  6326  6327  6328  6329  6330  6331  6332  6333  6334  6335  6336  6337  6338  6339  6340  6341  6342  6343  6344  6345  6346  6347  6348  6349  6350  6351  6352  6353  6354  6355  6356  6357  6358  6359  6360  6361  6362  6363  6364  6365  6366  6367  6368  6369  6370  6371  6372  6373  6374  6375  6376  6377  6378  6379  6380  6381  6382  6383  6384  6385  6386  6387  6388  6389  6390  6391  6392  6393  6394  6395  6396  6397  6398  6399  6400  6401  6402  6403  6404  6405  6406  6407  6408  6409  6410  6411  6412  6413  6414  6415  6416  6417  6418  6419  6420  6421  6422  6423  6424  6425  6426  6427  6428  6429  6430  6431  6432  6433  6434  6435  6436  6437  6438  6439  6440  6441  6442  6443  6444  6445  6446  6447  6448  6449  6450  6451  6452  6453  6454  6455  6456  6457  6458  6459  6460  6461  6462  6463  6464  6465  6466  6467  6468  6469  6470  6471  6472  6473  6474  6475  6476  6477  6478  6479  6480  6481  6482  6483  6484  6485  6486  6487  6488  6489  6490  6491  6492  6493  6494  6495  6496  6497  6498  6499  6500  6501  6502  6503  6504  6505  6506  6507  6508  6509  6510  6511  6512  6513  6514  6515  6516  6517  6518  6519  6520  6521  6522  6523  6524  6525  6526  6527  6528  6529  6530  6531  6532  6533  6534  6535  6536  6537  6538  6539  6540  6541  6542  6543  6544  6545  6546  6547  6548  6549  6550  6551  6552  6553  6554  6555  6556  6557  6558  6559  6560  6561  6562  6563  6564  6565  6566  6567  6568  6569  6570  6571  6572  6573  6574  6575  6576  6577  6578  6579  6580  6581  6582  6583  6584  6585  6586  6587  6588  6589  6590  6591  6592  6593  6594  6595  6596  6597  6598  6599  6600  6601  6602  6603  6604  6605  6606  6607  6608  6609  6610  6611  6612  6613  6614  6615  6616  6617  6618  6619  6620  6621  6622  6623  6624  6625  6626  6627  6628  6629  6630  6631  6632  6633  6634  6635  6636  6637  6638  6639  6640  6641  6642  6643  6644  6645  6646  6647  6648  6649  6650  6651  6652  6653  6654  6655  6656  6657  6658  6659  6660  6661  6662  6663  6664  6665  6666  6667  6668  6669  6670  6671  6672  6673  6674  6675  6676  6677  6678  6679  6680  6681  6682  6683  6684  6685  6686  6687  6688  6689  6690  6691  6692  6693  6694  6695  6696  6697  6698  6699  6700  6701  6702  6703  6704  6705  6706  6707  6708  6709  6710  6711  6712  6713  6714  6715  6716  6717  6718  6719  6720  6721  6722  6723  6724  6725  6726  6727  6728  6729  6730  6731  6732  6733  6734  6735  6736  6737  6738  6739  6740  6741  6742  6743  6744  6745  6746  6747  6748  6749  6750  6751  6752  6753  6754  6755  6756  6757  6758  6759  6760  6761  6762  6763  6764  6765  6766  6767  6768  6769  6770  6771  6772  6773  6774  6775  6776  6777  6778  6779  6780  6781  6782  6783  6784  6785  6786  6787  6788  6789  6790  6791  6792  6793  6794  6795  6796  6797  6798  6799  6800  6801  6802  6803  6804  6805  6806  6807  6808  6809  6810  6811  6812  6813  6814  6815  6816  6817  6818  6819  6820  6821  6822  6823  6824  6825  6826  6827  6828  6829  6830  6831  6832  6833  6834  6835  6836  6837  6838  6839  6840  6841  6842  6843  6844  6845  6846  6847  6848  6849  6850  6851  6852  6853  6854  6855  6856  6857  6858  6859  6860  6861  6862  6863  6864  6865  6866  6867  6868  6869  6870  6871  6872  6873  6874  6875  6876  6877  6878  6879  6880  6881  6882  6883  6884  6885  6886  6887  6888  6889  6890  6891  6892  6893  6894  6895  6896  6897  6898  6899  6900  6901  6902  6903  6904  6905  6906  6907  6908  6909  6910  6911  6912  6913  6914  6915  6916  6917  6918  6919  6920  6921  6922  6923  6924  6925  6926  6927  6928  6929  6930  6931  6932  6933  6934  6935  6936  6937  6938  6939  6940  6941  6942  6943  6944  6945  6946  6947  6948  6949  6950  6951  6952  6953  6954  6955  6956  6957  6958  6959  6960  6961  6962  6963  6964  6965  6966  6967  6968  6969  6970  6971  6972  6973  6974  6975  6976  6977  6978  6979  6980  6981  6982  6983  6984  6985  6986  6987  6988  6989  6990  6991  6992  6993  6994  6995  6996  6997  6998  6999  7000  7001  7002  7003  7004  7005  7006  7007  7008  7009  7010  7011  7012  7013  7014  7015  7016  7017  7018  7019  7020  7021  7022  7023  7024  7025  7026  7027  7028  7029  7030  7031  7032  7033  7034  7035  7036  7037  7038  7039  7040  7041  7042  7043  7044  7045  7046  7047  7048  7049  7050  7051  7052  7053  7054  7055  7056  7057  7058  7059  7060  7061  7062  7063  7064  7065  7066  7067  7068  7069  7070  7071  7072  7073  7074  7075  7076  7077  7078  7079  7080  7081  7082  7083  7084  7085  7086  7087  7088  7089  7090  7091  7092  7093  7094  7095  7096  7097  7098  7099  7100  7101  7102  7103  7104  7105  7106  7107  7108  7109  7110  7111  7112  7113  7114  7115  7116  7117  7118  7119  7120  7121  7122  7123  7124  7125  7126  7127  7128  7129  7130  7131  7132  7133  7134  7135  7136  7137  7138  7139  7140  7141  7142  7143  7144  7145  7146  7147  7148  7149  7150  7151  7152  7153  7154  7155  7156  7157  7158  7159  7160  7161  7162  7163  7164  7165  7166  7167  7168  7169  7170  7171  7172  7173  7174  7175  7176  7177  7178  7179  7180  7181  7182  7183  7184  7185  7186  7187  7188  7189  7190  7191  7192  7193  7194  7195  7196  7197  7198  7199  7200  7201  7202  7203  7204  7205  7206  7207  7208  7209  7210  7211  7212  7213  7214  7215  7216  7217  7218  7219  7220  7221  7222  7223  7224  7225  7226  7227  7228  7229  7230  7231  7232  7233  7234  7235  7236  7237  7238  7239  7240  7241  7242  7243  7244  7245  7246  7247  7248  7249  7250  7251  7252  7253  7254  7255  7256  7257  7258  7259  7260  7261  7262  7263  7264  7265  7266  7267  7268  7269  7270  7271  7272  7273  7274  7275  7276  7277  7278  7279  7280  7281  7282  7283  7284  7285  7286  7287  7288  7289  7290  7291  7292  7293  7294  7295  7296  7297  7298  7299  7300  7301  7302  7303  7304  7305  7306  7307  7308  7309  7310  7311  7312  7313  7314  7315  7316  7317  7318  7319  7320  7321  7322  7323  7324  7325  7326  7327  7328  7329  7330  7331  7332  7333  7334  7335  7336  7337  7338  7339  7340  7341  7342  7343  7344  7345  7346  7347  7348  7349  7350  7351  7352  7353  7354  7355  7356  7357  7358  7359  7360  7361  7362  7363  7364  7365  7366  7367  7368  7369  7370  7371  7372  7373  7374  7375  7376  7377  7378  7379  7380  7381  7382  7383  7384  7385  7386  7387  7388  7389  7390  7391  7392  7393  7394  7395  7396  7397  7398  7399  7400  7401  7402  7403  7404  7405  7406  7407  7408  7409  7410  7411  7412  7413  7414  7415  7416  7417  7418  7419  7420  7421  7422  7423  7424  7425  7426  7427  7428  7429  7430  7431  7432  7433  7434  7435  7436  7437  7438  7439  7440  7441  7442  7443  7444  7445  7446  7447  7448  7449  7450  7451  7452  7453  7454  7455  7456  7457  7458  7459  7460  7461  7462  7463  7464  7465  7466  7467  7468  7469  7470  7471  7472  7473  7474  7475  7476  7477  7478  7479  7480  7481  7482  7483  7484  7485  7486  7487  7488  7489  7490  7491  7492  7493  7494  7495  7496  7497  7498  7499  7500  7501  7502  7503  7504  7505  7506  7507  7508  7509  7510  7511  7512  7513  7514  7515  7516  7517  7518  7519  7520  7521  7522  7523  7524  7525  7526  7527  7528  7529  7530  7531  7532  7533  7534  7535  7536  7537  7538  7539  7540  7541  7542  7543  7544  7545  7546  7547  7548  7549  7550  7551  7552  7553  7554  7555  7556  7557  7558  7559  7560  7561  7562  7563  7564  7565  7566  7567  7568  7569  7570  7571  7572  7573  7574  7575  7576  7577  7578  7579  7580  7581  7582  7583  7584  7585  7586  7587  7588  7589  7590  7591  7592  7593  7594  7595  7596  7597  7598  7599  7600  7601  7602  7603  7604  7605  7606  7607  7608  7609  7610  7611  7612  7613  7614  7615  7616  7617  7618  7619  7620  7621  7622  7623  7624  7625  7626  7627  7628  7629  7630  7631  7632  7633  7634  7635  7636  7637  7638  7639  7640  7641  7642  7643  7644  7645  7646  7647  7648  7649  7650  7651  7652  7653  7654  7655  7656  7657  7658  7659  7660  7661  7662  7663  7664  7665  7666  7667  7668  7669  7670  7671  7672  7673  7674  7675  7676  7677  7678  7679  7680  7681  7682  7683  7684  7685  7686  7687  7688  7689  7690  7691  7692  7693  7694  7695  7696  7697  7698  7699  7700  7701  7702  7703  7704  7705  7706  7707  7708  7709  7710  7711  7712  7713  7714  7715  7716  7717  7718  7719  7720  7721  7722  7723  7724  7725  7726  7727  7728  7729  7730  7731  7732  7733  7734  7735  7736  7737  7738  7739  7740  7741  7742  7743  7744  7745  7746  7747  7748  7749  7750  7751  7752  7753  7754  7755  7756  7757  7758  7759  7760  7761  7762  7763  7764  7765  7766  7767  7768  7769  7770  7771  7772  7773  7774  7775  7776  7777  7778  7779  7780  7781  7782  7783  7784  7785  7786  7787  7788  7789  7790  7791  7792  7793  7794  7795  7796  7797  7798  7799  7800  7801  7802  7803  7804  7805  7806  7807  7808  7809  7810  7811  7812  7813  7814  7815  7816  7817  7818  7819  7820  7821  7822  7823  7824  7825  7826  7827  7828  7829  7830  7831  7832  7833  7834  7835  7836  7837  7838  7839  7840  7841  7842  7843  7844  7845  7846  7847  7848  7849  7850  7851  7852  7853  7854  7855  7856  7857  7858  7859  7860  7861  7862  7863  7864  7865  7866  7867  7868  7869  7870  7871  7872  7873  7874  7875  7876  7877  7878  7879  7880  7881  7882  7883  7884  7885  7886  7887  7888  7889  7890  7891  7892  7893  7894  7895  7896  7897  7898  7899  7900  7901  7902  7903  7904  7905  7906  7907  7908  7909  7910  7911  7912  7913  7914  7915  7916  7917  7918  7919  7920  7921  7922  7923  7924  7925  7926  7927  7928  7929  7930  7931  7932  7933  7934  7935  7936  7937  7938  7939  7940  7941  7942  7943  7944  7945  7946  7947  7948  7949  7950  7951  7952  7953  7954  7955  7956  7957  7958  7959  7960  7961  7962  7963  7964  7965  7966  7967  7968  7969  7970  7971  7972  7973  7974  7975  7976  7977  7978  7979  7980  7981  7982  7983  7984  7985  7986  7987  7988  7989  7990  7991  7992  7993  7994  7995  7996  7997  7998  7999  8000  8001  8002  8003  8004  8005  8006  8007  8008  8009  8010  8011  8012  8013  8014  8015  8016  8017  8018  8019  8020  8021  8022  8023  8024  8025  8026  8027  8028  8029  8030  8031  8032  8033  8034  8035  8036  8037  8038  8039  8040  8041  8042  8043  8044  8045  8046  8047  8048  8049  8050  8051  8052  8053  8054  8055  8056  8057  8058  8059  8060  8061  8062  8063  8064  8065  8066  8067  8068  8069  8070  8071  8072  8073  8074  8075  8076  8077  8078  8079  8080  8081  8082  8083  8084  8085  8086  8087  8088  8089  8090  8091  8092  8093  8094  8095  8096  8097  8098  8099  8100  8101  8102  8103  8104  8105  8106  8107  8108  8109  8110  8111  8112  8113  8114  8115  8116  8117  8118  8119  8120  8121  8122  8123  8124  8125  8126  8127  8128  8129  8130  8131  8132  8133  8134  8135  8136  8137  8138  8139  8140  8141  8142  8143  8144  8145  8146  8147  8148  8149  8150  8151  8152  8153  8154  8155  8156  8157  8158  8159  8160  8161  8162  8163  8164  8165  8166  8167  8168  8169  8170  8171  8172  8173  8174  8175  8176  8177  8178  8179  8180  8181  8182  8183  8184  8185  8186  8187  8188  8189  8190  8191  8192  8193  8194  8195  8196  8197  8198  8199  8200  8201  8202  8203  8204  8205  8206  8207  8208  8209  8210  8211  8212  8213  8214  8215  8216  8217  8218  8219  8220  8221  8222  8223  8224  8225  8226  8227  8228  8229  8230  8231  8232  8233  8234  8235  8236  8237  8238  8239  8240  8241  8242  8243  8244  8245  8246  8247  8248  8249  8250  8251  8252  8253  8254  8255  8256  8257  8258  8259  8260  8261  8262  8263  8264  8265  8266  8267  8268  8269  8270  8271  8272  8273  8274  8275  8276  8277  8278  8279  8280  8281  8282  8283  8284  8285  8286  8287  8288  8289  8290  8291  8292  8293  8294  8295  8296  8297  8298  8299  8300  8301  8302  8303  8304  8305  8306  8307  8308  8309  8310  8311  8312  8313  8314  8315  8316  8317  8318  8319  8320  8321  8322  8323  8324  8325  8326  8327  8328  8329  8330  8331  8332  8333  8334  8335  8336  8337  8338  8339  8340  8341  8342  8343  8344  8345  8346  8347  8348  8349  8350  8351  8352  8353  8354  8355  8356  8357  8358  8359  8360  8361  8362  8363  8364  8365  8366  8367  8368  8369  8370  8371  8372  8373  8374  8375  8376  8377  8378  8379  8380  8381  8382  8383  8384  8385  8386  8387  8388  8389  8390  8391  8392  8393  8394  8395  8396  8397  8398  8399  8400  8401  8402  8403  8404  8405  8406  8407  8408  8409  8410  8411  8412  8413  8414  8415  8416  8417  8418  8419  8420  8421  8422  8423  8424  8425  8426  8427  8428  8429  8430  8431  8432  8433  8434  8435  8436  8437  8438  8439  8440  8441  8442  8443  8444  8445  8446  8447  8448  8449  8450  8451  8452  8453  8454  8455  8456  8457  8458  8459  8460  8461  8462  8463  8464  8465  8466  8467  8468  8469  8470  8471  8472  8473  8474  8475  8476  8477  8478  8479  8480  8481  8482  8483  8484  8485  8486  8487  8488  8489  8490  8491  8492  8493  8494  8495  8496  8497  8498  8499  8500  8501  8502  8503  8504  8505  8506  8507  8508  8509  8510  8511  8512  8513  8514  8515  8516  8517  8518  8519  8520  8521  8522  8523  8524  8525  8526  8527  8528  8529  8530  8531  8532  8533  8534  8535  8536  8537  8538  8539  8540  8541  8542  8543  8544  8545  8546  8547  8548  8549  8550  8551  8552  8553  8554  8555  8556  8557  8558  8559  8560  8561  8562  8563  8564  8565  8566  8567  8568  8569  8570  8571  8572  8573  8574  8575  8576  8577  8578  8579  8580  8581  8582  8583  8584  8585  8586  8587  8588  8589  8590  8591  8592  8593  8594  8595  8596  8597  8598  8599  8600  8601  8602  8603  8604  8605  8606  8607  8608  8609  8610  8611  8612  8613  8614  8615  8616  8617  8618  8619  8620  8621  8622  8623  8624  8625  8626  8627  8628  8629  8630  8631  8632  8633  8634  8635  8636  8637  8638  8639  8640  8641  8642  8643  8644  8645  8646  8647  8648  8649  8650  8651  8652  8653  8654  8655  8656  8657  8658  8659  8660  8661  8662  8663  8664  8665  8666  8667  8668  8669  8670  8671  8672  8673  8674  8675  8676  8677  8678  8679  8680  8681  8682  8683  8684  8685  8686  8687  8688  8689  8690  8691  8692  8693  8694  8695  8696  8697  8698  8699  8700  8701  8702  8703  8704  8705  8706  8707  8708  8709  8710  8711  8712  8713  8714  8715  8716  8717  8718  8719  8720  8721  8722  8723  8724  8725  8726  8727  8728  8729  8730  8731  8732  8733  8734  8735  8736  8737  8738  8739  8740  8741  8742  8743  8744  8745  8746  8747  8748  8749  8750  8751  8752  8753  8754  8755  8756  8757  8758  8759  8760  8761  8762  8763  8764  8765  8766  8767  8768  8769  8770  8771  8772  8773  8774  8775  8776  8777  8778  8779  8780  8781  8782  8783  8784  8785  8786  8787  8788  8789  8790  8791  8792  8793  8794  8795  8796  8797  8798  8799  8800  8801  8802  8803  8804  8805  8806  8807  8808  8809  8810  8811  8812  8813  8814  8815  8816  8817  8818  8819  8820  8821  8822  8823  8824  8825  8826  8827  8828  8829  8830  8831  8832  8833  8834  8835  8836  8837  8838  8839  8840  8841  8842  8843  8844  8845  8846  8847  8848  8849  8850  8851  8852  8853  8854  8855  8856  8857  8858  8859  8860  8861  8862  8863  8864  8865  8866  8867  8868  8869  8870  8871  8872  8873  8874  8875  8876  8877  8878  8879  8880  8881  8882  8883  8884  8885  8886  8887  8888  8889  8890  8891  8892  8893  8894  8895  8896  8897  8898  8899  8900  8901  8902  8903  8904  8905  8906  8907  8908  8909  8910  8911  8912  8913  8914  8915  8916  8917  8918  8919  8920  8921  8922  8923  8924  8925  8926  8927  8928  8929  8930  8931  8932  8933  8934  8935  8936  8937  8938  8939  8940  8941  8942  8943  8944  8945  8946  8947  8948  8949  8950  8951  8952  8953  8954  8955  8956  8957  8958  8959  8960  8961  8962  8963  8964  8965  8966  8967  8968  8969  8970  8971  8972  8973  8974  8975  8976  8977  8978  8979  8980  8981  8982  8983  8984  8985  8986  8987  8988  8989  8990  8991  8992  8993  8994  8995  8996  8997  8998  8999  9000  9001  9002  9003  9004  9005  9006  9007  9008  9009  9010  9011  9012  9013  9014  9015  9016  9017  9018  9019  9020  9021  9022  9023  9024  9025  9026  9027  9028  9029  9030  9031  9032  9033  9034  9035  9036  9037  9038  9039  9040  9041  9042  9043  9044  9045  9046  9047  9048  9049  9050  9051  9052  9053  9054  9055  9056  9057  9058  9059  9060  9061  9062  9063  9064  9065  9066  9067  9068  9069  9070  9071  9072  9073  9074  9075  9076  9077  9078  9079  9080  9081  9082  9083  9084  9085  9086  9087  9088  9089  9090  9091  9092  9093  9094  9095  9096  9097  9098  9099  9100  9101  9102  9103  9104  9105  9106  9107  9108  9109  9110  9111  9112  9113  9114  9115  9116  9117  9118  9119  9120  9121  9122  9123  9124  9125  9126  9127  9128  9129  9130  9131  9132  9133  9134  9135  9136  9137  9138  9139  9140  9141  9142  9143  9144  9145  9146  9147  9148  9149  9150  9151  9152  9153  9154  9155  9156  9157  9158  9159  9160  9161  9162  9163  9164  9165  9166  9167  9168  9169  9170  9171  9172  9173  9174  9175  9176  9177  9178  9179  9180  9181  9182  9183  9184  9185  9186  9187  9188  9189  9190  9191  9192  9193  9194  9195  9196  9197  9198  9199  9200  9201  9202  9203  9204  9205  9206  9207  9208  9209  9210  9211  9212  9213  9214  9215  9216  9217  9218  9219  9220  9221  9222  9223  9224  9225  9226  9227  9228  9229  9230  9231  9232  9233  9234  9235  9236  9237  9238  9239  9240  9241  9242  9243  9244  9245  9246  9247  9248  9249  9250  9251  9252  9253  9254  9255  9256  9257  9258  9259  9260  9261  9262  9263  9264  9265  9266  9267  9268  9269  9270  9271  9272  9273  9274  9275  9276  9277  9278  9279  9280  9281  9282  9283  9284  9285  9286  9287  9288  9289  9290  9291  9292  9293  9294  9295  9296  9297  9298  9299  9300  9301  9302  9303  9304  9305  9306  9307  9308  9309  9310  9311  9312  9313  9314  9315  9316  9317  9318  9319  9320  9321  9322  9323  9324  9325  9326  9327  9328  9329  9330  9331  9332  9333  9334  9335  9336  9337  9338  9339  9340  9341  9342  9343  9344  9345  9346  9347  9348  9349  9350  9351  9352  9353  9354  9355  9356  9357  9358  9359  9360  9361  9362  9363  9364  9365  9366  9367  9368  9369  9370  9371  9372  9373  9374  9375  9376  9377  9378  9379  9380  9381  9382  9383  9384  9385  9386  9387  9388  9389  9390  9391  9392  9393  9394  9395  9396  9397  9398  9399  9400  9401  9402  9403  9404  9405  9406  9407  9408  9409  9410  9411  9412  9413  9414  9415  9416  9417  9418  9419  9420  9421  9422  9423  9424  9425  9426  9427  9428  9429  9430  9431  9432  9433  9434  9435  9436  9437  9438  9439  9440  9441  9442  9443  9444  9445  9446  9447  9448  9449  9450  9451  9452  9453  9454  9455  9456  9457  9458  9459  9460  9461  9462  9463  9464  9465  9466  9467  9468  9469  9470  9471  9472  9473  9474  9475  9476  9477  9478  9479  9480  9481  9482  9483  9484  9485  9486  9487  9488  9489  9490  9491  9492  9493  9494  9495  9496  9497  9498  9499  9500  9501  9502  9503  9504  9505  9506  9507  9508  9509  9510  9511  9512  9513  9514  9515  9516  9517  9518  9519  9520  9521  9522  9523  9524  9525  9526  9527  9528  9529  9530  9531  9532  9533  9534  9535  9536  9537  9538  9539  9540  9541  9542  9543  9544  9545  9546  9547  9548  9549  9550  9551  9552  9553  9554  9555  9556  9557  9558  9559  9560  9561  9562  9563  9564  9565  9566  9567  9568  9569  9570  9571  9572  9573  9574  9575  9576  9577  9578  9579  9580  9581  9582  9583  9584  9585  9586  9587  9588  9589  9590  9591  9592  9593  9594  9595  9596  9597  9598  9599  9600  9601  9602  9603  9604  9605  9606  9607  9608  9609  9610  9611  9612  9613  9614  9615  9616  9617  9618  9619  9620  9621  9622  9623  9624  9625  9626  9627  9628  9629  9630  9631  9632  9633  9634  9635  9636  9637  9638  9639  9640  9641  9642  9643  9644  9645  9646  9647  9648  9649  9650  9651  9652  9653  9654  9655  9656  9657  9658  9659  9660  9661  9662  9663  9664  9665  9666  9667  9668  9669  9670  9671  9672  9673  9674  9675  9676  9677  9678  9679  9680  9681  9682  9683  9684  9685  9686  9687  9688  9689  9690  9691  9692  9693  9694  9695  9696  9697  9698  9699  9700  9701  9702  9703  9704  9705  9706  9707  9708  9709  9710  9711  9712  9713  9714  9715  9716  9717  9718  9719  9720  9721  9722  9723  9724  9725  9726  9727  9728  9729  9730  9731  9732  9733  9734  9735  9736  9737  9738  9739  9740  9741  9742  9743  9744  9745  9746  9747  9748  9749  9750  9751  9752  9753  9754  9755  9756  9757  9758  9759  9760  9761  9762  9763  9764  9765  9766  9767  9768  9769  9770  9771  9772  9773  9774  9775  9776  9777  9778  9779  9780  9781  9782  9783  9784  9785  9786  9787  9788  9789  9790  9791  9792  9793  9794  9795  9796  9797  9798  9799  9800  9801  9802  9803  9804  9805  9806  9807  9808  9809  9810  9811  9812  9813  9814  9815  9816  9817  9818  9819  9820  9821  9822  9823  9824  9825  9826  9827  9828  9829  9830  9831  9832  9833  9834  9835  9836  9837  9838  9839  9840  9841  9842  9843  9844  9845  9846  9847  9848  9849  9850  9851  9852  9853  9854  9855  9856  9857  9858  9859  9860  9861  9862  9863  9864  9865  9866  9867  9868  9869  9870  9871  9872  9873  9874  9875  9876  9877  9878  9879  9880  9881  9882  9883  9884  9885  9886  9887  9888  9889  9890  9891  9892  9893  9894  9895  9896  9897  9898  9899  9900  9901  9902  9903  9904  9905  9906  9907  9908  9909  9910  9911  9912  9913  9914  9915  9916  9917  9918  9919  9920  9921  9922  9923  9924  9925  9926  9927  9928  9929  9930  9931  9932  9933  9934  9935  9936  9937  9938  9939  9940  9941  9942  9943  9944  9945  9946  9947  9948  9949  9950  9951  9952  9953  9954  9955  9956  9957  9958  9959  9960  9961  9962  9963  9964  9965  9966  9967  9968  9969  9970  9971  9972  9973  9974  9975  9976  9977  9978  9979  9980  9981  9982  9983  9984  9985  9986  9987  9988  9989  9990  9991  9992  9993  9994  9995  9996  9997  9998  9999 10000 10001 10002 10003 10004 10005 10006 10007 10008 10009 10010 10011 10012 10013 10014 10015 10016 10017 10018 10019 10020 10021 10022 10023 10024 10025 10026 10027 10028 10029 10030 10031 10032 10033 10034 10035 10036 10037 10038 10039 10040 10041 10042 10043 10044 10045 10046 10047 10048 10049 10050 10051 10052 10053 10054 10055 10056 10057 10058 10059 10060 10061 10062 10063 10064 10065 10066 10067 10068 10069 10070 10071 10072 10073 10074 10075 10076 10077 10078 10079 10080 10081 10082 10083 10084 10085 10086 10087 10088 10089 10090 10091 10092 10093 10094 10095 10096 10097 10098 10099 10100 10101 10102 10103 10104 10105 10106 10107 10108 10109 10110 10111 10112 10113 10114 10115 10116 10117 10118 10119 10120 10121 10122 10123 10124 10125 10126 10127 10128 10129 10130 10131 10132 10133 10134 10135 10136 10137 10138 10139 10140 10141 10142 10143 10144 10145 10146 10147 10148 10149 10150 10151 10152 10153 10154 10155 10156 10157 10158 10159 10160 10161 10162 10163 10164 10165 10166 10167 10168 10169 10170 10171 10172 10173 10174 10175 10176 10177 10178 10179 10180 10181 10182 10183 10184 10185 10186 10187 10188 10189 10190 10191 10192 10193 10194 10195 10196 10197 10198 10199 10200 10201 10202 10203 10204 10205 10206 10207 10208 10209 10210 10211 10212 10213 10214 10215 10216 10217 10218 10219 10220 10221 10222 10223 10224 10225 10226 10227 10228 10229 10230 10231 10232 10233 10234 10235 10236 10237 10238 10239 10240 10241 10242 10243 10244 10245 10246 10247 10248 10249 10250 10251 10252 10253 10254 10255 10256 10257 10258 10259 10260 10261 10262 10263 10264 10265 10266 10267 10268 10269 10270 10271 10272 10273 10274 10275 10276 10277 10278 10279 10280 10281 10282 10283 10284 10285 10286 10287 10288 10289 10290 10291 10292 10293 10294 10295 10296 10297 10298 10299 10300 10301 10302 10303 10304 10305 10306 10307 10308 10309 10310 10311 10312 10313 10314 10315 10316 10317 10318 10319 10320 10321 10322 10323 10324 10325 10326 10327 10328 10329 10330 10331 10332 10333 10334 10335 10336 10337 10338 10339 10340 10341 10342 10343 10344 10345 10346 10347 10348 10349 10350 10351 10352 10353 10354 10355 10356 10357 10358 10359 10360 10361 10362 10363 10364 10365 10366 10367 10368 10369 10370 10371 10372 10373 10374 10375 10376 10377 10378 10379 10380 10381 10382 10383 10384 10385 10386 10387 10388 10389 10390 10391 10392 10393 10394 10395 10396 10397 10398 10399 10400 10401 10402 10403 10404 10405 10406 10407 10408 10409 10410 10411 10412 10413 10414 10415 10416 10417 10418 10419 10420 10421 10422 10423 10424 10425 10426 10427 10428 10429 10430 10431 10432 10433 10434 10435 10436 10437 10438 10439 10440 10441 10442 10443 10444 10445 10446 10447 10448 10449 10450 10451 10452 10453 10454 10455 10456 10457 10458 10459 10460 10461 10462 10463 10464 10465 10466 10467 10468 10469 10470 10471 10472 10473 10474 10475 10476 10477 10478 10479 10480 10481 10482 10483 10484 10485 10486 10487 10488 10489 10490 10491 10492 10493 10494 10495 10496 10497 10498 10499 10500 10501 10502 10503 10504 10505 10506 10507 10508 10509 10510 10511 10512 10513 10514 10515 10516 10517 10518 10519 10520 10521 10522 10523 10524 10525 10526 10527 10528 10529 10530 10531 10532 10533 10534 10535 10536 10537 10538 10539 10540 10541 10542 10543 10544 10545 10546 10547 10548 10549 10550 10551 10552 10553 10554 10555 10556 10557 10558 10559 10560 10561 10562 10563 10564 10565 10566 10567 10568 10569 10570 10571 10572 10573 10574 10575 10576 10577 10578 10579 10580 10581 10582 10583 10584 10585 10586 10587 10588 10589 10590 10591 10592 10593 10594 10595 10596 10597 10598 10599 10600 10601 10602 10603 10604 10605 10606 10607 10608 10609 10610 10611 10612 10613 10614 10615 10616 10617 10618 10619 10620 10621 10622 10623 10624 10625 10626 10627 10628 10629 10630 10631 10632 10633 10634 10635 10636 10637 10638 10639 10640 10641 10642 10643 10644 10645 10646 10647 10648 10649 10650 10651 10652 10653 10654 10655 10656 10657 10658 10659 10660 10661 10662 10663 10664 10665 10666 10667 10668 10669 10670 10671 10672 10673 10674 10675 10676 10677 10678 10679 10680 10681 10682 10683 10684 10685 10686 10687 10688 10689 10690 10691 10692 10693 10694 10695 10696 10697 10698 10699 10700 10701 10702 10703 10704 10705 10706 10707 10708 10709 10710 10711 10712 10713 10714 10715 10716 10717 10718 10719 10720 10721 10722 10723 10724 10725 10726 10727 10728 10729 10730 10731 10732 10733 10734 10735 10736 10737 10738 10739 10740 10741 10742 10743 10744 10745 10746 10747 10748 10749 10750 10751 10752 10753 10754 10755 10756 10757 10758 10759 10760 10761 10762 10763 10764 10765 10766 10767 10768 10769 10770 10771 10772 10773 10774 10775 10776 10777 10778 10779 10780 10781 10782 10783 10784 10785 10786 10787 10788 10789 10790 10791 10792 10793 10794 10795 10796 10797 10798 10799 10800 10801 10802 10803 10804 10805 10806 10807 10808 10809 10810 10811 10812 10813 10814 10815 10816 10817 10818 10819 10820 10821 10822 10823 10824 10825 10826 10827 10828 10829 10830 10831 10832 10833 10834 10835 10836 10837 10838 10839 10840 10841 10842 10843 10844 10845 10846 10847 10848 10849 10850 10851 10852 10853 10854 10855 10856 10857 10858 10859 10860 10861 10862 10863 10864 10865 10866 10867 10868 10869 10870 10871 10872 10873 10874 10875 10876 10877 10878 10879 10880 10881 10882 10883 10884 10885 10886 10887 10888 10889 10890 10891 10892 10893 10894 10895 10896 10897 10898 10899 10900 10901 10902 10903 10904 10905 10906 10907 10908 10909 10910 10911 10912 10913 10914 10915 10916 10917 10918 10919 10920 10921 10922 10923 10924 10925 10926 10927 10928 10929 10930 10931 10932 10933 10934 10935 10936 10937 10938 10939 10940 10941 10942 10943 10944 10945 10946 10947 10948 10949 10950 10951 10952 10953 10954 10955 10956 10957 10958 10959 10960 10961 10962 10963 10964 10965 10966 10967 10968 10969 10970 10971 10972 10973 10974 10975 10976 10977 10978 10979 10980 10981 10982 10983 10984 10985 10986 10987 10988 10989 10990 10991 10992 10993 10994 10995 10996 10997 10998 10999 11000 11001 11002 11003 11004 11005 11006 11007 11008 11009 11010 11011 11012 11013 11014 11015 11016 11017 11018 11019 11020 11021 11022 11023 11024 11025 11026 11027 11028 11029 11030 11031 11032 11033 11034 11035 11036 11037 11038 11039 11040 11041 11042 11043 11044 11045 11046 11047 11048 11049 11050 11051 11052 11053 11054 11055 11056 11057 11058 11059 11060 11061 11062 11063 11064 11065 11066 11067 11068 11069 11070 11071 11072 11073 11074 11075 11076 11077 11078 11079 11080 11081 11082 11083 11084 11085 11086 11087 11088 11089 11090 11091 11092 11093 11094 11095 11096 11097 11098 11099 11100 11101 11102 11103 11104 11105 11106 11107 11108 11109 11110 11111 11112 11113 11114 11115 11116 11117 11118 11119 11120 11121 11122 11123 11124 11125 11126 11127 11128 11129 11130 11131 11132 11133 11134 11135 11136 11137 11138 11139 11140 11141 11142 11143 11144 11145 11146 11147 11148 11149 11150 11151 11152 11153 11154 11155 11156 11157 11158 11159 11160 11161 11162 11163 11164 11165 11166 11167 11168 11169 11170 11171 11172 11173 11174 11175 11176 11177 11178 11179 11180 11181 11182 11183 11184 11185 11186 11187 11188 11189 11190 11191 11192 11193 11194 11195 11196 11197 11198 11199 11200 11201 11202 11203 11204 11205 11206 11207 11208 11209 11210 11211 11212 11213 11214 11215 11216 11217 11218 11219 11220 11221 11222 11223 11224 11225 11226 11227 11228 11229 11230 11231 11232 11233 11234 11235 11236 11237 11238 11239 11240 11241 11242 11243 11244 11245 11246 11247 11248 11249 11250 11251 11252 11253 11254 11255 11256 11257 11258 11259 11260 11261 11262 11263 11264 11265 11266 11267 11268 11269 11270 11271 11272 11273 11274 11275 11276 11277 11278 11279 11280 11281 11282 11283 11284 11285 11286 11287 11288 11289 11290 11291 11292 11293 11294 11295 11296 11297 11298 11299 11300 11301 11302 11303 11304 11305 11306 11307 11308 11309 11310 11311 11312 11313 11314 11315 11316 11317 11318 11319 11320 11321 11322 11323 11324 11325 11326 11327 11328 11329 11330 11331 11332 11333 11334 11335 11336 11337 11338 11339 11340 11341 11342 11343 11344 11345 11346 11347 11348 11349 11350 11351 11352 11353 11354 11355 11356 11357 11358 11359 11360 11361 11362 11363 11364 11365 11366 11367 11368 11369 11370 11371 11372 11373 11374 11375 11376 11377 11378 11379 11380 11381 11382 11383 11384 11385 11386 11387 11388 11389 11390 11391 11392 11393 11394 11395 11396 11397 11398 11399 11400 11401 11402 11403 11404 11405 11406 11407 11408 11409 11410 11411 11412 11413 11414 11415 11416 11417 11418 11419 11420 11421 11422 11423 11424 11425 11426 11427 11428 11429 11430 11431 11432 11433 11434 11435 11436 11437 11438 11439 11440 11441 11442 11443 11444 11445 11446 11447 11448 11449 11450 11451 11452 11453 11454 11455 11456 11457 11458 11459 11460 11461 11462 11463 11464 11465 11466 11467 11468 11469 11470 11471 11472 11473 11474 11475 11476 11477 11478 11479 11480 11481 11482 11483 11484 11485 11486 11487 11488 11489 11490 11491 11492 11493 11494 11495 11496 11497 11498 11499 11500 11501 11502 11503 11504 11505 11506 11507 11508 11509 11510 11511 11512 11513 11514 11515 11516 11517 11518 11519 11520 11521 11522 11523 11524 11525 11526 11527 11528 11529 11530 11531 11532 11533 11534 11535 11536 11537 11538 11539 11540 11541 11542 11543 11544 11545 11546 11547 11548 11549 11550 11551 11552 11553 11554 11555 11556 11557 11558 11559 11560 11561 11562 11563 11564 11565 11566 11567 11568 11569 11570 11571 11572 11573 11574 11575 11576 11577 11578 11579 11580 11581 11582 11583 11584 11585 11586 11587 11588 11589 11590 11591 11592 11593 11594 11595 11596 11597 11598 11599 11600 11601 11602 11603 11604 11605 11606 11607 11608 11609 11610 11611 11612 11613 11614 11615 11616 11617 11618 11619 11620 11621 11622 11623 11624 11625 11626 11627 11628 11629 11630 11631 11632 11633 11634 11635 11636 11637 11638 11639 11640 11641 11642 11643 11644 11645 11646 11647 11648 11649 11650 11651 11652 11653 11654 11655 11656 11657 11658 11659 11660 11661 11662 11663 11664 11665 11666 11667 11668 11669 11670 11671 11672 11673 11674 11675 11676 11677 11678 11679 11680 11681 11682 11683 11684 11685 11686 11687 11688 11689 11690 11691 11692 11693 11694 11695 11696 11697 11698 11699 11700 11701 11702 11703 11704 11705 11706 11707 11708 11709 11710 11711 11712 11713 11714 11715 11716 11717 11718 11719 11720 11721 11722 11723 11724 11725 11726 11727 11728 11729 11730 11731 11732 11733 11734 11735 11736 11737 11738 11739 11740 11741 11742 11743 11744 11745 11746 11747 11748 11749 11750 11751 11752 11753 11754 11755 11756 11757 11758 11759 11760 11761 11762 11763 11764 11765 11766 11767 11768 11769 11770 11771 11772 11773 11774 11775 11776 11777 11778 11779 11780 11781 11782 11783 11784 11785 11786 11787 11788 11789 11790 11791 11792 11793 11794 11795 11796 11797 11798 11799 11800 11801 11802 11803 11804 11805 11806 11807 11808 11809 11810 11811 11812 11813 11814 11815 11816 11817 11818 11819 11820 11821 11822 11823 11824 11825 11826 11827 11828 11829 11830 11831 11832 11833 11834 11835 11836 11837 11838 11839 11840 11841 11842 11843 11844 11845 11846 11847 11848 11849 11850 11851 11852 11853 11854 11855 11856 11857 11858 11859 11860 11861 11862 11863 11864 11865 11866 11867 11868 11869 11870 11871 11872 11873 11874 11875 11876 11877 11878 11879 11880 11881 11882 11883 11884 11885 11886 11887 11888 11889 11890 11891 11892 11893 11894 11895 11896 11897 11898 11899 11900 11901 11902 11903 11904 11905 11906 11907 11908 11909 11910 11911 11912 11913 11914 11915 11916 11917 11918 11919 11920 11921 11922 11923 11924 11925 11926 11927 11928 11929 11930 11931 11932 11933 11934 11935 11936 11937 11938 11939 11940 11941 11942 11943 11944 11945 11946 11947 11948 11949 11950 11951 11952 11953 11954 11955 11956 11957 11958 11959 11960 11961 11962 11963 11964 11965 11966 11967 11968 11969 11970 11971 11972 11973 11974 11975 11976 11977 11978 11979 11980 11981 11982 11983 11984 11985 11986 11987 11988 11989 11990 11991 11992 11993 11994 11995 11996 11997 11998 11999 12000 12001 12002 12003 12004 12005 12006 12007 12008 12009 12010 12011 12012 12013 12014 12015 12016 12017 12018 12019 12020 12021 12022 12023 12024 12025 12026 12027 12028 12029 12030 12031 12032 12033 12034 12035 12036 12037 12038 12039 12040 12041 12042 12043 12044 12045 12046 12047 12048 12049 12050 12051 12052 12053 12054 12055 12056 12057 12058 12059 12060 12061 12062 12063 12064 12065 12066 12067 12068 12069 12070 12071 12072 12073 12074 12075 12076 12077 12078 12079 12080 12081 12082 12083 12084 12085 12086 12087 12088 12089 12090 12091 12092 12093 12094 12095 12096 12097 12098 12099 12100 12101 12102 12103 12104 12105 12106 12107 12108 12109 12110 12111 12112 12113 12114 12115 12116 12117 12118 12119 12120 12121 12122 12123 12124 12125 12126 12127 12128 12129 12130 12131 12132 12133 12134 12135 12136 12137 12138 12139 12140 12141 12142 12143 12144 12145 12146 12147 12148 12149 12150 12151 12152 12153 12154 12155 12156 12157 12158 12159 12160 12161 12162 12163 12164 12165 12166 12167 12168 12169 12170 12171 12172 12173 12174 12175 12176 12177 12178 12179 12180 12181 12182 12183 12184 12185 12186 12187 12188 12189 12190 12191 12192 12193 12194 12195 12196 12197 12198 12199 12200 12201 12202 12203 12204 12205 12206 12207 12208 12209 12210 12211 12212 12213 12214 12215 12216 12217 12218 12219 12220 12221 12222 12223 12224 12225 12226 12227 12228 12229 12230 12231 12232 12233 12234 12235 12236 12237 12238 12239 12240 12241 12242 12243 12244 12245 12246 12247 12248 12249 12250 12251 12252 12253 12254 12255 12256 12257 12258 12259 12260 12261 12262 12263 12264 12265 12266 12267 12268 12269 12270 12271 12272 12273 12274 12275 12276 12277 12278 12279 12280 12281 12282 12283 12284 12285 12286 12287 12288 12289 12290 12291 12292 12293 12294 12295 12296 12297 12298 12299 12300 12301 12302 12303 12304 12305 12306 12307 12308 12309 12310 12311 12312 12313 12314 12315 12316 12317 12318 12319 12320 12321 12322 12323 12324 12325 12326 12327 12328 12329 12330 12331 12332 12333 12334 12335 12336 12337 12338 12339 12340 12341 12342 12343 12344 12345 12346 12347 12348 12349 12350 12351 12352 12353 12354 12355 12356 12357 12358 12359 12360 12361 12362 12363 12364 12365 12366 12367 12368 12369 12370 12371 12372 12373 12374 12375 12376 12377 12378 12379 12380 12381 12382 12383 12384 12385 12386 12387 12388 12389 12390 12391 12392 12393 12394 12395 12396 12397 12398 12399 12400 12401 12402 12403 12404 12405 12406 12407 12408 12409 12410 12411 12412 12413 12414 12415 12416 12417 12418 12419 12420 12421 12422 12423 12424 12425 12426 12427 12428 12429 12430 12431 12432 12433 12434 12435 12436 12437 12438 12439 12440 12441 12442 12443 12444 12445 12446 12447 12448 12449 12450 12451 12452 12453 12454 12455 12456 12457 12458 12459 12460 12461 12462 12463 12464 12465 12466 12467 12468 12469 12470 12471 12472 12473 12474 12475 12476 12477 12478 12479 12480 12481 12482 12483 12484 12485 12486 12487 12488 12489 12490 12491 12492 12493 12494 12495 12496 12497 12498 12499 12500 12501 12502 12503 12504 12505 12506 12507 12508 12509 12510 12511 12512 12513 12514 12515 12516 12517 12518 12519 12520 12521 12522 12523 12524 12525 12526 12527 12528 12529 12530 12531 12532 12533 12534 12535 12536 12537 12538 12539 12540 12541 12542 12543 12544 12545 12546 12547 12548 12549 12550 12551 12552 12553 12554 12555 12556 12557 12558 12559 12560 12561 12562 12563 12564 12565 12566 12567 12568 12569 12570 12571 12572 12573 12574 12575 12576 12577 12578 12579 12580 12581 12582 12583 12584 12585 12586 12587 12588 12589 12590 12591 12592 12593 12594 12595 12596 12597 12598 12599 12600 12601 12602 12603 12604 12605 12606 12607 12608 12609 12610 12611 12612 12613 12614 12615 12616 12617 12618 12619 12620 12621 12622 12623 12624 12625 12626 12627 12628 12629 12630 12631 12632 12633 12634 12635 12636 12637 12638 12639 12640 12641 12642 12643 12644 12645 12646 12647 12648 12649 12650 12651 12652 12653 12654 12655 12656 12657 12658 12659 12660 12661 12662 12663 12664 12665 12666 12667 12668 12669 12670 12671 12672 12673 12674 12675 12676 12677 12678 12679 12680 12681 12682 12683 12684 12685 12686 12687 12688 12689 12690 12691 12692 12693 12694 12695 12696 12697 12698 12699 12700 12701 12702 12703 12704 12705 12706 12707 12708 12709 12710 12711 12712 12713 12714 12715 12716 12717 12718 12719 12720 12721 12722 12723 12724 12725 12726 12727 12728 12729 12730 12731 12732 12733 12734 12735 12736 12737 12738 12739 12740 12741 12742 12743 12744 12745 12746 12747 12748 12749 12750 12751 12752 12753 12754 12755 12756 12757 12758 12759 12760 12761 12762 12763 12764 12765 12766 12767 12768 12769 12770 12771 12772 12773 12774 12775 12776 12777 12778 12779 12780 12781 12782 12783 12784 12785 12786 12787 12788 12789 12790 12791 12792 12793 12794 12795 12796 12797 12798 12799 12800 12801 12802 12803 12804 12805 12806 12807 12808 12809 12810 12811 12812 12813 12814 12815 12816 12817 12818 12819 12820 12821 12822 12823 12824 12825 12826 12827 12828 12829 12830 12831 12832 12833 12834 12835 12836 12837 12838 12839 12840 12841 12842 12843 12844 12845 12846 12847 12848 12849 12850 12851 12852 12853 12854 12855 12856 12857 12858 12859 12860 12861 12862 12863 12864 12865 12866 12867 12868 12869 12870 12871 12872 12873 12874 12875 12876 12877 12878 12879 12880 12881 12882 12883 12884 12885 12886 12887 12888 12889 12890 12891 12892 12893 12894 12895 12896 12897 12898 12899 12900 12901 12902 12903 12904 12905 12906 12907 12908 12909 12910 12911 12912 12913 12914 12915 12916 12917 12918 12919 12920 12921 12922 12923 12924 12925 12926 12927 12928 12929 12930 12931 12932 12933 12934 12935 12936 12937 12938 12939 12940 12941 12942 12943 12944 12945 12946 12947 12948 12949 12950 12951 12952 12953 12954 12955 12956 12957 12958 12959 12960 12961 12962 12963 12964 12965 12966 12967 12968 12969 12970 12971 12972 12973 12974 12975 12976 12977 12978 12979 12980 12981 12982 12983 12984 12985 12986 12987 12988 12989 12990 12991 12992 12993 12994 12995 12996 12997 12998 12999 13000 13001 13002 13003 13004 13005 13006 13007 13008 13009 13010 13011 13012 13013 13014 13015 13016 13017 13018 13019 13020 13021 13022 13023 13024 13025 13026 13027 13028 13029 13030 13031 13032 13033 13034 13035 13036 13037 13038 13039 13040 13041 13042 13043 13044 13045 13046 13047 13048 13049 13050 13051 13052 13053 13054 13055 13056 13057 13058 13059 13060 13061 13062 13063 13064 13065 13066 13067 13068 13069 13070 13071 13072 13073 13074 13075 13076 13077 13078 13079 13080 13081 13082 13083 13084 13085 13086 13087 13088 13089 13090 13091 13092 13093 13094 13095 13096 13097 13098 13099 13100 13101 13102 13103 13104 13105 13106 13107 13108 13109 13110 13111 13112 13113 13114 13115 13116 13117 13118 13119 13120 13121 13122 13123 13124 13125 13126 13127 13128 13129 13130 13131 13132 13133 13134 13135 13136 13137 13138 13139 13140 13141 13142 13143 13144 13145 13146 13147 13148 13149 13150 13151 13152 13153 13154 13155 13156 13157 13158 13159 13160 13161 13162 13163 13164 13165 13166 13167 13168 13169 13170 13171 13172 13173 13174 13175 13176 13177 13178 13179 13180 13181 13182 13183 13184 13185 13186 13187 13188 13189 13190 13191 13192 13193 13194 13195 13196 13197 13198 13199 13200 13201 13202 13203 13204 13205 13206 13207 13208 13209 13210 13211 13212 13213 13214 13215 13216 13217 13218 13219 13220 13221 13222 13223 13224 13225 13226 13227 13228 13229 13230 13231 13232 13233 13234 13235 13236 13237 13238 13239 13240 13241 13242 13243 13244 13245 13246 13247 13248 13249 13250 13251 13252 13253 13254 13255 13256 13257 13258 13259 13260 13261 13262 13263 13264 13265 13266 13267 13268 13269 13270 13271 13272 13273 13274 13275 13276 13277 13278 13279 13280 13281 13282 13283 13284 13285 13286 13287 13288 13289 13290 13291 13292 13293 13294 13295 13296 13297 13298 13299 13300 13301 13302 13303 13304 13305 13306 13307 13308 13309 13310 13311 13312 13313 13314 13315 13316 13317 13318 13319 13320 13321 13322 13323 13324 13325 13326 13327 13328 13329 13330 13331 13332 13333 13334 13335 13336 13337 13338 13339 13340 13341 13342 13343 13344 13345 13346 13347 13348 13349 13350 13351 13352 13353 13354 13355 13356 13357 13358 13359 13360 13361 13362 13363 13364 13365 13366 13367 13368 13369 13370 13371 13372 13373 13374 13375 13376 13377 13378 13379 13380 13381 13382 13383 13384 13385 13386 13387 13388 13389 13390 13391 13392 13393 13394 13395 13396 13397 13398 13399 13400 13401 13402 13403 13404 13405 13406 13407 13408 13409 13410 13411 13412 13413 13414 13415 13416 13417 13418 13419 13420 13421 13422 13423 13424 13425 13426 13427 13428 13429 13430 13431 13432 13433 13434 13435 13436 13437 13438 13439 13440 13441 13442 13443 13444 13445 13446 13447 13448 13449 13450 13451 13452 13453 13454 13455 13456 13457 13458 13459 13460 13461 13462 13463 13464 13465 13466 13467 13468 13469 13470 13471 13472 13473 13474 13475 13476 13477 13478 13479 13480 13481 13482 13483 13484 13485 13486 13487 13488 13489 13490 13491 13492 13493 13494 13495 13496 13497 13498 13499 13500 13501 13502 13503 13504 13505 13506 13507 13508 13509 13510 13511 13512 13513 13514 13515 13516 13517 13518 13519 13520 13521 13522 13523 13524 13525 13526 13527 13528 13529 13530 13531 13532 13533 13534 13535 13536 13537 13538 13539 13540 13541 13542 13543 13544 13545 13546 13547 13548 13549 13550 13551 13552 13553 13554 13555 13556 13557 13558 13559 13560 13561 13562 13563 13564 13565 13566 13567 13568 13569 13570 13571 13572 13573 13574 13575 13576 13577 13578 13579 13580 13581 13582 13583 13584 13585 13586 13587 13588 13589 13590 13591 13592 13593 13594 13595 13596 13597 13598 13599 13600 13601 13602 13603 13604 13605 13606 13607 13608 13609 13610 13611 13612 13613 13614 13615 13616 13617 13618 13619 13620 13621 13622 13623 13624 13625 13626 13627 13628 13629 13630 13631 13632 13633 13634 13635 13636 13637 13638 13639 13640 13641 13642 13643 13644 13645 13646 13647 13648 13649 13650 13651 13652 13653 13654 13655 13656 13657 13658 13659 13660 13661 13662 13663 13664 13665 13666 13667 13668 13669 13670 13671 13672 13673 13674 13675 13676 13677 13678 13679 13680 13681 13682 13683 13684 13685 13686 13687 13688 13689 13690 13691 13692 13693 13694 13695 13696 13697 13698 13699 13700 13701 13702 13703 13704 13705 13706 13707 13708 13709 13710 13711 13712 13713 13714 13715 13716 13717 13718 13719 13720 13721 13722 13723 13724 13725 13726 13727 13728 13729 13730 13731 13732 13733 13734 13735 13736 13737 13738 13739 13740 13741 13742 13743 13744 13745 13746 13747 13748 13749 13750 13751 13752 13753 13754 13755 13756 13757 13758 13759 13760 13761 13762 13763 13764 13765 13766 13767 13768 13769 13770 13771 13772 13773 13774 13775 13776 13777 13778 13779 13780 13781 13782 13783 13784 13785 13786 13787 13788 13789 13790 13791 13792 13793 13794 13795 13796 13797 13798 13799 13800 13801 13802 13803 13804 13805 13806 13807 13808 13809 13810 13811 13812 13813 13814 13815 13816 13817 13818 13819 13820 13821 13822 13823 13824 13825 13826 13827 13828 13829 13830 13831 13832 13833 13834 13835 13836 13837 13838 13839 13840 13841 13842 13843 13844 13845 13846 13847 13848 13849 13850 13851 13852 13853 13854 13855 13856 13857 13858 13859 13860 13861 13862 13863 13864 13865 13866 13867 13868 13869 13870 13871 13872 13873 13874 13875 13876 13877 13878 13879 13880 13881 13882 13883 13884 13885 13886 13887 13888 13889 13890 13891 13892 13893 13894 13895 13896 13897 13898 13899 13900 13901 13902 13903 13904 13905 13906 13907 13908 13909 13910 13911 13912 13913 13914 13915 13916 13917 13918 13919 13920 13921 13922 13923 13924 13925 13926 13927 13928 13929 13930 13931 13932 13933 13934 13935 13936 13937 13938 13939 13940 13941 13942 13943 13944 13945 13946 13947 13948 13949 13950 13951 13952 13953 13954 13955 13956 13957 13958 13959 13960 13961 13962 13963 13964 13965 13966 13967 13968 13969 13970 13971 13972 13973 13974 13975 13976 13977 13978 13979 13980 13981 13982 13983 13984 13985 13986 13987 13988 13989 13990 13991 13992 13993 13994 13995 13996 13997 13998 13999 14000 14001 14002 14003 14004 14005 14006 14007 14008 14009 14010 14011 14012 14013 14014 14015 14016 14017 14018 14019 14020 14021 14022 14023 14024 14025 14026 14027 14028 14029 14030 14031 14032 14033 14034 14035 14036 14037 14038 14039 14040 14041 14042 14043 14044 14045 14046 14047 14048 14049 14050 14051 14052 14053 14054 14055 14056 14057 14058 14059 14060 14061 14062 14063 14064 14065 14066 14067 14068 14069 14070 14071 14072 14073 14074 14075 14076 14077 14078 14079 14080 14081 14082 14083 14084 14085 14086 14087 14088 14089 14090 14091 14092 14093 14094 14095 14096 14097 14098 14099 14100 14101 14102 14103 14104 14105 14106 14107 14108 14109 14110 14111 14112 14113 14114 14115 14116 14117 14118 14119 14120 14121 14122 14123 14124 14125 14126 14127 14128 14129 14130 14131 14132 14133 14134 14135 14136 14137 14138 14139 14140 14141 14142 14143 14144 14145 14146 14147 14148 14149 14150 14151 14152 14153 14154 14155 14156 14157 14158 14159 14160 14161 14162 14163 14164 14165 14166 14167 14168 14169 14170 14171 14172 14173 14174 14175 14176 14177 14178 14179 14180 14181 14182 14183 14184 14185 14186 14187 14188 14189 14190 14191 14192 14193 14194 14195 14196 14197 14198 14199 14200 14201 14202 14203 14204 14205 14206 14207 14208 14209 14210 14211 14212 14213 14214 14215 14216 14217 14218 14219 14220 14221 14222 14223 14224 14225 14226 14227 14228 14229 14230 14231 14232 14233 14234 14235 14236 14237 14238 14239 14240 14241 14242 14243 14244 14245 14246 14247 14248 14249 14250 14251 14252 14253 14254 14255 14256 14257 14258 14259 14260 14261 14262 14263 14264 14265 14266 14267 14268 14269 14270 14271 14272 14273 14274 14275 14276 14277 14278 14279 14280 14281 14282 14283 14284 14285 14286 14287 14288 14289 14290 14291 14292 14293 14294 14295 14296 14297 14298 14299 14300 14301 14302 14303 14304 14305 14306 14307 14308 14309 14310 14311 14312 14313 14314 14315 14316 14317 14318 14319 14320 14321 14322 14323 14324 14325 14326 14327 14328 14329 14330 14331 14332 14333 14334 14335 14336 14337 14338 14339 14340 14341 14342 14343 14344 14345 14346 14347 14348 14349 14350 14351 14352 14353 14354 14355 14356 14357 14358 14359 14360 14361 14362 14363 14364 14365 14366 14367 14368 14369 14370 14371 14372 14373 14374 14375 14376 14377 14378 14379 14380 14381 14382 14383 14384 14385 14386 14387 14388 14389 14390 14391 14392 14393 14394 14395 14396 14397 14398 14399 14400 14401 14402 14403 14404 14405 14406 14407 14408 14409 14410 14411 14412 14413 14414 14415 14416 14417 14418 14419 14420 14421 14422 14423 14424 14425 14426 14427 14428 14429 14430 14431 14432 14433 14434 14435 14436 14437 14438 14439 14440 14441 14442 14443 14444 14445 14446 14447 14448 14449 14450 14451 14452 14453 14454 14455 14456 14457 14458 14459 14460 14461 14462 14463 14464 14465 14466 14467 14468 14469 14470 14471 14472 14473 14474 14475 14476 14477 14478 14479 14480 14481 14482 14483 14484 14485 14486 14487 14488 14489 14490 14491 14492 14493 14494 14495 14496 14497 14498 14499 14500 14501 14502 14503 14504 14505 14506 14507 14508 14509 14510 14511 14512 14513 14514 14515 14516 14517 14518 14519 14520 14521 14522 14523 14524 14525 14526 14527 14528 14529 14530 14531 14532 14533 14534 14535 14536 14537 14538 14539 14540 14541 14542 14543 14544 14545 14546 14547 14548 14549 14550 14551 14552 14553 14554 14555 14556 14557 14558 14559 14560 14561 14562 14563 14564 14565 14566 14567 14568 14569 14570 14571 14572 14573 14574 14575 14576 14577 14578 14579 14580 14581 14582 14583 14584 14585 14586 14587 14588 14589 14590 14591 14592 14593 14594 14595 14596 14597 14598 14599 14600 14601 14602 14603 14604 14605 14606 14607 14608 14609 14610 14611 14612 14613 14614 14615 14616 14617 14618 14619 14620 14621 14622 14623 14624 14625 14626 14627 14628 14629 14630 14631 14632 14633 14634 14635 14636 14637 14638 14639 14640 14641 14642 14643 14644 14645 14646 14647 14648 14649 14650 14651 14652 14653 14654 14655 14656 14657 14658 14659 14660 14661 14662 14663 14664 14665 14666 14667 14668 14669 14670 14671 14672 14673 14674 14675 14676 14677 14678 14679 14680 14681 14682 14683 14684 14685 14686 14687 14688 14689 14690 14691 14692 14693 14694 14695 14696 14697 14698 14699 14700 14701 14702 14703 14704 14705 14706 14707 14708 14709 14710 14711 14712 14713 14714 14715 14716 14717 14718 14719 14720 14721 14722 14723 14724 14725 14726 14727 14728 14729 14730 14731 14732 14733 14734 14735 14736 14737 14738 14739 14740 14741 14742 14743 14744 14745 14746 14747 14748 14749 14750 14751 14752 14753 14754 14755 14756 14757 14758 14759 14760 14761 14762 14763 14764 14765 14766 14767 14768 14769 14770 14771 14772 14773 14774 14775 14776 14777 14778 14779 14780 14781 14782 14783 14784 14785 14786 14787 14788 14789 14790 14791 14792 14793 14794 14795 14796 14797 14798 14799 14800 14801 14802 14803 14804 14805 14806 14807 14808 14809 14810 14811 14812 14813 14814 14815 14816 14817 14818 14819 14820 14821 14822 14823 14824 14825 14826 14827 14828 14829 14830 14831 14832 14833 14834 14835 14836 14837 14838 14839 14840 14841 14842 14843 14844 14845 14846 14847 14848 14849 14850 14851 14852 14853 14854 14855 14856 14857 14858 14859 14860 14861 14862 14863 14864 14865 14866 14867 14868 14869 14870 14871 14872 14873 14874 14875 14876 14877 14878 14879 14880 14881 14882 14883 14884 14885 14886 14887 14888 14889 14890 14891 14892 14893 14894 14895 14896 14897 14898 14899 14900 14901 14902 14903 14904 14905 14906 14907 14908 14909 14910 14911 14912 14913 14914 14915 14916 14917 14918 14919 14920 14921 14922 14923 14924 14925 14926 14927 14928 14929 14930 14931 14932 14933 14934 14935 14936 14937 14938 14939 14940 14941 14942 14943 14944 14945 14946 14947 14948 14949 14950 14951 14952 14953 14954 14955 14956 14957 14958 14959 14960 14961 14962 14963 14964 14965 14966 14967 14968 14969 14970 14971 14972 14973 14974 14975 14976 14977 14978 14979 14980 14981 14982 14983 14984 14985 14986 14987 14988 14989 14990 14991 14992 14993 14994 14995 14996 14997 14998 14999 15000 15001 15002 15003 15004 15005 15006 15007 15008 15009 15010 15011 15012 15013 15014 15015 15016 15017 15018 15019 15020 15021 15022 15023 15024 15025 15026 15027 15028 15029 15030 15031 15032 15033 15034 15035 15036 15037 15038 15039 15040 15041 15042 15043 15044 15045 15046 15047 15048 15049 15050 15051 15052 15053 15054 15055 15056 15057 15058 15059 15060 15061 15062 15063 15064 15065 15066 15067 15068 15069 15070 15071 15072 15073 15074 15075 15076 15077 15078 15079 15080 15081 15082 15083 15084 15085 15086 15087 15088 15089 15090 15091 15092 15093 15094 15095 15096 15097 15098 15099 15100 15101 15102 15103 15104 15105 15106 15107 15108 15109 15110 15111 15112 15113 15114 15115 15116 15117 15118 15119 15120 15121 15122 15123 15124 15125 15126 15127 15128 15129 15130 15131 15132 15133 15134 15135 15136 15137 15138 15139 15140 15141 15142 15143 15144 15145 15146 15147 15148 15149 15150 15151 15152 15153 15154 15155 15156 15157 15158 15159 15160 15161 15162 15163 15164 15165 15166 15167 15168 15169 15170 15171 15172 15173 15174 15175 15176 15177 15178 15179 15180 15181 15182 15183 15184 15185 15186 15187 15188 15189 15190 15191 15192 15193 15194 15195 15196 15197 15198 15199 15200 15201 15202 15203 15204 15205 15206 15207 15208 15209 15210 15211 15212 15213 15214 15215 15216 15217 15218 15219 15220 15221 15222 15223 15224 15225 15226 15227 15228 15229 15230 15231 15232 15233 15234 15235 15236 15237 15238 15239 15240 15241 15242 15243 15244 15245 15246 15247 15248 15249 15250 15251 15252 15253 15254 15255 15256 15257 15258 15259 15260 15261 15262 15263 15264 15265 15266 15267 15268 15269 15270 15271 15272 15273 15274 15275 15276 15277 15278 15279 15280 15281 15282 15283 15284 15285 15286 15287 15288 15289 15290 15291 15292 15293 15294 15295 15296 15297 15298 15299 15300 15301 15302 15303 15304 15305 15306 15307 15308 15309 15310 15311 15312 15313 15314 15315 15316 15317 15318 15319 15320 15321 15322 15323 15324 15325 15326 15327 15328 15329 15330 15331 15332 15333 15334 15335 15336 15337 15338 15339 15340 15341 15342 15343 15344 15345 15346 15347 15348 15349 15350 15351 15352 15353 15354 15355 15356 15357 15358 15359 15360 15361 15362 15363 15364 15365 15366 15367 15368 15369 15370 15371 15372 15373 15374 15375 15376 15377 15378 15379 15380 15381 15382 15383 15384 15385 15386 15387 15388 15389 15390 15391 15392 15393 15394 15395 15396 15397 15398 15399 15400 15401 15402 15403 15404 15405 15406 15407 15408 15409 15410 15411 15412 15413 15414 15415 15416 15417 15418 15419 15420 15421 15422 15423 15424 15425 15426 15427 15428 15429 15430 15431 15432 15433 15434 15435 15436 15437 15438 15439 15440 15441 15442 15443 15444 15445 15446 15447 15448 15449 15450 15451 15452 15453 15454 15455 15456 15457 15458 15459 15460 15461 15462 15463 15464 15465 15466 15467 15468 15469 15470 15471 15472 15473 15474 15475 15476 15477 15478 15479 15480 15481 15482 15483 15484 15485 15486 15487 15488 15489 15490 15491 15492 15493 15494 15495 15496 15497 15498 15499 15500 15501 15502 15503 15504 15505 15506 15507 15508 15509 15510 15511 15512 15513 15514 15515 15516 15517 15518 15519 15520 15521 15522 15523 15524 15525 15526 15527 15528 15529 15530 15531 15532 15533 15534 15535 15536 15537 15538 15539 15540 15541 15542 15543 15544 15545 15546 15547 15548 15549 15550 15551 15552 15553 15554 15555 15556 15557 15558 15559 15560 15561 15562 15563 15564 15565 15566 15567 15568 15569 15570 15571 15572 15573 15574 15575 15576 15577 15578 15579 15580 15581 15582 15583 15584 15585 15586 15587 15588 15589 15590 15591 15592 15593 15594 15595 15596 15597 15598 15599 15600 15601 15602 15603 15604 15605 15606 15607 15608 15609 15610 15611 15612 15613 15614 15615 15616 15617 15618 15619 15620 15621 15622 15623 15624 15625 15626 15627 15628 15629 15630 15631 15632 15633 15634 15635 15636 15637 15638 15639 15640 15641 15642 15643 15644 15645 15646 15647 15648 15649 15650 15651 15652 15653 15654 15655 15656 15657 15658 15659 15660 15661 15662 15663 15664 15665 15666 15667 15668 15669 15670 15671 15672 15673 15674 15675 15676 15677 15678 15679 15680 15681 15682 15683 15684 15685 15686 15687 15688 15689 15690 15691 15692 15693 15694 15695 15696 15697 15698 15699 15700 15701 15702 15703 15704 15705 15706 15707 15708 15709 15710 15711 15712 15713 15714 15715 15716 15717 15718 15719 15720 15721 15722 15723 15724 15725 15726 15727 15728 15729 15730 15731 15732 15733 15734 15735 15736 15737 15738 15739 15740 15741 15742 15743 15744 15745 15746 15747 15748 15749 15750 15751 15752 15753 15754 15755 15756 15757 15758 15759 15760 15761 15762 15763 15764 15765 15766 15767 15768 15769 15770 15771 15772 15773 15774 15775 15776 15777 15778 15779 15780 15781 15782 15783 15784 15785 15786 15787 15788 15789 15790 15791 15792 15793 15794 15795 15796 15797 15798 15799 15800 15801 15802 15803 15804 15805 15806 15807 15808 15809 15810 15811 15812 15813 15814 15815 15816 15817 15818 15819 15820 15821 15822 15823 15824 15825 15826 15827 15828 15829 15830 15831 15832 15833 15834 15835 15836 15837 15838 15839 15840 15841 15842 15843 15844 15845 15846 15847 15848 15849 15850 15851 15852 15853 15854 15855 15856 15857 15858 15859 15860 15861 15862 15863 15864 15865 15866 15867 15868 15869 15870 15871 15872 15873 15874 15875 15876 15877 15878 15879 15880 15881 15882 15883 15884 15885 15886 15887 15888 15889 15890 15891 15892 15893 15894 15895 15896 15897 15898 15899 15900 15901 15902 15903 15904 15905 15906 15907 15908 15909 15910 15911 15912 15913 15914 15915 15916 15917 15918 15919 15920 15921 15922 15923 15924 15925 15926 15927 15928 15929 15930 15931 15932 15933 15934 15935 15936 15937 15938 15939 15940 15941 15942 15943 15944 15945 15946 15947 15948 15949 15950 15951 15952 15953 15954 15955 15956 15957 15958 15959 15960 15961 15962 15963 15964 15965 15966 15967 15968 15969 15970 15971 15972 15973 15974 15975 15976 15977 15978 15979 15980 15981 15982 15983 15984 15985 15986 15987 15988 15989 15990 15991 15992 15993 15994 15995 15996 15997 15998 15999 16000 16001 16002 16003 16004 16005 16006 16007 16008 16009 16010 16011 16012 16013 16014 16015 16016 16017 16018 16019 16020 16021 16022 16023 16024 16025 16026 16027 16028 16029 16030 16031 16032 16033 16034 16035 16036 16037 16038 16039 16040 16041 16042 16043 16044 16045 16046 16047 16048 16049 16050 16051 16052 16053 16054 16055 16056 16057 16058 16059 16060 16061 16062 16063 16064 16065 16066 16067 16068 16069 16070 16071 16072 16073 16074 16075 16076 16077 16078 16079 16080 16081 16082 16083 16084 16085 16086 16087 16088 16089 16090 16091 16092 16093 16094 16095 16096 16097 16098 16099 16100 16101 16102 16103 16104 16105 16106 16107 16108 16109 16110 16111 16112 16113 16114 16115 16116 16117 16118 16119 16120 16121 16122 16123 16124 16125 16126 16127 16128 16129 16130 16131 16132 16133 16134 16135 16136 16137 16138 16139 16140 16141 16142 16143 16144 16145 16146 16147 16148 16149 16150 16151 16152 16153 16154 16155 16156 16157 16158 16159 16160 16161 16162 16163 16164 16165 16166 16167 16168 16169 16170 16171 16172 16173 16174 16175 16176 16177 16178 16179 16180 16181 16182 16183 16184 16185 16186 16187 16188 16189 16190 16191 16192 16193 16194 16195 16196 16197 16198 16199 16200 16201 16202 16203 16204 16205 16206 16207 16208 16209 16210 16211 16212 16213 16214 16215 16216 16217 16218 16219 16220 16221 16222 16223 16224 16225 16226 16227 16228 16229 16230 16231 16232 16233 16234 16235 16236 16237 16238 16239 16240 16241 16242 16243 16244 16245 16246 16247 16248 16249 16250 16251 16252 16253 16254 16255 16256 16257 16258 16259 16260 16261 16262 16263 16264 16265 16266 16267 16268 16269 16270 16271 16272 16273 16274 16275 16276 16277 16278 16279 16280 16281 16282 16283 16284 16285 16286 16287 16288 16289 16290 16291 16292 16293 16294 16295 16296 16297 16298 16299 16300 16301 16302 16303 16304 16305 16306 16307 16308 16309 16310 16311 16312 16313 16314 16315 16316 16317 16318 16319 16320 16321 16322 16323 16324 16325 16326 16327 16328 16329 16330 16331 16332 16333 16334 16335 16336 16337 16338 16339 16340 16341 16342 16343 16344 16345 16346 16347 16348 16349 16350 16351 16352 16353 16354 16355 16356 16357 16358 16359 16360 16361 16362 16363 16364 16365 16366 16367 16368 16369 16370 16371 16372 16373 16374 16375 16376 16377 16378 16379 16380 16381 16382 16383 16384 16385 16386 16387 16388 16389 16390 16391 16392 16393 16394 16395 16396 16397 16398 16399 16400 16401 16402 16403 16404 16405 16406 16407 16408 16409 16410 16411 16412 16413 16414 16415 16416 16417 16418 16419 16420 16421 16422 16423 16424 16425 16426 16427 16428 16429 16430 16431 16432 16433 16434 16435 16436 16437 16438 16439 16440 16441 16442 16443 16444 16445 16446 16447 16448 16449 16450 16451 16452 16453 16454 16455 16456 16457 16458 16459 16460 16461 16462 16463 16464 16465 16466 16467 16468 16469 16470 16471 16472 16473 16474 16475 16476 16477 16478 16479 16480 16481 16482 16483 16484 16485 16486 16487 16488 16489 16490 16491 16492 16493 16494 16495 16496 16497 16498 16499 16500 16501 16502 16503 16504 16505 16506 16507 16508 16509 16510 16511 16512 16513 16514 16515 16516 16517 16518 16519 16520 16521 16522 16523 16524 16525 16526 16527 16528 16529 16530 16531 16532 16533 16534 16535 16536 16537 16538 16539 16540 16541 16542 16543 16544 16545 16546 16547 16548 16549 16550 16551 16552 16553 16554 16555 16556 16557 16558 16559 16560 16561 16562 16563 16564 16565 16566 16567 16568 16569 16570 16571 16572 16573 16574 16575 16576 16577 16578 16579 16580 16581 16582 16583 16584 16585 16586 16587 16588 16589 16590 16591 16592 16593 16594 16595 16596 16597 16598 16599 16600 16601 16602 16603 16604 16605 16606 16607 16608 16609 16610 16611 16612 16613 16614 16615 16616 16617 16618 16619 16620 16621 16622 16623 16624 16625 16626 16627 16628 16629 16630 16631 16632 16633 16634 16635 16636 16637 16638 16639 16640 16641 16642 16643 16644 16645 16646 16647 16648 16649 16650 16651 16652 16653 16654 16655 16656 16657 16658 16659 16660 16661 16662 16663 16664 16665 16666 16667 16668 16669 16670 16671 16672 16673 16674 16675 16676 16677 16678 16679 16680 16681 16682 16683 16684 16685 16686 16687 16688 16689 16690 16691 16692 16693 16694 16695 16696 16697 16698 16699 16700 16701 16702 16703 16704 16705 16706 16707 16708 16709 16710 16711 16712 16713 16714 16715 16716 16717 16718 16719 16720 16721 16722 16723 16724 16725 16726 16727 16728 16729 16730 16731 16732 16733 16734 16735 16736 16737 16738 16739 16740 16741 16742 16743 16744 16745 16746 16747 16748 16749 16750 16751 16752 16753 16754 16755 16756 16757 16758 16759 16760 16761 16762 16763 16764 16765 16766 16767 16768 16769 16770 16771 16772 16773 16774 16775 16776 16777 16778 16779 16780 16781 16782 16783 16784 16785 16786 16787 16788 16789 16790 16791 16792 16793 16794 16795 16796 16797 16798 16799 16800 16801 16802 16803 16804 16805 16806 16807 16808 16809 16810 16811 16812 16813 16814 16815 16816 16817 16818 16819 16820 16821 16822 16823 16824 16825 16826 16827 16828 16829 16830 16831 16832 16833 16834 16835 16836 16837 16838 16839 16840 16841 16842 16843 16844 16845 16846 16847 16848 16849 16850 16851 16852 16853 16854 16855 16856 16857 16858 16859 16860 16861 16862 16863 16864 16865 16866 16867 16868 16869 16870 16871 16872 16873 16874 16875 16876 16877 16878 16879 16880 16881 16882 16883 16884 16885 16886 16887 16888 16889 16890 16891 16892 16893 16894 16895 16896 16897 16898 16899 16900 16901 16902 16903 16904 16905 16906 16907 16908 16909 16910 16911 16912 16913 16914 16915 16916 16917 16918 16919 16920 16921 16922 16923 16924 16925 16926 16927 16928 16929 16930 16931 16932 16933 16934 16935 16936 16937 16938 16939 16940 16941 16942 16943 16944 16945 16946 16947 16948 16949 16950 16951 16952 16953 16954 16955 16956 16957 16958 16959 16960 16961 16962 16963 16964 16965 16966 16967 16968 16969 16970 16971 16972 16973 16974 16975 16976 16977 16978 16979 16980 16981 16982 16983 16984 16985 16986 16987 16988 16989 16990 16991 16992 16993 16994 16995 16996 16997 16998 16999 17000 17001 17002 17003 17004 17005 17006 17007 17008 17009 17010 17011 17012 17013 17014 17015 17016 17017 17018 17019 17020 17021 17022 17023 17024 17025 17026 17027 17028 17029 17030 17031 17032 17033 17034 17035 17036 17037 17038 17039 17040 17041 17042 17043 17044 17045 17046 17047 17048 17049 17050 17051 17052 17053 17054 17055 17056 17057 17058 17059 17060 17061 17062 17063 17064 17065 17066 17067 17068 17069 17070 17071 17072 17073 17074 17075 17076 17077 17078 17079 17080 17081 17082 17083 17084 17085 17086 17087 17088 17089 17090 17091 17092 17093 17094 17095 17096 17097 17098 17099 17100 17101 17102 17103 17104 17105 17106 17107 17108 17109 17110 17111 17112 17113 17114 17115 17116 17117 17118 17119 17120 17121 17122 17123 17124 17125 17126 17127 17128 17129 17130 17131 17132 17133 17134 17135 17136 17137 17138 17139 17140 17141 17142 17143 17144 17145 17146 17147 17148 17149 17150 17151 17152 17153 17154 17155 17156 17157 17158 17159 17160 17161 17162 17163 17164 17165 17166 17167 17168 17169 17170 17171 17172 17173 17174 17175 17176 17177 17178 17179 17180 17181 17182 17183 17184 17185 17186 17187 17188 17189 17190 17191 17192 17193 17194 17195 17196 17197 17198 17199 17200 17201 17202 17203 17204 17205 17206 17207 17208 17209 17210 17211 17212 17213 17214 17215 17216 17217 17218 17219 17220 17221 17222 17223 17224 17225 17226 17227 17228 17229 17230 17231 17232 17233 17234 17235 17236 17237 17238 17239 17240 17241 17242 17243 17244 17245 17246 17247 17248 17249 17250 17251 17252 17253 17254 17255 17256 17257 17258 17259 17260 17261 17262 17263 17264 17265 17266 17267 17268 17269 17270 17271 17272 17273 17274 17275 17276 17277 17278 17279 17280 17281 17282 17283 17284 17285 17286 17287 17288 17289 17290 17291 17292 17293 17294 17295 17296 17297 17298 17299 17300 17301 17302 17303 17304 17305 17306 17307 17308 17309 17310 17311 17312 17313 17314 17315 17316 17317 17318 17319 17320 17321 17322 17323 17324 17325 17326 17327 17328 17329 17330 17331 17332 17333 17334 17335 17336 17337 17338 17339 17340 17341 17342 17343 17344 17345 17346 17347 17348 17349 17350 17351 17352 17353 17354 17355 17356 17357 17358 17359 17360 17361 17362 17363 17364 17365 17366 17367 17368 17369 17370 17371 17372 17373 17374 17375 17376 17377 17378 17379 17380 17381 17382 17383 17384 17385 17386 17387 17388 17389 17390 17391 17392 17393 17394 17395 17396 17397 17398 17399 17400 17401 17402 17403 17404 17405 17406 17407 17408 17409 17410 17411 17412 17413 17414 17415 17416 17417 17418 17419 17420 17421 17422 17423 17424 17425 17426 17427 17428 17429 17430 17431 17432 17433 17434 17435 17436 17437 17438 17439 17440 17441 17442 17443 17444 17445 17446 17447 17448 17449 17450 17451 17452 17453 17454 17455 17456 17457 17458 17459 17460 17461 17462 17463 17464 17465 17466 17467 17468 17469 17470 17471 17472 17473 17474 17475 17476 17477 17478 17479 17480 17481 17482 17483 17484 17485 17486 17487 17488 17489 17490 17491 17492 17493 17494 17495 17496 17497 17498 17499 17500 17501 17502 17503 17504 17505 17506 17507 17508 17509 17510 17511 17512 17513 17514 17515 17516 17517 17518 17519 17520 17521 17522 17523 17524 17525 17526 17527 17528 17529 17530 17531 17532 17533 17534 17535 17536 17537 17538 17539 17540 17541 17542 17543 17544 17545 17546 17547 17548 17549 17550 17551 17552 17553 17554 17555 17556 17557 17558 17559 17560 17561 17562 17563 17564 17565 17566 17567 17568 17569 17570 17571 17572 17573 17574 17575 17576 17577 17578 17579 17580 17581 17582 17583 17584 17585 17586 17587 17588 17589 17590 17591 17592 17593 17594 17595 17596 17597 17598 17599 17600 17601 17602 17603 17604 17605 17606 17607 17608 17609 17610 17611 17612 17613 17614 17615 17616 17617 17618 17619 17620 17621 17622 17623 17624 17625 17626 17627 17628 17629 17630 17631 17632 17633 17634 17635 17636 17637 17638 17639 17640 17641 17642 17643 17644 17645 17646 17647 17648 17649 17650 17651 17652 17653 17654 17655 17656 17657 17658 17659 17660 17661 17662 17663 17664 17665 17666 17667 17668 17669 17670 17671 17672 17673 17674 17675 17676 17677 17678 17679 17680 17681 17682 17683 17684 17685 17686 17687 17688 17689 17690 17691 17692 17693 17694 17695 17696 17697 17698 17699 17700 17701 17702 17703 17704 17705 17706 17707 17708 17709 17710 17711 17712 17713 17714 17715 17716 17717 17718 17719 17720 17721 17722 17723 17724 17725 17726 17727 17728 17729 17730 17731 17732 17733 17734 17735 17736 17737 17738 17739 17740 17741 17742 17743 17744 17745 17746 17747 17748 17749 17750 17751 17752 17753 17754 17755 17756 17757 17758 17759 17760 17761 17762 17763 17764 17765 17766 17767 17768 17769 17770 17771 17772 17773 17774 17775 17776 17777 17778 17779 17780 17781 17782 17783 17784 17785 17786 17787 17788 17789 17790 17791 17792 17793 17794 17795 17796 17797 17798 17799 17800 17801 17802 17803 17804 17805 17806 17807 17808 17809 17810 17811 17812 17813 17814 17815 17816 17817 17818 17819 17820 17821 17822 17823 17824 17825 17826 17827 17828 17829 17830 17831 17832 17833 17834 17835 17836 17837 17838 17839 17840 17841 17842 17843 17844 17845 17846 17847 17848 17849 17850 17851 17852 17853 17854 17855 17856 17857 17858 17859 17860 17861 17862 17863 17864 17865 17866 17867 17868 17869 17870 17871 17872 17873 17874 17875 17876 17877 17878 17879 17880 17881 17882 17883 17884 17885 17886 17887 17888 17889 17890 17891 17892 17893 17894 17895 17896 17897 17898 17899 17900 17901 17902 17903 17904 17905 17906 17907 17908 17909 17910 17911 17912 17913 17914 17915 17916 17917 17918 17919 17920 17921 17922 17923 17924 17925 17926 17927 17928 17929 17930 17931 17932 17933 17934 17935 17936 17937 17938 17939 17940 17941 17942 17943 17944 17945 17946 17947 17948 17949 17950 17951 17952 17953 17954 17955 17956 17957 17958 17959 17960 17961 17962 17963 17964 17965 17966 17967 17968 17969 17970 17971 17972 17973 17974 17975 17976 17977 17978 17979 17980 17981 17982 17983 17984 17985 17986 17987 17988 17989 17990 17991 17992 17993 17994 17995 17996 17997 17998 17999 18000 18001 18002 18003 18004 18005 18006 18007 18008 18009 18010 18011 18012 18013 18014 18015 18016 18017 18018 18019 18020 18021 18022 18023 18024 18025 18026 18027 18028 18029 18030 18031 18032 18033 18034 18035 18036 18037 18038 18039 18040 18041 18042 18043 18044 18045 18046 18047 18048 18049 18050 18051 18052 18053 18054 18055 18056 18057 18058 18059 18060 18061 18062 18063 18064 18065 18066 18067 18068 18069 18070 18071 18072 18073 18074 18075 18076 18077 18078 18079 18080 18081 18082 18083 18084 18085 18086 18087 18088 18089 18090 18091 18092 18093 18094 18095 18096 18097 18098 18099 18100 18101 18102 18103 18104 18105 18106 18107 18108 18109 18110 18111 18112 18113 18114 18115 18116 18117 18118 18119 18120 18121 18122 18123 18124 18125 18126 18127 18128 18129 18130 18131 18132 18133 18134 18135 18136 18137 18138 18139 18140 18141 18142 18143 18144 18145 18146 18147 18148 18149 18150 18151 18152 18153 18154 18155 18156 18157 18158 18159 18160 18161 18162 18163 18164 18165 18166 18167 18168 18169 18170 18171 18172 18173 18174 18175 18176 18177 18178 18179 18180 18181 18182 18183 18184 18185 18186 18187 18188 18189 18190 18191 18192 18193 18194 18195 18196 18197 18198 18199 18200 18201 18202 18203 18204 18205 18206 18207 18208 18209 18210 18211 18212 18213 18214 18215 18216 18217 18218 18219 18220 18221 18222 18223 18224 18225 18226 18227 18228 18229 18230 18231 18232 18233 18234 18235 18236 18237 18238 18239 18240 18241 18242 18243 18244 18245 18246 18247 18248 18249 18250 18251 18252 18253 18254 18255 18256 18257 18258 18259 18260 18261 18262 18263 18264 18265 18266 18267 18268 18269 18270 18271 18272 18273 18274 18275 18276 18277 18278 18279 18280 18281 18282 18283 18284 18285 18286 18287 18288 18289 18290 18291 18292 18293 18294 18295 18296 18297 18298 18299 18300 18301 18302 18303 18304 18305 18306 18307 18308 18309 18310 18311 18312 18313 18314 18315 18316 18317 18318 18319 18320 18321 18322 18323 18324 18325 18326 18327 18328 18329 18330 18331 18332 18333 18334 18335 18336 18337 18338 18339 18340 18341 18342 18343 18344 18345 18346 18347 18348 18349 18350 18351 18352 18353 18354 18355 18356 18357 18358 18359 18360 18361 18362 18363 18364 18365 18366 18367 18368 18369 18370 18371 18372 18373 18374 18375 18376 18377 18378 18379 18380 18381 18382 18383 18384 18385 18386 18387 18388 18389 18390 18391 18392 18393 18394 18395 18396 18397 18398 18399 18400 18401 18402 18403 18404 18405 18406 18407 18408 18409 18410 18411 18412 18413 18414 18415 18416 18417 18418 18419 18420 18421 18422 18423 18424 18425 18426 18427 18428 18429 18430 18431 18432 18433 18434 18435 18436 18437 18438 18439 18440 18441 18442 18443 18444 18445 18446 18447 18448 18449 18450 18451 18452 18453 18454 18455 18456 18457 18458 18459 18460 18461 18462 18463 18464 18465 18466 18467 18468 18469 18470 18471 18472 18473 18474 18475 18476 18477 18478 18479 18480 18481 18482 18483 18484 18485 18486 18487 18488 18489 18490 18491 18492 18493 18494 18495 18496 18497 18498 18499 18500 18501 18502 18503 18504 18505 18506 18507 18508 18509 18510 18511 18512 18513 18514 18515 18516 18517 18518 18519 18520 18521 18522 18523 18524 18525 18526 18527 18528 18529 18530 18531 18532 18533 18534 18535 18536 18537 18538 18539 18540 18541 18542 18543 18544 18545 18546 18547 18548 18549 18550 18551 18552 18553 18554 18555 18556 18557 18558 18559 18560 18561 18562 18563 18564 18565 18566 18567 18568 18569 18570 18571 18572 18573 18574 18575 18576 18577 18578 18579 18580 18581 18582 18583 18584 18585 18586 18587 18588 18589 18590 18591 18592 18593 18594 18595 18596 18597 18598 18599 18600 18601 18602 18603 18604 18605 18606 18607 18608 18609 18610 18611 18612 18613 18614 18615 18616 18617 18618 18619 18620 18621 18622 18623 18624 18625 18626 18627 18628 18629 18630 18631 18632 18633 18634 18635 18636 18637 18638 18639 18640 18641 18642 18643 18644 18645 18646 18647 18648 18649 18650 18651 18652 18653 18654 18655 18656 18657 18658 18659 18660 18661 18662 18663 18664 18665 18666 18667 18668 18669 18670 18671 18672 18673 18674 18675 18676 18677 18678 18679 18680 18681 18682 18683 18684 18685 18686 18687 18688 18689 18690 18691 18692 18693 18694 18695 18696 18697 18698 18699 18700 18701 18702 18703 18704 18705 18706 18707 18708 18709 18710 18711 18712 18713 18714 18715 18716 18717 18718 18719 18720 18721 18722 18723 18724 18725 18726 18727 18728 18729 18730 18731 18732 18733 18734 18735 18736 18737 18738 18739 18740 18741 18742 18743 18744 18745 18746 18747 18748 18749 18750 18751 18752 18753 18754 18755 18756 18757 18758 18759 18760 18761 18762 18763 18764 18765 18766 18767 18768 18769 18770 18771 18772 18773 18774 18775 18776 18777 18778 18779 18780 18781 18782 18783 18784 18785 18786 18787 18788 18789 18790 18791 18792 18793 18794 18795 18796 18797 18798 18799 18800 18801 18802 18803 18804 18805 18806 18807 18808 18809 18810 18811 18812 18813 18814 18815 18816 18817 18818 18819 18820 18821 18822 18823 18824 18825 18826 18827 18828 18829 18830 18831 18832 18833 18834 18835 18836 18837 18838 18839 18840 18841 18842 18843 18844 18845 18846 18847 18848 18849 18850 18851 18852 18853 18854 18855 18856 18857 18858 18859 18860 18861 18862 18863 18864 18865 18866 18867 18868 18869 18870 18871 18872 18873 18874 18875 18876 18877 18878 18879 18880 18881 18882 18883 18884 18885 18886 18887 18888 18889 18890 18891 18892 18893 18894 18895 18896 18897 18898 18899 18900 18901 18902 18903 18904 18905 18906 18907 18908 18909 18910 18911 18912 18913 18914 18915 18916 18917 18918 18919 18920 18921 18922 18923 18924 18925 18926 18927 18928 18929 18930 18931 18932 18933 18934 18935 18936 18937 18938 18939 18940 18941 18942 18943 18944 18945 18946 18947 18948 18949 18950 18951 18952 18953 18954 18955 18956 18957 18958 18959 18960 18961 18962 18963 18964 18965 18966 18967 18968 18969 18970 18971 18972 18973 18974 18975 18976 18977 18978 18979 18980 18981 18982 18983 18984 18985 18986 18987 18988 18989 18990 18991 18992 18993 18994 18995 18996 18997 18998 18999 19000 19001 19002 19003 19004 19005 19006 19007 19008 19009 19010 19011 19012 19013 19014 19015 19016 19017 19018 19019 19020 19021 19022 19023 19024 19025 19026 19027 19028 19029 19030 19031 19032 19033 19034 19035 19036 19037 19038 19039 19040 19041 19042 19043 19044 19045 19046 19047 19048 19049 19050 19051 19052 19053 19054 19055 19056 19057 19058 19059 19060 19061 19062 19063 19064 19065 19066 19067 19068 19069 19070 19071 19072 19073 19074 19075 19076 19077 19078 19079 19080 19081 19082 19083 19084 19085 19086 19087 19088 19089 19090 19091 19092 19093 19094 19095 19096 19097 19098 19099 19100 19101 19102 19103 19104 19105 19106 19107 19108 19109 19110 19111 19112 19113 19114 19115 19116 19117 19118 19119 19120 19121 19122 19123 19124 19125 19126 19127 19128 19129 19130 19131 19132 19133 19134 19135 19136 19137 19138 19139 19140 19141 19142 19143 19144 19145 19146 19147 19148 19149 19150 19151 19152 19153 19154 19155 19156 19157 19158 19159 19160 19161 19162 19163 19164 19165 19166 19167 19168 19169 19170 19171 19172 19173 19174 19175 19176 19177 19178 19179 19180 19181 19182 19183 19184 19185 19186 19187 19188 19189 19190 19191 19192 19193 19194 19195 19196 19197 19198 19199 19200 19201 19202 19203 19204 19205 19206 19207 19208 19209 19210 19211 19212 19213 19214 19215 19216 19217 19218 19219 19220 19221 19222 19223 19224 19225 19226 19227 19228 19229 19230 19231 19232 19233 19234 19235 19236 19237 19238 19239 19240 19241 19242 19243 19244 19245 19246 19247 19248 19249 19250 19251 19252 19253 19254 19255 19256 19257 19258 19259 19260 19261 19262 19263 19264 19265 19266 19267 19268 19269 19270 19271 19272 19273 19274 19275 19276 19277 19278 19279 19280 19281 19282 19283 19284 19285 19286 19287 19288 19289 19290 19291 19292 19293 19294 19295 19296 19297 19298 19299 19300 19301 19302 19303 19304 19305 19306 19307 19308 19309 19310 19311 19312 19313 19314 19315 19316 19317 19318 19319 19320 19321 19322 19323 19324 19325 19326 19327 19328 19329 19330 19331 19332 19333 19334 19335 19336 19337 19338 19339 19340 19341 19342 19343 19344 19345 19346 19347 19348 19349 19350 19351 19352 19353 19354 19355 19356 19357 19358 19359 19360 19361 19362 19363 19364 19365 19366 19367 19368 19369 19370 19371 19372 19373 19374 19375 19376 19377 19378 19379 19380 19381 19382 19383 19384 19385 19386 19387 19388 19389 19390 19391 19392 19393 19394 19395 19396 19397 19398 19399 19400 19401 19402 19403 19404 19405 19406 19407 19408 19409 19410 19411 19412 19413 19414 19415 19416 19417 19418 19419 19420 19421 19422 19423 19424 19425 19426 19427 19428 19429 19430 19431 19432 19433 19434 19435 19436 19437 19438 19439 19440 19441 19442 19443 19444 19445 19446 19447 19448 19449 19450 19451 19452 19453 19454 19455 19456 19457 19458 19459 19460 19461 19462 19463 19464 19465 19466 19467 19468 19469 19470 19471 19472 19473 19474 19475 19476 19477 19478 19479 19480 19481 19482 19483 19484 19485 19486 19487 19488 19489 19490 19491 19492 19493 19494 19495 19496 19497 19498 19499 19500 19501 19502 19503 19504 19505 19506 19507 19508 19509 19510 19511 19512 19513 19514 19515 19516 19517 19518 19519 19520 19521 19522 19523 19524 19525 19526 19527 19528 19529 19530 19531 19532 19533 19534 19535 19536 19537 19538 19539 19540 19541 19542 19543 19544 19545 19546 19547 19548 19549 19550 19551 19552 19553 19554 19555 19556 19557 19558 19559 19560 19561 19562 19563 19564 19565 19566 19567 19568 19569 19570 19571 19572 19573 19574 19575 19576 19577 19578 19579 19580 19581 19582 19583 19584 19585 19586 19587 19588 19589 19590 19591 19592 19593 19594 19595 19596 19597 19598 19599 19600 19601 19602 19603 19604 19605 19606 19607 19608 19609 19610 19611 19612 19613 19614 19615 19616 19617 19618 19619 19620 19621 19622 19623 19624 19625 19626 19627 19628 19629 19630 19631 19632 19633 19634 19635 19636 19637 19638 19639 19640 19641 19642 19643 19644 19645 19646 19647 19648 19649 19650 19651 19652 19653 19654 19655 19656 19657 19658 19659 19660 19661 19662 19663 19664 19665 19666 19667 19668 19669 19670 19671 19672 19673 19674 19675 19676 19677 19678 19679 19680 19681 19682 19683 19684 19685 19686 19687 19688 19689 19690 19691 19692 19693 19694 19695 19696 19697 19698 19699 19700 19701 19702 19703 19704 19705 19706 19707 19708 19709 19710 19711 19712 19713 19714 19715 19716 19717 19718 19719 19720 19721 19722 19723 19724 19725 19726 19727 19728 19729 19730 19731 19732 19733 19734 19735 19736 19737 19738 19739 19740 19741 19742 19743 19744 19745 19746 19747 19748 19749 19750 19751 19752 19753 19754 19755 19756 19757 19758 19759 19760 19761 19762 19763 19764 19765 19766 19767 19768 19769 19770 19771 19772 19773 19774 19775 19776 19777 19778 19779 19780 19781 19782 19783 19784 19785 19786 19787 19788 19789 19790 19791 19792 19793 19794 19795 19796 19797 19798 19799 19800 19801 19802 19803 19804 19805 19806 19807 19808 19809 19810 19811 19812 19813 19814 19815 19816 19817 19818 19819 19820 19821 19822 19823 19824 19825 19826 19827 19828 19829 19830 19831 19832 19833 19834 19835 19836 19837 19838 19839 19840 19841 19842 19843 19844 19845 19846 19847 19848 19849 19850 19851 19852 19853 19854 19855 19856 19857 19858 19859 19860 19861 19862 19863 19864 19865 19866 19867 19868 19869 19870 19871 19872 19873 19874 19875 19876 19877 19878 19879 19880 19881 19882 19883 19884 19885 19886 19887 19888 19889 19890 19891 19892 19893 19894 19895 19896 19897 19898 19899 19900 19901 19902 19903 19904 19905 19906 19907 19908 19909 19910 19911 19912 19913 19914 19915 19916 19917 19918 19919 19920 19921 19922 19923 19924 19925 19926 19927 19928 19929 19930 19931 19932 19933 19934 19935 19936 19937 19938 19939 19940 19941 19942 19943 19944 19945 19946 19947 19948 19949 19950 19951 19952 19953 19954 19955 19956 19957 19958 19959 19960 19961 19962 19963 19964 19965 19966 19967 19968 19969 19970 19971 19972 19973 19974 19975 19976 19977 19978 19979 19980 19981 19982 19983 19984 19985 19986 19987 19988 19989 19990 19991 19992 19993 19994 19995 19996 19997 19998 19999 20000 20001 20002 20003 20004 20005 20006 20007 20008 20009 20010 20011 20012 20013 20014 20015 20016 20017 20018 20019 20020 20021 20022 20023 20024 20025 20026 20027 20028 20029 20030 20031 20032 20033 20034 20035 20036 20037 20038 20039 20040 20041 20042 20043 20044 20045 20046 20047 20048 20049 20050 20051 20052 20053 20054 20055 20056 20057 20058 20059 20060 20061 20062 20063 20064 20065 20066 20067 20068 20069 20070 20071 20072 20073 20074 20075 20076 20077 20078 20079 20080 20081 20082 20083 20084 20085 20086 20087 20088 20089 20090 20091 20092 20093 20094 20095 20096 20097 20098 20099 20100 20101 20102 20103 20104 20105 20106 20107 20108 20109 20110 20111 20112 20113 20114 20115 20116 20117 20118 20119 20120 20121 20122 20123 20124 20125 20126 20127 20128 20129 20130 20131 20132 20133 20134 20135 20136 20137 20138 20139 20140 20141 20142 20143 20144 20145 20146 20147 20148 20149 20150 20151 20152 20153 20154 20155 20156 20157 20158 20159 20160 20161 20162 20163 20164 20165 20166 20167 20168 20169 20170 20171 20172 20173 20174 20175 20176 20177 20178 20179 20180 20181 20182 20183 20184 20185 20186 20187 20188 20189 20190 20191 20192 20193 20194 20195 20196 20197 20198 20199 20200 20201 20202 20203 20204 20205 20206 20207 20208 20209 20210 20211 20212 20213 20214 20215 20216 20217 20218 20219 20220 20221 20222 20223 20224 20225 20226 20227 20228 20229 20230 20231 20232 20233 20234 20235 20236 20237 20238 20239 20240 20241 20242 20243 20244 20245 20246 20247 20248 20249 20250 20251 20252 20253 20254 20255 20256 20257 20258 20259 20260 20261 20262 20263 20264 20265 20266 20267 20268 20269 20270 20271 20272 20273 20274 20275 20276 20277 20278 20279 20280 20281 20282 20283 20284 20285 20286 20287 20288 20289 20290 20291 20292 20293 20294 20295 20296 20297 20298 20299 20300 20301 20302 20303 20304 20305 20306 20307 20308 20309 20310 20311 20312 20313 20314 20315 20316 20317 20318 20319 20320 20321 20322 20323 20324 20325 20326 20327 20328 20329 20330 20331 20332 20333 20334 20335 20336 20337 20338 20339 20340 20341 20342 20343 20344 20345 20346 20347 20348 20349 20350 20351 20352 20353 20354 20355 20356 20357 20358 20359 20360 20361 20362 20363 20364 20365 20366 20367 20368 20369 20370 20371 20372 20373 20374 20375 20376 20377 20378 20379 20380 20381 20382 20383 20384 20385 20386 20387 20388 20389 20390 20391 20392 20393 20394 20395 20396 20397 20398 20399 20400 20401 20402 20403 20404 20405 20406 20407 20408 20409 20410 20411 20412 20413 20414 20415 20416 20417 20418 20419 20420 20421 20422 20423 20424 20425 20426 20427 20428 20429 20430 20431 20432 20433 20434 20435 20436 20437 20438 20439 20440 20441 20442 20443 20444 20445 20446 20447 20448 20449 20450 20451 20452 20453 20454 20455 20456 20457 20458 20459 20460 20461 20462 20463 20464 20465 20466 20467 20468 20469 20470 20471 20472 20473 20474 20475 20476 20477 20478 20479 20480 20481 20482 20483 20484 20485 20486 20487 20488 20489 20490 20491 20492 20493 20494 20495 20496 20497 20498 20499 20500 20501 20502 20503 20504 20505 20506 20507 20508 20509 20510 20511 20512 20513 20514 20515 20516 20517 20518 20519 20520 20521 20522 20523 20524 20525 20526 20527 20528 20529 20530 20531 20532 20533 20534 20535 20536 20537 20538 20539 20540 20541 20542 20543 20544 20545 20546 20547 20548 20549 20550 20551 20552 20553 20554 20555 20556 20557 20558 20559 20560 20561 20562 20563 20564 20565 20566 20567 20568 20569 20570 20571 20572 20573 20574 20575 20576 20577 20578 20579 20580 20581 20582 20583 20584 20585 20586 20587 20588 20589 20590 20591 20592 20593 20594 20595 20596 20597 20598 20599 20600 20601 20602 20603 20604 20605 20606 20607 20608 20609 20610 20611 20612 20613 20614 20615 20616 20617 20618 20619 20620 20621 20622 20623 20624 20625 20626 20627 20628 20629 20630 20631 20632 20633 20634 20635 20636 20637 20638 20639 20640 20641 20642 20643 20644 20645 20646 20647 20648 20649 20650 20651 20652 20653 20654 20655 20656 20657 20658 20659 20660 20661 20662 20663 20664 20665 20666 20667 20668 20669 20670 20671 20672 20673 20674 20675 20676 20677 20678 20679 20680 20681 20682 20683 20684 20685 20686 20687 20688 20689 20690 20691 20692 20693 20694 20695 20696 20697 20698 20699 20700 20701 20702 20703 20704 20705 20706 20707 20708 20709 20710 20711 20712 20713 20714 20715 20716 20717 20718 20719 20720 20721 20722 20723 20724 20725 20726 20727 20728 20729 20730 20731 20732 20733 20734 20735 20736 20737 20738 20739 20740 20741 20742 20743 20744 20745 20746 20747 20748 20749 20750 20751 20752 20753 20754 20755 20756 20757 20758 20759 20760 20761 20762 20763 20764 20765 20766 20767 20768 20769 20770 20771 20772 20773 20774 20775 20776 20777 20778 20779 20780 20781 20782 20783 20784 20785 20786 20787 20788 20789 20790 20791 20792 20793 20794 20795 20796 20797 20798 20799 20800 20801 20802 20803 20804 20805 20806 20807 20808 20809 20810 20811 20812 20813 20814 20815 20816 20817 20818 20819 20820 20821 20822 20823 20824 20825 20826 20827 20828 20829 20830 20831 20832 20833 20834 20835 20836 20837 20838 20839 20840 20841 20842 20843 20844 20845 20846 20847 20848 20849 20850 20851 20852 20853 20854 20855 20856 20857 20858 20859 20860 20861 20862 20863 20864 20865 20866 20867 20868 20869 20870 20871 20872 20873 20874 20875 20876 20877 20878 20879 20880 20881 20882 20883 20884 20885 20886 20887 20888 20889 20890 20891 20892 20893 20894 20895 20896 20897 20898 20899 20900 20901 20902 20903 20904 20905 20906 20907 20908 20909 20910 20911 20912 20913 20914 20915 20916 20917 20918 20919 20920 20921 20922 20923 20924 20925 20926 20927 20928 20929 20930 20931 20932 20933 20934 20935 20936 20937 20938 20939 20940 20941 20942 20943 20944 20945 20946 20947 20948 20949 20950 20951 20952 20953 20954 20955 20956 20957 20958 20959 20960 20961 20962 20963 20964 20965 20966 20967 20968 20969 20970 20971 20972 20973 20974 20975 20976 20977 20978 20979 20980 20981 20982 20983 20984 20985 20986 20987 20988 20989 20990 20991 20992 20993 20994 20995 20996 20997 20998 20999 21000 21001 21002 21003 21004 21005 21006 21007 21008 21009 21010 21011 21012 21013 21014 21015 21016 21017 21018 21019 21020 21021 21022 21023 21024 21025 21026 21027 21028 21029 21030 21031 21032 21033 21034 21035 21036 21037 21038 21039 21040 21041 21042 21043 21044 21045 21046 21047 21048 21049 21050 21051 21052 21053 21054 21055 21056 21057 21058 21059 21060 21061 21062 21063 21064 21065 21066 21067 21068 21069 21070 21071 21072 21073 21074 21075 21076 21077 21078 21079 21080 21081 21082 21083 21084 21085 21086 21087 21088 21089 21090 21091 21092 21093 21094 21095 21096 21097 21098 21099 21100 21101 21102 21103 21104 21105 21106 21107 21108 21109 21110 21111 21112 21113 21114 21115 21116 21117 21118 21119 21120 21121 21122 21123 21124 21125 21126 21127 21128 21129 21130 21131 21132 21133 21134 21135 21136 21137 21138 21139 21140 21141 21142 21143 21144 21145 21146 21147 21148 21149 21150 21151 21152 21153 21154 21155 21156 21157 21158 21159 21160 21161 21162 21163 21164 21165 21166 21167 21168 21169 21170 21171 21172 21173 21174 21175 21176 21177 21178 21179 21180 21181 21182 21183 21184 21185 21186 21187 21188 21189 21190 21191 21192 21193 21194 21195 21196 21197 21198 21199 21200 21201 21202 21203 21204 21205 21206 21207 21208 21209 21210 21211 21212 21213 21214 21215 21216 21217 21218 21219 21220 21221 21222 21223 21224 21225 21226 21227 21228 21229 21230 21231 21232 21233 21234 21235 21236 21237 21238 21239 21240 21241 21242 21243 21244 21245 21246 21247 21248 21249 21250 21251 21252 21253 21254 21255 21256 21257 21258 21259 21260 21261 21262 21263 21264 21265 21266 21267 21268 21269 21270 21271 21272 21273 21274 21275 21276 21277 21278 21279 21280 21281 21282 21283 21284 21285 21286 21287 21288 21289 21290 21291 21292 21293 21294 21295 21296 21297 21298 21299 21300 21301 21302 21303 21304 21305 21306 21307 21308 21309 21310 21311 21312 21313 21314 21315 21316 21317 21318 21319 21320 21321 21322 21323 21324 21325 21326 21327 21328 21329 21330 21331 21332 21333 21334 21335 21336 21337 21338 21339 21340 21341 21342 21343 21344 21345 21346 21347 21348 21349 21350 21351 21352 21353 21354 21355 21356 21357 21358 21359 21360 21361 21362 21363 21364 21365 21366 21367 21368 21369 21370 21371 21372 21373 21374 21375 21376 21377 21378 21379 21380 21381 21382 21383 21384 21385 21386 21387 21388 21389 21390 21391 21392 21393 21394 21395 21396 21397 21398 21399 21400 21401 21402 21403 21404 21405 21406 21407 21408 21409 21410 21411 21412 21413 21414 21415 21416 21417 21418 21419 21420 21421 21422 21423 21424 21425 21426 21427 21428 21429 21430 21431 21432 21433 21434 21435 21436 21437 21438 21439 21440 21441 21442 21443 21444 21445 21446 21447 21448 21449 21450 21451 21452 21453 21454 21455 21456 21457 21458 21459 21460 21461 21462 21463 21464 21465 21466 21467 21468 21469 21470 21471 21472 21473 21474 21475 21476 21477 21478 21479 21480 21481 21482 21483 21484 21485 21486 21487 21488 21489 21490 21491 21492 21493 21494 21495 21496 21497 21498 21499 21500 21501 21502 21503 21504 21505 21506 21507 21508 21509 21510 21511 21512 21513 21514 21515 21516 21517 21518 21519 21520 21521 21522 21523 21524 21525 21526 21527 21528 21529 21530 21531 21532 21533 21534 21535 21536 21537 21538 21539 21540 21541 21542 21543 21544 21545 21546 21547 21548 21549 21550 21551 21552 21553 21554 21555 21556 21557 21558 21559 21560 21561 21562 21563 21564 21565 21566 21567 21568 21569 21570 21571 21572 21573 21574 21575 21576 21577 21578 21579 21580 21581 21582 21583 21584 21585 21586 21587 21588 21589 21590 21591 21592 21593 21594 21595 21596 21597 21598 21599 21600 21601 21602 21603 21604 21605 21606 21607 21608 21609 21610 21611 21612 21613 21614 21615 21616 21617 21618 21619 21620 21621 21622 21623 21624 21625 21626 21627 21628 21629 21630 21631 21632 21633 21634 21635 21636 21637 21638 21639 21640 21641 21642 21643 21644 21645 21646 21647 21648 21649 21650 21651 21652 21653 21654 21655 21656 21657 21658 21659 21660 21661 21662 21663 21664 21665 21666 21667 21668 21669 21670 21671 21672 21673 21674 21675 21676 21677 21678 21679 21680 21681 21682 21683 21684 21685 21686 21687 21688 21689 21690 21691 21692 21693 21694 21695 21696 21697 21698 21699 21700 21701 21702 21703 21704 21705 21706 21707 21708 21709 21710 21711 21712 21713 21714 21715 21716 21717 21718 21719 21720 21721 21722 21723 21724 21725 21726 21727 21728 21729 21730 21731 21732 21733 21734 21735 21736 21737 21738 21739 21740 21741 21742 21743 21744 21745 21746 21747 21748 21749 21750 21751 21752 21753 21754 21755 21756 21757 21758 21759 21760 21761 21762 21763 21764 21765 21766 21767 21768 21769 21770 21771 21772 21773 21774 21775 21776 21777 21778 21779 21780 21781 21782 21783 21784 21785 21786 21787 21788 21789 21790 21791 21792 21793 21794 21795 21796 21797 21798 21799 21800 21801 21802 21803 21804 21805 21806 21807 21808 21809 21810 21811 21812 21813 21814 21815 21816 21817 21818 21819 21820 21821 21822 21823 21824 21825 21826 21827 21828 21829 21830 21831 21832 21833 21834 21835 21836 21837 21838 21839 21840 21841 21842 21843 21844 21845 21846 21847 21848 21849 21850 21851 21852 21853 21854 21855 21856 21857 21858 21859 21860 21861 21862 21863 21864 21865 21866 21867 21868 21869 21870 21871 21872 21873 21874 21875 21876 21877 21878 21879 21880 21881 21882 21883 21884 21885 21886 21887 21888 21889 21890 21891 21892 21893 21894 21895 21896 21897 21898 21899 21900 21901 21902 21903 21904 21905 21906 21907 21908 21909 21910 21911 21912 21913 21914 21915 21916 21917 21918 21919 21920 21921 21922 21923 21924 21925 21926 21927 21928 21929 21930 21931 21932 21933 21934 21935 21936 21937 21938 21939 21940 21941 21942 21943 21944 21945 21946 21947 21948 21949 21950 21951 21952 21953 21954 21955 21956 21957 21958 21959 21960 21961 21962 21963 21964 21965 21966 21967 21968 21969 21970 21971 21972 21973 21974 21975 21976 21977 21978 21979 21980 21981 21982 21983 21984 21985 21986 21987 21988 21989 21990 21991 21992 21993 21994 21995 21996 21997 21998 21999 22000 22001 22002 22003 22004 22005 22006 22007 22008 22009 22010 22011 22012 22013 22014 22015 22016 22017 22018 22019 22020 22021 22022 22023 22024 22025 22026 22027 22028 22029 22030 22031 22032 22033 22034 22035 22036 22037 22038 22039 22040 22041 22042 22043 22044 22045 22046 22047 22048 22049 22050 22051 22052 22053 22054 22055 22056 22057 22058 22059 22060 22061 22062 22063 22064 22065 22066 22067 22068 22069 22070 22071 22072 22073 22074 22075 22076 22077 22078 22079 22080 22081 22082 22083 22084 22085 22086 22087 22088 22089 22090 22091 22092 22093 22094 22095 22096 22097 22098 22099 22100 22101 22102 22103 22104 22105 22106 22107 22108 22109 22110 22111 22112 22113 22114 22115 22116 22117 22118 22119 22120 22121 22122 22123 22124 22125 22126 22127 22128 22129 22130 22131 22132 22133 22134 22135 22136 22137 22138 22139 22140 22141 22142 22143 22144 22145 22146 22147 22148 22149 22150 22151 22152 22153 22154 22155 22156 22157 22158 22159 22160 22161 22162 22163 22164 22165 22166 22167 22168 22169 22170 22171 22172 22173 22174 22175 22176 22177 22178 22179 22180 22181 22182 22183 22184 22185 22186 22187 22188 22189 22190 22191 22192 22193 22194 22195 22196 22197 22198 22199 22200 22201 22202 22203 22204 22205 22206 22207 22208 22209 22210 22211 22212 22213 22214 22215 22216 22217 22218 22219 22220 22221 22222 22223 22224 22225 22226 22227 22228 22229 22230 22231 22232 22233 22234 22235 22236 22237 22238 22239 22240 22241 22242 22243 22244 22245 22246 22247 22248 22249 22250 22251 22252 22253 22254 22255 22256 22257 22258 22259 22260 22261 22262 22263 22264 22265 22266 22267 22268 22269 22270 22271 22272 22273 22274 22275 22276 22277 22278 22279 22280 22281 22282 22283 22284 22285 22286 22287 22288 22289 22290 22291 22292 22293 22294 22295 22296 22297 22298 22299 22300 22301 22302 22303 22304 22305 22306 22307 22308 22309 22310 22311 22312 22313 22314 22315 22316 22317 22318 22319 22320 22321 22322 22323 22324 22325 22326 22327 22328 22329 22330 22331 22332 22333 22334 22335 22336 22337 22338 22339 22340 22341 22342 22343 22344 22345 22346 22347 22348 22349 22350 22351 22352 22353 22354 22355 22356 22357 22358 22359 22360 22361 22362 22363 22364 22365 22366 22367 22368 22369 22370 22371 22372 22373 22374 22375 22376 22377 22378 22379 22380 22381 22382 22383 22384 22385 22386 22387 22388 22389 22390 22391 22392 22393 22394 22395 22396 22397 22398 22399 22400 22401 22402 22403 22404 22405 22406 22407 22408 22409 22410 22411 22412 22413 22414 22415 22416 22417 22418 22419 22420 22421 22422 22423 22424 22425 22426 22427 22428 22429 22430 22431 22432 22433 22434 22435 22436 22437 22438 22439 22440 22441 22442 22443 22444 22445 22446 22447 22448 22449 22450 22451 22452 22453 22454 22455 22456 22457 22458 22459 22460 22461 22462 22463 22464 22465 22466 22467 22468 22469 22470 22471 22472 22473 22474 22475 22476 22477 22478 22479 22480 22481 22482 22483 22484 22485 22486 22487 22488 22489 22490 22491 22492 22493 22494 22495 22496 22497 22498 22499 22500 22501 22502 22503 22504 22505 22506 22507 22508 22509 22510 22511 22512 22513 22514 22515 22516 22517 22518 22519 22520 22521 22522 22523 22524 22525 22526 22527 22528 22529 22530 22531 22532 22533 22534 22535 22536 22537 22538 22539 22540 22541 22542 22543 22544 22545 22546 22547 22548 22549 22550 22551 22552 22553 22554 22555 22556 22557 22558 22559 22560 22561 22562 22563 22564 22565 22566 22567 22568 22569 22570 22571 22572 22573 22574 22575 22576 22577 22578 22579 22580 22581 22582 22583 22584 22585 22586 22587 22588 22589 22590 22591 22592 22593 22594 22595 22596 22597 22598 22599 22600 22601 22602 22603 22604 22605 22606 22607 22608 22609 22610 22611 22612 22613 22614 22615 22616 22617 22618 22619 22620 22621 22622 22623 22624 22625 22626 22627 22628 22629 22630 22631 22632 22633 22634 22635 22636 22637 22638 22639 22640 22641 22642 22643 22644 22645 22646 22647 22648 22649 22650 22651 22652 22653 22654 22655 22656 22657 22658 22659 22660 22661 22662 22663 22664 22665 22666 22667 22668 22669 22670 22671 22672 22673 22674 22675 22676 22677 22678 22679 22680 22681 22682 22683 22684 22685 22686 22687 22688 22689 22690 22691 22692 22693 22694 22695 22696 22697 22698 22699 22700 22701 22702 22703 22704 22705 22706 22707 22708 22709 22710 22711 22712 22713 22714 22715 22716 22717 22718 22719 22720 22721 22722 22723 22724 22725 22726 22727 22728 22729 22730 22731 22732 22733 22734 22735 22736 22737 22738 22739 22740 22741 22742 22743 22744 22745 22746 22747 22748 22749 22750 22751 22752 22753 22754 22755 22756 22757 22758 22759 22760 22761 22762 22763 22764 22765 22766 22767 22768 22769 22770 22771 22772 22773 22774 22775 22776 22777 22778 22779 22780 22781 22782 22783 22784 22785 22786 22787 22788 22789 22790 22791 22792 22793 22794 22795 22796 22797 22798 22799 22800 22801 22802 22803 22804 22805 22806 22807 22808 22809 22810 22811 22812 22813 22814 22815 22816 22817 22818 22819 22820 22821 22822 22823 22824 22825 22826 22827 22828 22829 22830 22831 22832 22833 22834 22835 22836 22837 22838 22839 22840 22841 22842 22843 22844 22845 22846 22847 22848 22849 22850 22851 22852 22853 22854 22855 22856 22857 22858 22859 22860 22861 22862 22863 22864 22865 22866 22867 22868 22869 22870 22871 22872 22873 22874 22875 22876 22877 22878 22879 22880 22881 22882 22883 22884 22885 22886 22887 22888 22889 22890 22891 22892 22893 22894 22895 22896 22897 22898 22899 22900 22901 22902 22903 22904 22905 22906 22907 22908 22909 22910 22911 22912 22913 22914 22915 22916 22917 22918 22919 22920 22921 22922 22923 22924 22925 22926 22927 22928 22929 22930 22931 22932 22933 22934 22935 22936 22937 22938 22939 22940 22941 22942 22943 22944 22945 22946 22947 22948 22949 22950 22951 22952 22953 22954 22955 22956 22957 22958 22959 22960 22961 22962 22963 22964 22965 22966 22967 22968 22969 22970 22971 22972 22973 22974 22975 22976 22977 22978 22979 22980 22981 22982 22983 22984 22985 22986 22987 22988 22989 22990 22991 22992 22993 22994 22995 22996 22997 22998 22999 23000 23001 23002 23003 23004 23005 23006 23007 23008 23009 23010 23011 23012 23013 23014 23015 23016 23017 23018 23019 23020 23021 23022 23023 23024 23025 23026 23027 23028 23029 23030 23031 23032 23033 23034 23035 23036 23037 23038 23039 23040 23041 23042 23043 23044 23045 23046 23047 23048 23049 23050 23051 23052 23053 23054 23055 23056 23057 23058 23059 23060 23061 23062 23063 23064 23065 23066 23067 23068 23069 23070 23071 23072 23073 23074 23075 23076 23077 23078 23079 23080 23081 23082 23083 23084 23085 23086 23087 23088 23089 23090 23091 23092 23093 23094 23095 23096 23097 23098 23099 23100 23101 23102 23103 23104 23105 23106 23107 23108 23109 23110 23111 23112 23113 23114 23115 23116 23117 23118 23119 23120 23121 23122 23123 23124 23125 23126 23127 23128 23129 23130 23131 23132 23133 23134 23135 23136 23137 23138 23139 23140 23141 23142 23143 23144 23145 23146 23147 23148 23149 23150 23151 23152 23153 23154 23155 23156 23157 23158 23159 23160 23161 23162 23163 23164 23165 23166 23167 23168 23169 23170 23171 23172 23173 23174 23175 23176 23177 23178 23179 23180 23181 23182 23183 23184 23185 23186 23187 23188 23189 23190 23191 23192 23193 23194 23195 23196 23197 23198 23199 23200 23201 23202 23203 23204 23205 23206 23207 23208 23209 23210 23211 23212 23213 23214 23215 23216 23217 23218 23219 23220 23221 23222 23223 23224 23225 23226 23227 23228 23229 23230 23231 23232 23233 23234 23235 23236 23237 23238 23239 23240 23241 23242 23243 23244 23245 23246 23247 23248 23249 23250 23251 23252 23253 23254 23255 23256 23257 23258 23259 23260 23261 23262 23263 23264 23265 23266 23267 23268 23269 23270 23271 23272 23273 23274 23275 23276 23277 23278 23279 23280 23281 23282 23283 23284 23285 23286 23287 23288 23289 23290 23291 23292 23293 23294 23295 23296 23297 23298 23299 23300 23301 23302 23303 23304 23305 23306 23307 23308 23309 23310 23311 23312 23313 23314 23315 23316 23317 23318 23319 23320 23321 23322 23323 23324 23325 23326 23327 23328 23329 23330 23331 23332 23333 23334 23335 23336 23337 23338 23339 23340 23341 23342 23343 23344 23345 23346 23347 23348 23349 23350 23351 23352 23353 23354 23355 23356 23357 23358 23359 23360 23361 23362 23363 23364 23365 23366 23367 23368 23369 23370 23371 23372 23373 23374 23375 23376 23377 23378 23379 23380 23381 23382 23383 23384 23385 23386 23387 23388 23389 23390 23391 23392 23393 23394 23395 23396 23397 23398 23399 23400 23401 23402 23403 23404 23405 23406 23407 23408 23409 23410 23411 23412 23413 23414 23415 23416 23417 23418 23419 23420 23421 23422 23423 23424 23425 23426 23427 23428 23429 23430 23431 23432 23433 23434 23435 23436 23437 23438 23439 23440 23441 23442 23443 23444 23445 23446 23447 23448 23449 23450 23451 23452 23453 23454 23455 23456 23457 23458 23459 23460 23461 23462 23463 23464 23465 23466 23467 23468 23469 23470 23471 23472 23473 23474 23475 23476 23477 23478 23479 23480 23481 23482 23483 23484 23485 23486 23487 23488 23489 23490 23491 23492 23493 23494 23495 23496 23497 23498 23499 23500 23501 23502 23503 23504 23505 23506 23507 23508 23509 23510 23511 23512 23513 23514 23515 23516 23517 23518 23519 23520 23521 23522 23523 23524 23525 23526 23527 23528 23529 23530 23531 23532 23533 23534 23535 23536 23537 23538 23539 23540 23541 23542 23543 23544 23545 23546 23547 23548 23549 23550 23551 23552 23553 23554 23555 23556 23557 23558 23559 23560 23561 23562 23563 23564 23565 23566 23567 23568 23569 23570 23571 23572 23573 23574 23575 23576 23577 23578 23579 23580 23581 23582 23583 23584 23585 23586 23587 23588 23589 23590 23591 23592 23593 23594 23595 23596 23597 23598 23599 23600 23601 23602 23603 23604 23605 23606 23607 23608 23609 23610 23611 23612 23613 23614 23615 23616 23617 23618 23619 23620 23621 23622 23623 23624 23625 23626 23627 23628 23629 23630 23631 23632 23633 23634 23635 23636 23637 23638 23639 23640 23641 23642 23643 23644 23645 23646 23647 23648 23649 23650 23651 23652 23653 23654 23655 23656 23657 23658 23659 23660 23661 23662 23663 23664 23665 23666 23667 23668 23669 23670 23671 23672 23673 23674 23675 23676 23677 23678 23679 23680 23681 23682 23683 23684 23685 23686 23687 23688 23689 23690 23691 23692 23693 23694 23695 23696 23697 23698 23699 23700 23701 23702 23703 23704 23705 23706 23707 23708 23709 23710 23711 23712 23713 23714 23715 23716 23717 23718 23719 23720 23721 23722 23723 23724 23725 23726 23727 23728 23729 23730 23731 23732 23733 23734 23735 23736 23737 23738 23739 23740 23741 23742 23743 23744 23745 23746 23747 23748 23749 23750 23751 23752 23753 23754 23755 23756 23757 23758 23759 23760 23761 23762 23763 23764 23765 23766 23767 23768 23769 23770 23771 23772 23773 23774 23775 23776 23777 23778 23779 23780 23781 23782 23783 23784 23785 23786 23787 23788 23789 23790 23791 23792 23793 23794 23795 23796 23797 23798 23799 23800 23801 23802 23803 23804 23805 23806 23807 23808 23809 23810 23811 23812 23813 23814 23815 23816 23817 23818 23819 23820 23821 23822 23823 23824 23825 23826 23827 23828 23829 23830 23831 23832 23833 23834 23835 23836 23837 23838 23839 23840 23841 23842 23843 23844 23845 23846 23847 23848 23849 23850 23851 23852 23853 23854 23855 23856 23857 23858 23859 23860 23861 23862 23863 23864 23865 23866 23867 23868 23869 23870 23871 23872 23873 23874 23875 23876 23877 23878 23879 23880 23881 23882 23883 23884 23885 23886 23887 23888 23889 23890 23891 23892 23893 23894 23895 23896 23897 23898 23899 23900 23901 23902 23903 23904 23905 23906 23907 23908 23909 23910 23911 23912 23913 23914 23915 23916 23917 23918 23919 23920 23921 23922 23923 23924 23925 23926 23927 23928 23929 23930 23931 23932 23933 23934 23935 23936 23937 23938 23939 23940 23941 23942 23943 23944 23945 23946 23947 23948 23949 23950 23951 23952 23953 23954 23955 23956 23957 23958 23959 23960 23961 23962 23963 23964 23965 23966 23967 23968 23969 23970 23971 23972 23973 23974 23975 23976 23977 23978 23979 23980 23981 23982 23983 23984 23985 23986 23987 23988 23989 23990 23991 23992 23993 23994 23995 23996 23997 23998 23999 24000 24001 24002 24003 24004 24005 24006 24007 24008 24009 24010 24011 24012 24013 24014 24015 24016 24017 24018 24019 24020 24021 24022 24023 24024 24025 24026 24027 24028 24029 24030 24031 24032 24033 24034 24035 24036 24037 24038 24039 24040 24041 24042 24043 24044 24045 24046 24047 24048 24049 24050 24051 24052 24053 24054 24055 24056 24057 24058 24059 24060 24061 24062 24063 24064 24065 24066 24067 24068 24069 24070 24071 24072 24073 24074 24075 24076 24077 24078 24079 24080 24081 24082 24083 24084 24085 24086 24087 24088 24089 24090 24091 24092 24093 24094 24095 24096 24097 24098 24099 24100 24101 24102 24103 24104 24105 24106 24107 24108 24109 24110 24111 24112 24113 24114 24115 24116 24117 24118 24119 24120 24121 24122 24123 24124 24125 24126 24127 24128 24129 24130 24131 24132 24133 24134 24135 24136 24137 24138 24139 24140 24141 24142 24143 24144 24145 24146 24147 24148 24149 24150 24151 24152 24153 24154 24155 24156 24157 24158 24159 24160 24161 24162 24163 24164 24165 24166 24167 24168 24169 24170 24171 24172 24173 24174 24175 24176 24177 24178 24179 24180 24181 24182 24183 24184 24185 24186 24187 24188 24189 24190 24191 24192 24193 24194 24195 24196 24197 24198 24199 24200 24201 24202 24203 24204 24205 24206 24207 24208 24209 24210 24211 24212 24213 24214 24215 24216 24217 24218 24219 24220 24221 24222 24223 24224 24225 24226 24227 24228 24229 24230 24231 24232 24233 24234 24235 24236 24237 24238 24239 24240 24241 24242 24243 24244 24245 24246 24247 24248 24249 24250 24251 24252 24253 24254 24255 24256 24257 24258 24259 24260 24261 24262 24263 24264 24265 24266 24267 24268 24269 24270 24271 24272 24273 24274 24275 24276 24277 24278 24279 24280 24281 24282 24283 24284 24285 24286 24287 24288 24289 24290 24291 24292 24293 24294 24295 24296 24297 24298 24299 24300 24301 24302 24303 24304 24305 24306 24307 24308 24309 24310 24311 24312 24313 24314 24315 24316 24317 24318 24319 24320 24321 24322 24323 24324 24325 24326 24327 24328 24329 24330 24331 24332 24333 24334 24335 24336 24337 24338 24339 24340 24341 24342 24343 24344 24345 24346 24347 24348 24349 24350 24351 24352 24353 24354 24355 24356 24357 24358 24359 24360 24361 24362 24363 24364 24365 24366 24367 24368 24369 24370 24371 24372 24373 24374 24375 24376 24377 24378 24379 24380 24381 24382 24383 24384 24385 24386 24387 24388 24389 24390 24391 24392 24393 24394 24395 24396 24397 24398 24399 24400 24401 24402 24403 24404 24405 24406 24407 24408 24409 24410 24411 24412 24413 24414 24415 24416 24417 24418 24419 24420 24421 24422 24423 24424 24425 24426 24427 24428 24429 24430 24431 24432 24433 24434 24435 24436 24437 24438 24439 24440 24441 24442 24443 24444 24445 24446 24447 24448 24449 24450 24451 24452 24453 24454 24455 24456 24457 24458 24459 24460 24461 24462 24463 24464 24465 24466 24467 24468 24469 24470 24471 24472 24473 24474 24475 24476 24477 24478 24479 24480 
<?php
//============================================================+
// File name   : tcpdf.php
// Version     : 6.2.12
// Begin       : 2002-08-03
// Last Update : 2015-06-18
// Author      : Nicola Asuni - Tecnick.com LTD - www.tecnick.com - info@tecnick.com
// License     : GNU-LGPL v3 (http://www.gnu.org/copyleft/lesser.html)
// -------------------------------------------------------------------
// Copyright (C) 2002-2015 Nicola Asuni - Tecnick.com LTD
//
// This file is part of TCPDF software library.
//
// TCPDF is free software: you can redistribute it and/or modify it
// under the terms of the GNU Lesser General Public License as
// published by the Free Software Foundation, either version 3 of the
// License, or (at your option) any later version.
//
// TCPDF is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
// See the GNU Lesser General Public License for more details.
//
// You should have received a copy of the License
// along with TCPDF. If not, see
// <http://www.tecnick.com/pagefiles/tcpdf/LICENSE.TXT>.
//
// See LICENSE.TXT file for more information.
// -------------------------------------------------------------------
//
// Description :
//   This is a PHP class for generating PDF documents without requiring external extensions.
//
// NOTE:
//   This class was originally derived in 2002 from the Public
//   Domain FPDF class by Olivier Plathey (http://www.fpdf.org),
//   but now is almost entirely rewritten and contains thousands of
//   new lines of code and hundreds new features.
//
// Main features:
//  * no external libraries are required for the basic functions;
//  * all standard page formats, custom page formats, custom margins and units of measure;
//  * UTF-8 Unicode and Right-To-Left languages;
//  * TrueTypeUnicode, TrueType, Type1 and CID-0 fonts;
//  * font subsetting;
//  * methods to publish some XHTML + CSS code, Javascript and Forms;
//  * images, graphic (geometric figures) and transformation methods;
//  * supports JPEG, PNG and SVG images natively, all images supported by GD (GD, GD2, GD2PART, GIF, JPEG, PNG, BMP, XBM, XPM) and all images supported via ImagMagick (http://www.imagemagick.org/www/formats.html)
//  * 1D and 2D barcodes: CODE 39, ANSI MH10.8M-1983, USD-3, 3 of 9, CODE 93, USS-93, Standard 2 of 5, Interleaved 2 of 5, CODE 128 A/B/C, 2 and 5 Digits UPC-Based Extension, EAN 8, EAN 13, UPC-A, UPC-E, MSI, POSTNET, PLANET, RMS4CC (Royal Mail 4-state Customer Code), CBC (Customer Bar Code), KIX (Klant index - Customer index), Intelligent Mail Barcode, Onecode, USPS-B-3200, CODABAR, CODE 11, PHARMACODE, PHARMACODE TWO-TRACKS, Datamatrix, QR-Code, PDF417;
//  * JPEG and PNG ICC profiles, Grayscale, RGB, CMYK, Spot Colors and Transparencies;
//  * automatic page header and footer management;
//  * document encryption up to 256 bit and digital signature certifications;
//  * transactions to UNDO commands;
//  * PDF annotations, including links, text and file attachments;
//  * text rendering modes (fill, stroke and clipping);
//  * multiple columns mode;
//  * no-write page regions;
//  * bookmarks, named destinations and table of content;
//  * text hyphenation;
//  * text stretching and spacing (tracking);
//  * automatic page break, line break and text alignments including justification;
//  * automatic page numbering and page groups;
//  * move and delete pages;
//  * page compression (requires php-zlib extension);
//  * XOBject Templates;
//  * Layers and object visibility.
//  * PDF/A-1b support
//============================================================+

/**
 * @file
 * This is a PHP class for generating PDF documents without requiring external extensions.<br>
 * TCPDF project (http://www.tcpdf.org) was originally derived in 2002 from the Public Domain FPDF class by Olivier Plathey (http://www.fpdf.org), but now is almost entirely rewritten.<br>
 * <h3>TCPDF main features are:</h3>
 * <ul>
 * <li>no external libraries are required for the basic functions;</li>
 * <li>all standard page formats, custom page formats, custom margins and units of measure;</li>
 * <li>UTF-8 Unicode and Right-To-Left languages;</li>
 * <li>TrueTypeUnicode, TrueType, Type1 and CID-0 fonts;</li>
 * <li>font subsetting;</li>
 * <li>methods to publish some XHTML + CSS code, Javascript and Forms;</li>
 * <li>images, graphic (geometric figures) and transformation methods;
 * <li>supports JPEG, PNG and SVG images natively, all images supported by GD (GD, GD2, GD2PART, GIF, JPEG, PNG, BMP, XBM, XPM) and all images supported via ImagMagick (http://www.imagemagick.org/www/formats.html)</li>
 * <li>1D and 2D barcodes: CODE 39, ANSI MH10.8M-1983, USD-3, 3 of 9, CODE 93, USS-93, Standard 2 of 5, Interleaved 2 of 5, CODE 128 A/B/C, 2 and 5 Digits UPC-Based Extension, EAN 8, EAN 13, UPC-A, UPC-E, MSI, POSTNET, PLANET, RMS4CC (Royal Mail 4-state Customer Code), CBC (Customer Bar Code), KIX (Klant index - Customer index), Intelligent Mail Barcode, Onecode, USPS-B-3200, CODABAR, CODE 11, PHARMACODE, PHARMACODE TWO-TRACKS, Datamatrix, QR-Code, PDF417;</li>
 * <li>JPEG and PNG ICC profiles, Grayscale, RGB, CMYK, Spot Colors and Transparencies;</li>
 * <li>automatic page header and footer management;</li>
 * <li>document encryption up to 256 bit and digital signature certifications;</li>
 * <li>transactions to UNDO commands;</li>
 * <li>PDF annotations, including links, text and file attachments;</li>
 * <li>text rendering modes (fill, stroke and clipping);</li>
 * <li>multiple columns mode;</li>
 * <li>no-write page regions;</li>
 * <li>bookmarks, named destinations and table of content;</li>
 * <li>text hyphenation;</li>
 * <li>text stretching and spacing (tracking);</li>
 * <li>automatic page break, line break and text alignments including justification;</li>
 * <li>automatic page numbering and page groups;</li>
 * <li>move and delete pages;</li>
 * <li>page compression (requires php-zlib extension);</li>
 * <li>XOBject Templates;</li>
 * <li>Layers and object visibility;</li>
 * <li>PDF/A-1b support.</li>
 * </ul>
 * Tools to encode your unicode fonts are on fonts/utils directory.</p>
 * @package com.tecnick.tcpdf
 * @author Nicola Asuni
 * @version 6.2.8
 */

// TCPDF configuration
require_once(dirname(__FILE__).'/tcpdf_autoconfig.php');
// TCPDF static font methods and data
require_once(dirname(__FILE__).'/include/tcpdf_font_data.php');
// TCPDF static font methods and data
require_once(dirname(__FILE__).'/include/tcpdf_fonts.php');
// TCPDF static color methods and data
require_once(dirname(__FILE__).'/include/tcpdf_colors.php');
// TCPDF static image methods and data
require_once(dirname(__FILE__).'/include/tcpdf_images.php');
// TCPDF static methods and data
require_once(dirname(__FILE__).'/include/tcpdf_static.php');

// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

/**
 * @class TCPDF
 * PHP class for generating PDF documents without requiring external extensions.
 * TCPDF project (http://www.tcpdf.org) has been originally derived in 2002 from the Public Domain FPDF class by Olivier Plathey (http://www.fpdf.org), but now is almost entirely rewritten.<br>
 * @package com.tecnick.tcpdf
 * @brief PHP class for generating PDF documents without requiring external extensions.
 * @version 6.2.8
 * @author Nicola Asuni - info@tecnick.com
 */
class TCPDF {

    // Protected properties

    /**
     * Current page number.
     * @protected
     */
    protected $page;

    /**
     * Current object number.
     * @protected
     */
    protected $n;

    /**
     * Array of object offsets.
     * @protected
     */
    protected $offsets = array();

    /**
     * Array of object IDs for each page.
     * @protected
     */
    protected $pageobjects = array();

    /**
     * Buffer holding in-memory PDF.
     * @protected
     */
    protected $buffer;

    /**
     * Array containing pages.
     * @protected
     */
    protected $pages = array();

    /**
     * Current document state.
     * @protected
     */
    protected $state;

    /**
     * Compression flag.
     * @protected
     */
    protected $compress;

    /**
     * Current page orientation (P = Portrait, L = Landscape).
     * @protected
     */
    protected $CurOrientation;

    /**
     * Page dimensions.
     * @protected
     */
    protected $pagedim = array();

    /**
     * Scale factor (number of points in user unit).
     * @protected
     */
    protected $k;

    /**
     * Width of page format in points.
     * @protected
     */
    protected $fwPt;

    /**
     * Height of page format in points.
     * @protected
     */
    protected $fhPt;

    /**
     * Current width of page in points.
     * @protected
     */
    protected $wPt;

    /**
     * Current height of page in points.
     * @protected
     */
    protected $hPt;

    /**
     * Current width of page in user unit.
     * @protected
     */
    protected $w;

    /**
     * Current height of page in user unit.
     * @protected
     */
    protected $h;

    /**
     * Left margin.
     * @protected
     */
    protected $lMargin;

    /**
     * Right margin.
     * @protected
     */
    protected $rMargin;

    /**
     * Cell left margin (used by regions).
     * @protected
     */
    protected $clMargin;

    /**
     * Cell right margin (used by regions).
     * @protected
     */
    protected $crMargin;

    /**
     * Top margin.
     * @protected
     */
    protected $tMargin;

    /**
     * Page break margin.
     * @protected
     */
    protected $bMargin;

    /**
     * Array of cell internal paddings ('T' => top, 'R' => right, 'B' => bottom, 'L' => left).
     * @since 5.9.000 (2010-10-03)
     * @protected
     */
    protected $cell_padding = array('T' => 0, 'R' => 0, 'B' => 0, 'L' => 0);

    /**
     * Array of cell margins ('T' => top, 'R' => right, 'B' => bottom, 'L' => left).
     * @since 5.9.000 (2010-10-04)
     * @protected
     */
    protected $cell_margin = array('T' => 0, 'R' => 0, 'B' => 0, 'L' => 0);

    /**
     * Current horizontal position in user unit for cell positioning.
     * @protected
     */
    protected $x;

    /**
     * Current vertical position in user unit for cell positioning.
     * @protected
     */
    protected $y;

    /**
     * Height of last cell printed.
     * @protected
     */
    protected $lasth;

    /**
     * Line width in user unit.
     * @protected
     */
    protected $LineWidth;

    /**
     * Array of standard font names.
     * @protected
     */
    protected $CoreFonts;

    /**
     * Array of used fonts.
     * @protected
     */
    protected $fonts = array();

    /**
     * Array of font files.
     * @protected
     */
    protected $FontFiles = array();

    /**
     * Array of encoding differences.
     * @protected
     */
    protected $diffs = array();

    /**
     * Array of used images.
     * @protected
     */
    protected $images = array();

    /**
     * Depth of the svg tag, to keep track if the svg tag is a subtag or the root tag.
     * @protected
     */
    protected $svg_tag_depth = 0;

    /**
     * Array of Annotations in pages.
     * @protected
     */
    protected $PageAnnots = array();

    /**
     * Array of internal links.
     * @protected
     */
    protected $links = array();

    /**
     * Current font family.
     * @protected
     */
    protected $FontFamily;

    /**
     * Current font style.
     * @protected
     */
    protected $FontStyle;

    /**
     * Current font ascent (distance between font top and baseline).
     * @protected
     * @since 2.8.000 (2007-03-29)
     */
    protected $FontAscent;

    /**
     * Current font descent (distance between font bottom and baseline).
     * @protected
     * @since 2.8.000 (2007-03-29)
     */
    protected $FontDescent;

    /**
     * Underlining flag.
     * @protected
     */
    protected $underline;

    /**
     * Overlining flag.
     * @protected
     */
    protected $overline;

    /**
     * Current font info.
     * @protected
     */
    protected $CurrentFont;

    /**
     * Current font size in points.
     * @protected
     */
    protected $FontSizePt;

    /**
     * Current font size in user unit.
     * @protected
     */
    protected $FontSize;

    /**
     * Commands for drawing color.
     * @protected
     */
    protected $DrawColor;

    /**
     * Commands for filling color.
     * @protected
     */
    protected $FillColor;

    /**
     * Commands for text color.
     * @protected
     */
    protected $TextColor;

    /**
     * Indicates whether fill and text colors are different.
     * @protected
     */
    protected $ColorFlag;

    /**
     * Automatic page breaking.
     * @protected
     */
    protected $AutoPageBreak;

    /**
     * Threshold used to trigger page breaks.
     * @protected
     */
    protected $PageBreakTrigger;

    /**
     * Flag set when processing page header.
     * @protected
     */
    protected $InHeader = false;

    /**
     * Flag set when processing page footer.
     * @protected
     */
    protected $InFooter = false;

    /**
     * Zoom display mode.
     * @protected
     */
    protected $ZoomMode;

    /**
     * Layout display mode.
     * @protected
     */
    protected $LayoutMode;

    /**
     * If true set the document information dictionary in Unicode.
     * @protected
     */
    protected $docinfounicode = true;

    /**
     * Document title.
     * @protected
     */
    protected $title = '';

    /**
     * Document subject.
     * @protected
     */
    protected $subject = '';

    /**
     * Document author.
     * @protected
     */
    protected $author = '';

    /**
     * Document keywords.
     * @protected
     */
    protected $keywords = '';

    /**
     * Document creator.
     * @protected
     */
    protected $creator = '';

    /**
     * Starting page number.
     * @protected
     */
    protected $starting_page_number = 1;

    /**
     * The right-bottom (or left-bottom for RTL) corner X coordinate of last inserted image.
     * @since 2002-07-31
     * @author Nicola Asuni
     * @protected
     */
    protected $img_rb_x;

    /**
     * The right-bottom corner Y coordinate of last inserted image.
     * @since 2002-07-31
     * @author Nicola Asuni
     * @protected
     */
    protected $img_rb_y;

    /**
     * Adjusting factor to convert pixels to user units.
     * @since 2004-06-14
     * @author Nicola Asuni
     * @protected
     */
    protected $imgscale = 1;

    /**
     * Boolean flag set to true when the input text is unicode (require unicode fonts).
     * @since 2005-01-02
     * @author Nicola Asuni
     * @protected
     */
    protected $isunicode = false;

    /**
     * PDF version.
     * @since 1.5.3
     * @protected
     */
    protected $PDFVersion = '1.7';

    /**
     * ID of the stored default header template (-1 = not set).
     * @protected
     */
    protected $header_xobjid = false;

    /**
     * If true reset the Header Xobject template at each page
     * @protected
     */
    protected $header_xobj_autoreset = false;

    /**
     * Minimum distance between header and top page margin.
     * @protected
     */
    protected $header_margin;

    /**
     * Minimum distance between footer and bottom page margin.
     * @protected
     */
    protected $footer_margin;

    /**
     * Original left margin value.
     * @protected
     * @since 1.53.0.TC013
     */
    protected $original_lMargin;

    /**
     * Original right margin value.
     * @protected
     * @since 1.53.0.TC013
     */
    protected $original_rMargin;

    /**
     * Default font used on page header.
     * @protected
     */
    protected $header_font;

    /**
     * Default font used on page footer.
     * @protected
     */
    protected $footer_font;

    /**
     * Language templates.
     * @protected
     */
    protected $l;

    /**
     * Barcode to print on page footer (only if set).
     * @protected
     */
    protected $barcode = false;

    /**
     * Boolean flag to print/hide page header.
     * @protected
     */
    protected $print_header = true;

    /**
     * Boolean flag to print/hide page footer.
     * @protected
     */
    protected $print_footer = true;

    /**
     * Header image logo.
     * @protected
     */
    protected $header_logo = '';

    /**
     * Width of header image logo in user units.
     * @protected
     */
    protected $header_logo_width = 30;

    /**
     * Title to be printed on default page header.
     * @protected
     */
    protected $header_title = '';

    /**
     * String to pring on page header after title.
     * @protected
     */
    protected $header_string = '';

    /**
     * Color for header text (RGB array).
     * @since 5.9.174 (2012-07-25)
     * @protected
     */
    protected $header_text_color = array(0,0,0);

    /**
     * Color for header line (RGB array).
     * @since 5.9.174 (2012-07-25)
     * @protected
     */
    protected $header_line_color = array(0,0,0);

    /**
     * Color for footer text (RGB array).
     * @since 5.9.174 (2012-07-25)
     * @protected
     */
    protected $footer_text_color = array(0,0,0);

    /**
     * Color for footer line (RGB array).
     * @since 5.9.174 (2012-07-25)
     * @protected
     */
    protected $footer_line_color = array(0,0,0);

    /**
     * Text shadow data array.
     * @since 5.9.174 (2012-07-25)
     * @protected
     */
    protected $txtshadow = array('enabled'=>false, 'depth_w'=>0, 'depth_h'=>0, 'color'=>false, 'opacity'=>1, 'blend_mode'=>'Normal');

    /**
     * Default number of columns for html table.
     * @protected
     */
    protected $default_table_columns = 4;

    // variables for html parser

    /**
     * HTML PARSER: array to store current link and rendering styles.
     * @protected
     */
    protected $HREF = array();

    /**
     * List of available fonts on filesystem.
     * @protected
     */
    protected $fontlist = array();

    /**
     * Current foreground color.
     * @protected
     */
    protected $fgcolor;

    /**
     * HTML PARSER: array of boolean values, true in case of ordered list (OL), false otherwise.
     * @protected
     */
    protected $listordered = array();

    /**
     * HTML PARSER: array count list items on nested lists.
     * @protected
     */
    protected $listcount = array();

    /**
     * HTML PARSER: current list nesting level.
     * @protected
     */
    protected $listnum = 0;

    /**
     * HTML PARSER: indent amount for lists.
     * @protected
     */
    protected $listindent = 0;

    /**
     * HTML PARSER: current list indententation level.
     * @protected
     */
    protected $listindentlevel = 0;

    /**
     * Current background color.
     * @protected
     */
    protected $bgcolor;

    /**
     * Temporary font size in points.
     * @protected
     */
    protected $tempfontsize = 10;

    /**
     * Spacer string for LI tags.
     * @protected
     */
    protected $lispacer = '';

    /**
     * Default encoding.
     * @protected
     * @since 1.53.0.TC010
     */
    protected $encoding = 'UTF-8';

    /**
     * PHP internal encoding.
     * @protected
     * @since 1.53.0.TC016
     */
    protected $internal_encoding;

    /**
     * Boolean flag to indicate if the document language is Right-To-Left.
     * @protected
     * @since 2.0.000
     */
    protected $rtl = false;

    /**
     * Boolean flag used to force RTL or LTR string direction.
     * @protected
     * @since 2.0.000
     */
    protected $tmprtl = false;

    // --- Variables used for document encryption:

    /**
     * IBoolean flag indicating whether document is protected.
     * @protected
     * @since 2.0.000 (2008-01-02)
     */
    protected $encrypted;

    /**
     * Array containing encryption settings.
     * @protected
     * @since 5.0.005 (2010-05-11)
     */
    protected $encryptdata = array();

    /**
     * Last RC4 key encrypted (cached for optimisation).
     * @protected
     * @since 2.0.000 (2008-01-02)
     */
    protected $last_enc_key;

    /**
     * Last RC4 computed key.
     * @protected
     * @since 2.0.000 (2008-01-02)
     */
    protected $last_enc_key_c;

    /**
     * File ID (used on document trailer).
     * @protected
     * @since 5.0.005 (2010-05-12)
     */
    protected $file_id;

    // --- bookmark ---

    /**
     * Outlines for bookmark.
     * @protected
     * @since 2.1.002 (2008-02-12)
     */
    protected $outlines = array();

    /**
     * Outline root for bookmark.
     * @protected
     * @since 2.1.002 (2008-02-12)
     */
    protected $OutlineRoot;

    // --- javascript and form ---

    /**
     * Javascript code.
     * @protected
     * @since 2.1.002 (2008-02-12)
     */
    protected $javascript = '';

    /**
     * Javascript counter.
     * @protected
     * @since 2.1.002 (2008-02-12)
     */
    protected $n_js;

    /**
     * line through state
     * @protected
     * @since 2.8.000 (2008-03-19)
     */
    protected $linethrough;

    /**
     * Array with additional document-wide usage rights for the document.
     * @protected
     * @since 5.8.014 (2010-08-23)
     */
    protected $ur = array();

    /**
     * DPI (Dot Per Inch) Document Resolution (do not change).
     * @protected
     * @since 3.0.000 (2008-03-27)
     */
    protected $dpi = 72;

    /**
     * Array of page numbers were a new page group was started (the page numbers are the keys of the array).
     * @protected
     * @since 3.0.000 (2008-03-27)
     */
    protected $newpagegroup = array();

    /**
     * Array that contains the number of pages in each page group.
     * @protected
     * @since 3.0.000 (2008-03-27)
     */
    protected $pagegroups = array();

    /**
     * Current page group number.
     * @protected
     * @since 3.0.000 (2008-03-27)
     */
    protected $currpagegroup = 0;

    /**
     * Array of transparency objects and parameters.
     * @protected
     * @since 3.0.000 (2008-03-27)
     */
    protected $extgstates;

    /**
     * Set the default JPEG compression quality (1-100).
     * @protected
     * @since 3.0.000 (2008-03-27)
     */
    protected $jpeg_quality;

    /**
     * Default cell height ratio.
     * @protected
     * @since 3.0.014 (2008-05-23)
     */
    protected $cell_height_ratio = K_CELL_HEIGHT_RATIO;

    /**
     * PDF viewer preferences.
     * @protected
     * @since 3.1.000 (2008-06-09)
     */
    protected $viewer_preferences;

    /**
     * A name object specifying how the document should be displayed when opened.
     * @protected
     * @since 3.1.000 (2008-06-09)
     */
    protected $PageMode;

    /**
     * Array for storing gradient information.
     * @protected
     * @since 3.1.000 (2008-06-09)
     */
    protected $gradients = array();

    /**
     * Array used to store positions inside the pages buffer (keys are the page numbers).
     * @protected
     * @since 3.2.000 (2008-06-26)
     */
    protected $intmrk = array();

    /**
     * Array used to store positions inside the pages buffer (keys are the page numbers).
     * @protected
     * @since 5.7.000 (2010-08-03)
     */
    protected $bordermrk = array();

    /**
     * Array used to store page positions to track empty pages (keys are the page numbers).
     * @protected
     * @since 5.8.007 (2010-08-18)
     */
    protected $emptypagemrk = array();

    /**
     * Array used to store content positions inside the pages buffer (keys are the page numbers).
     * @protected
     * @since 4.6.021 (2009-07-20)
     */
    protected $cntmrk = array();

    /**
     * Array used to store footer positions of each page.
     * @protected
     * @since 3.2.000 (2008-07-01)
     */
    protected $footerpos = array();

    /**
     * Array used to store footer length of each page.
     * @protected
     * @since 4.0.014 (2008-07-29)
     */
    protected $footerlen = array();

    /**
     * Boolean flag to indicate if a new line is created.
     * @protected
     * @since 3.2.000 (2008-07-01)
     */
    protected $newline = true;

    /**
     * End position of the latest inserted line.
     * @protected
     * @since 3.2.000 (2008-07-01)
     */
    protected $endlinex = 0;

    /**
     * PDF string for width value of the last line.
     * @protected
     * @since 4.0.006 (2008-07-16)
     */
    protected $linestyleWidth = '';

    /**
     * PDF string for CAP value of the last line.
     * @protected
     * @since 4.0.006 (2008-07-16)
     */
    protected $linestyleCap = '0 J';

    /**
     * PDF string for join value of the last line.
     * @protected
     * @since 4.0.006 (2008-07-16)
     */
    protected $linestyleJoin = '0 j';

    /**
     * PDF string for dash value of the last line.
     * @protected
     * @since 4.0.006 (2008-07-16)
     */
    protected $linestyleDash = '[] 0 d';

    /**
     * Boolean flag to indicate if marked-content sequence is open.
     * @protected
     * @since 4.0.013 (2008-07-28)
     */
    protected $openMarkedContent = false;

    /**
     * Count the latest inserted vertical spaces on HTML.
     * @protected
     * @since 4.0.021 (2008-08-24)
     */
    protected $htmlvspace = 0;

    /**
     * Array of Spot colors.
     * @protected
     * @since 4.0.024 (2008-09-12)
     */
    protected $spot_colors = array();

    /**
     * Symbol used for HTML unordered list items.
     * @protected
     * @since 4.0.028 (2008-09-26)
     */
    protected $lisymbol = '';

    /**
     * String used to mark the beginning and end of EPS image blocks.
     * @protected
     * @since 4.1.000 (2008-10-18)
     */
    protected $epsmarker = 'x#!#EPS#!#x';

    /**
     * Array of transformation matrix.
     * @protected
     * @since 4.2.000 (2008-10-29)
     */
    protected $transfmatrix = array();

    /**
     * Current key for transformation matrix.
     * @protected
     * @since 4.8.005 (2009-09-17)
     */
    protected $transfmatrix_key = 0;

    /**
     * Booklet mode for double-sided pages.
     * @protected
     * @since 4.2.000 (2008-10-29)
     */
    protected $booklet = false;

    /**
     * Epsilon value used for float calculations.
     * @protected
     * @since 4.2.000 (2008-10-29)
     */
    protected $feps = 0.005;

    /**
     * Array used for custom vertical spaces for HTML tags.
     * @protected
     * @since 4.2.001 (2008-10-30)
     */
    protected $tagvspaces = array();

    /**
     * HTML PARSER: custom indent amount for lists. Negative value means disabled.
     * @protected
     * @since 4.2.007 (2008-11-12)
     */
    protected $customlistindent = -1;

    /**
     * Boolean flag to indicate if the border of the cell sides that cross the page should be removed.
     * @protected
     * @since 4.2.010 (2008-11-14)
     */
    protected $opencell = true;

    /**
     * Array of files to embedd.
     * @protected
     * @since 4.4.000 (2008-12-07)
     */
    protected $embeddedfiles = array();

    /**
     * Boolean flag to indicate if we are inside a PRE tag.
     * @protected
     * @since 4.4.001 (2008-12-08)
     */
    protected $premode = false;

    /**
     * Array used to store positions of graphics transformation blocks inside the page buffer.
     * keys are the page numbers
     * @protected
     * @since 4.4.002 (2008-12-09)
     */
    protected $transfmrk = array();

    /**
     * Default color for html links.
     * @protected
     * @since 4.4.003 (2008-12-09)
     */
    protected $htmlLinkColorArray = array(0, 0, 255);

    /**
     * Default font style to add to html links.
     * @protected
     * @since 4.4.003 (2008-12-09)
     */
    protected $htmlLinkFontStyle = 'U';

    /**
     * Counts the number of pages.
     * @protected
     * @since 4.5.000 (2008-12-31)
     */
    protected $numpages = 0;

    /**
     * Array containing page lengths in bytes.
     * @protected
     * @since 4.5.000 (2008-12-31)
     */
    protected $pagelen = array();

    /**
     * Counts the number of pages.
     * @protected
     * @since 4.5.000 (2008-12-31)
     */
    protected $numimages = 0;

    /**
     * Store the image keys.
     * @protected
     * @since 4.5.000 (2008-12-31)
     */
    protected $imagekeys = array();

    /**
     * Length of the buffer in bytes.
     * @protected
     * @since 4.5.000 (2008-12-31)
     */
    protected $bufferlen = 0;

    /**
     * Counts the number of fonts.
     * @protected
     * @since 4.5.000 (2009-01-02)
     */
    protected $numfonts = 0;

    /**
     * Store the font keys.
     * @protected
     * @since 4.5.000 (2009-01-02)
     */
    protected $fontkeys = array();

    /**
     * Store the font object IDs.
     * @protected
     * @since 4.8.001 (2009-09-09)
     */
    protected $font_obj_ids = array();

    /**
     * Store the fage status (true when opened, false when closed).
     * @protected
     * @since 4.5.000 (2009-01-02)
     */
    protected $pageopen = array();

    /**
     * Default monospace font.
     * @protected
     * @since 4.5.025 (2009-03-10)
     */
    protected $default_monospaced_font = 'courier';

    /**
     * Cloned copy of the current class object.
     * @protected
     * @since 4.5.029 (2009-03-19)
     */
    protected $objcopy;

    /**
     * Array used to store the lengths of cache files.
     * @protected
     * @since 4.5.029 (2009-03-19)
     */
    protected $cache_file_length = array();

    /**
     * Table header content to be repeated on each new page.
     * @protected
     * @since 4.5.030 (2009-03-20)
     */
    protected $thead = '';

    /**
     * Margins used for table header.
     * @protected
     * @since 4.5.030 (2009-03-20)
     */
    protected $theadMargins = array();

    /**
     * Boolean flag to enable document digital signature.
     * @protected
     * @since 4.6.005 (2009-04-24)
     */
    protected $sign = false;

    /**
     * Digital signature data.
     * @protected
     * @since 4.6.005 (2009-04-24)
     */
    protected $signature_data = array();

    /**
     * Digital signature max length.
     * @protected
     * @since 4.6.005 (2009-04-24)
     */
    protected $signature_max_length = 11742;

    /**
     * Data for digital signature appearance.
     * @protected
     * @since 5.3.011 (2010-06-16)
     */
    protected $signature_appearance = array('page' => 1, 'rect' => '0 0 0 0');

    /**
     * Array of empty digital signature appearances.
     * @protected
     * @since 5.9.101 (2011-07-06)
     */
    protected $empty_signature_appearance = array();

    /**
     * Boolean flag to enable document timestamping with TSA.
     * @protected
     * @since 6.0.085 (2014-06-19)
     */
    protected $tsa_timestamp = false;

    /**
     * Timestamping data.
     * @protected
     * @since 6.0.085 (2014-06-19)
     */
    protected $tsa_data = array();

    /**
     * Regular expression used to find blank characters (required for word-wrapping).
     * @protected
     * @since 4.6.006 (2009-04-28)
     */
    protected $re_spaces = '/[^\S\xa0]/';

    /**
     * Array of $re_spaces parts.
     * @protected
     * @since 5.5.011 (2010-07-09)
     */
    protected $re_space = array('p' => '[^\S\xa0]', 'm' => '');

    /**
     * Digital signature object ID.
     * @protected
     * @since 4.6.022 (2009-06-23)
     */
    protected $sig_obj_id = 0;

    /**
     * ID of page objects.
     * @protected
     * @since 4.7.000 (2009-08-29)
     */
    protected $page_obj_id = array();

    /**
     * List of form annotations IDs.
     * @protected
     * @since 4.8.000 (2009-09-07)
     */
    protected $form_obj_id = array();

    /**
     * Deafult Javascript field properties. Possible values are described on official Javascript for Acrobat API reference. Annotation options can be directly specified using the 'aopt' entry.
     * @protected
     * @since 4.8.000 (2009-09-07)
     */
    protected $default_form_prop = array('lineWidth'=>1, 'borderStyle'=>'solid', 'fillColor'=>array(255, 255, 255), 'strokeColor'=>array(128, 128, 128));

    /**
     * Javascript objects array.
     * @protected
     * @since 4.8.000 (2009-09-07)
     */
    protected $js_objects = array();

    /**
     * Current form action (used during XHTML rendering).
     * @protected
     * @since 4.8.000 (2009-09-07)
     */
    protected $form_action = '';

    /**
     * Current form encryption type (used during XHTML rendering).
     * @protected
     * @since 4.8.000 (2009-09-07)
     */
    protected $form_enctype = 'application/x-www-form-urlencoded';

    /**
     * Current method to submit forms.
     * @protected
     * @since 4.8.000 (2009-09-07)
     */
    protected $form_mode = 'post';

    /**
     * List of fonts used on form fields (fontname => fontkey).
     * @protected
     * @since 4.8.001 (2009-09-09)
     */
    protected $annotation_fonts = array();

    /**
     * List of radio buttons parent objects.
     * @protected
     * @since 4.8.001 (2009-09-09)
     */
    protected $radiobutton_groups = array();

    /**
     * List of radio group objects IDs.
     * @protected
     * @since 4.8.001 (2009-09-09)
     */
    protected $radio_groups = array();

    /**
     * Text indentation value (used for text-indent CSS attribute).
     * @protected
     * @since 4.8.006 (2009-09-23)
     */
    protected $textindent = 0;

    /**
     * Store page number when startTransaction() is called.
     * @protected
     * @since 4.8.006 (2009-09-23)
     */
    protected $start_transaction_page = 0;

    /**
     * Store Y position when startTransaction() is called.
     * @protected
     * @since 4.9.001 (2010-03-28)
     */
    protected $start_transaction_y = 0;

    /**
     * True when we are printing the thead section on a new page.
     * @protected
     * @since 4.8.027 (2010-01-25)
     */
    protected $inthead = false;

    /**
     * Array of column measures (width, space, starting Y position).
     * @protected
     * @since 4.9.001 (2010-03-28)
     */
    protected $columns = array();

    /**
     * Number of colums.
     * @protected
     * @since 4.9.001 (2010-03-28)
     */
    protected $num_columns = 1;

    /**
     * Current column number.
     * @protected
     * @since 4.9.001 (2010-03-28)
     */
    protected $current_column = 0;

    /**
     * Starting page for columns.
     * @protected
     * @since 4.9.001 (2010-03-28)
     */
    protected $column_start_page = 0;

    /**
     * Maximum page and column selected.
     * @protected
     * @since 5.8.000 (2010-08-11)
     */
    protected $maxselcol = array('page' => 0, 'column' => 0);

    /**
     * Array of: X difference between table cell x start and starting page margin, cellspacing, cellpadding.
     * @protected
     * @since 5.8.000 (2010-08-11)
     */
    protected $colxshift = array('x' => 0, 's' => array('H' => 0, 'V' => 0), 'p' => array('L' => 0, 'T' => 0, 'R' => 0, 'B' => 0));

    /**
     * Text rendering mode: 0 = Fill text; 1 = Stroke text; 2 = Fill, then stroke text; 3 = Neither fill nor stroke text (invisible); 4 = Fill text and add to path for clipping; 5 = Stroke text and add to path for clipping; 6 = Fill, then stroke text and add to path for clipping; 7 = Add text to path for clipping.
     * @protected
     * @since 4.9.008 (2010-04-03)
     */
    protected $textrendermode = 0;

    /**
     * Text stroke width in doc units.
     * @protected
     * @since 4.9.008 (2010-04-03)
     */
    protected $textstrokewidth = 0;

    /**
     * Current stroke color.
     * @protected
     * @since 4.9.008 (2010-04-03)
     */
    protected $strokecolor;

    /**
     * Default unit of measure for document.
     * @protected
     * @since 5.0.000 (2010-04-22)
     */
    protected $pdfunit = 'mm';

    /**
     * Boolean flag true when we are on TOC (Table Of Content) page.
     * @protected
     */
    protected $tocpage = false;

    /**
     * Boolean flag: if true convert vector images (SVG, EPS) to raster image using GD or ImageMagick library.
     * @protected
     * @since 5.0.000 (2010-04-26)
     */
    protected $rasterize_vector_images = false;

    /**
     * Boolean flag: if true enables font subsetting by default.
     * @protected
     * @since 5.3.002 (2010-06-07)
     */
    protected $font_subsetting = true;

    /**
     * Array of default graphic settings.
     * @protected
     * @since 5.5.008 (2010-07-02)
     */
    protected $default_graphic_vars = array();

    /**
     * Array of XObjects.
     * @protected
     * @since 5.8.014 (2010-08-23)
     */
    protected $xobjects = array();

    /**
     * Boolean value true when we are inside an XObject.
     * @protected
     * @since 5.8.017 (2010-08-24)
     */
    protected $inxobj = false;

    /**
     * Current XObject ID.
     * @protected
     * @since 5.8.017 (2010-08-24)
     */
    protected $xobjid = '';

    /**
     * Percentage of character stretching.
     * @protected
     * @since 5.9.000 (2010-09-29)
     */
    protected $font_stretching = 100;

    /**
     * Increases or decreases the space between characters in a text by the specified amount (tracking).
     * @protected
     * @since 5.9.000 (2010-09-29)
     */
    protected $font_spacing = 0;

    /**
     * Array of no-write regions.
     * ('page' => page number or empy for current page, 'xt' => X top, 'yt' => Y top, 'xb' => X bottom, 'yb' => Y bottom, 'side' => page side 'L' = left or 'R' = right)
     * @protected
     * @since 5.9.003 (2010-10-14)
     */
    protected $page_regions = array();

    /**
     * Boolean value true when page region check is active.
     * @protected
     */
    protected $check_page_regions = true;

    /**
     * Array of PDF layers data.
     * @protected
     * @since 5.9.102 (2011-07-13)
     */
    protected $pdflayers = array();

    /**
     * A dictionary of names and corresponding destinations (Dests key on document Catalog).
     * @protected
     * @since 5.9.097 (2011-06-23)
     */
    protected $dests = array();

    /**
     * Object ID for Named Destinations
     * @protected
     * @since 5.9.097 (2011-06-23)
     */
    protected $n_dests;

    /**
     * Embedded Files Names
     * @protected
     * @since 5.9.204 (2013-01-23)
     */
    protected $efnames = array();

    /**
     * Directory used for the last SVG image.
     * @protected
     * @since 5.0.000 (2010-05-05)
     */
    protected $svgdir = '';

    /**
     *  Deafult unit of measure for SVG.
     * @protected
     * @since 5.0.000 (2010-05-02)
     */
    protected $svgunit = 'px';

    /**
     * Array of SVG gradients.
     * @protected
     * @since 5.0.000 (2010-05-02)
     */
    protected $svggradients = array();

    /**
     * ID of last SVG gradient.
     * @protected
     * @since 5.0.000 (2010-05-02)
     */
    protected $svggradientid = 0;

    /**
     * Boolean value true when in SVG defs group.
     * @protected
     * @since 5.0.000 (2010-05-02)
     */
    protected $svgdefsmode = false;

    /**
     * Array of SVG defs.
     * @protected
     * @since 5.0.000 (2010-05-02)
     */
    protected $svgdefs = array();

    /**
     * Boolean value true when in SVG clipPath tag.
     * @protected
     * @since 5.0.000 (2010-04-26)
     */
    protected $svgclipmode = false;

    /**
     * Array of SVG clipPath commands.
     * @protected
     * @since 5.0.000 (2010-05-02)
     */
    protected $svgclippaths = array();

    /**
     * Array of SVG clipPath tranformation matrix.
     * @protected
     * @since 5.8.022 (2010-08-31)
     */
    protected $svgcliptm = array();

    /**
     * ID of last SVG clipPath.
     * @protected
     * @since 5.0.000 (2010-05-02)
     */
    protected $svgclipid = 0;

    /**
     * SVG text.
     * @protected
     * @since 5.0.000 (2010-05-02)
     */
    protected $svgtext = '';

    /**
     * SVG text properties.
     * @protected
     * @since 5.8.013 (2010-08-23)
     */
    protected $svgtextmode = array();

    /**
     * Array of SVG properties.
     * @protected
     * @since 5.0.000 (2010-05-02)
     */
    protected $svgstyles = array(array(
        'alignment-baseline' => 'auto',
        'baseline-shift' => 'baseline',
        'clip' => 'auto',
        'clip-path' => 'none',
        'clip-rule' => 'nonzero',
        'color' => 'black',
        'color-interpolation' => 'sRGB',
        'color-interpolation-filters' => 'linearRGB',
        'color-profile' => 'auto',
        'color-rendering' => 'auto',
        'cursor' => 'auto',
        'direction' => 'ltr',
        'display' => 'inline',
        'dominant-baseline' => 'auto',
        'enable-background' => 'accumulate',
        'fill' => 'black',
        'fill-opacity' => 1,
        'fill-rule' => 'nonzero',
        'filter' => 'none',
        'flood-color' => 'black',
        'flood-opacity' => 1,
        'font' => '',
        'font-family' => 'helvetica',
        'font-size' => 'medium',
        'font-size-adjust' => 'none',
        'font-stretch' => 'normal',
        'font-style' => 'normal',
        'font-variant' => 'normal',
        'font-weight' => 'normal',
        'glyph-orientation-horizontal' => '0deg',
        'glyph-orientation-vertical' => 'auto',
        'image-rendering' => 'auto',
        'kerning' => 'auto',
        'letter-spacing' => 'normal',
        'lighting-color' => 'white',
        'marker' => '',
        'marker-end' => 'none',
        'marker-mid' => 'none',
        'marker-start' => 'none',
        'mask' => 'none',
        'opacity' => 1,
        'overflow' => 'auto',
        'pointer-events' => 'visiblePainted',
        'shape-rendering' => 'auto',
        'stop-color' => 'black',
        'stop-opacity' => 1,
        'stroke' => 'none',
        'stroke-dasharray' => 'none',
        'stroke-dashoffset' => 0,
        'stroke-linecap' => 'butt',
        'stroke-linejoin' => 'miter',
        'stroke-miterlimit' => 4,
        'stroke-opacity' => 1,
        'stroke-width' => 1,
        'text-anchor' => 'start',
        'text-decoration' => 'none',
        'text-rendering' => 'auto',
        'unicode-bidi' => 'normal',
        'visibility' => 'visible',
        'word-spacing' => 'normal',
        'writing-mode' => 'lr-tb',
        'text-color' => 'black',
        'transfmatrix' => array(1, 0, 0, 1, 0, 0)
        ));

    /**
     * If true force sRGB color profile for all document.
     * @protected
     * @since 5.9.121 (2011-09-28)
     */
    protected $force_srgb = false;

    /**
     * If true set the document to PDF/A mode.
     * @protected
     * @since 5.9.121 (2011-09-27)
     */
    protected $pdfa_mode = false;

    /**
     * Document creation date-time
     * @protected
     * @since 5.9.152 (2012-03-22)
     */
    protected $doc_creation_timestamp;

    /**
     * Document modification date-time
     * @protected
     * @since 5.9.152 (2012-03-22)
     */
    protected $doc_modification_timestamp;

    /**
     * Custom XMP data.
     * @protected
     * @since 5.9.128 (2011-10-06)
     */
    protected $custom_xmp = '';

    /**
     * Overprint mode array.
     * (Check the "Entries in a Graphics State Parameter Dictionary" on PDF 32000-1:2008).
     * @protected
     * @since 5.9.152 (2012-03-23)
     */
    protected $overprint = array('OP' => false, 'op' => false, 'OPM' => 0);

    /**
     * Alpha mode array.
     * (Check the "Entries in a Graphics State Parameter Dictionary" on PDF 32000-1:2008).
     * @protected
     * @since 5.9.152 (2012-03-23)
     */
    protected $alpha = array('CA' => 1, 'ca' => 1, 'BM' => '/Normal', 'AIS' => false);

    /**
     * Define the page boundaries boxes to be set on document.
     * @protected
     * @since 5.9.152 (2012-03-23)
     */
    protected $page_boxes = array('MediaBox', 'CropBox', 'BleedBox', 'TrimBox', 'ArtBox');

    /**
     * If true print TCPDF meta link.
     * @protected
     * @since 5.9.152 (2012-03-23)
     */
    protected $tcpdflink = true;

    /**
     * Cache array for computed GD gamma values.
     * @protected
     * @since 5.9.1632 (2012-06-05)
     */
    protected $gdgammacache = array();

    //------------------------------------------------------------
    // METHODS
    //------------------------------------------------------------

    /**
     * This is the class constructor.
     * It allows to set up the page format, the orientation and the measure unit used in all the methods (except for the font sizes).
     * 
     * IMPORTANT: Please note that this method sets the mb_internal_encoding to ASCII, so if you are using the mbstring module functions with TCPDF you need to correctly set/unset the mb_internal_encoding when needed.
     * 
     * @param $orientation (string) page orientation. Possible values are (case insensitive):<ul><li>P or Portrait (default)</li><li>L or Landscape</li><li>'' (empty string) for automatic orientation</li></ul>
     * @param $unit (string) User measure unit. Possible values are:<ul><li>pt: point</li><li>mm: millimeter (default)</li><li>cm: centimeter</li><li>in: inch</li></ul><br />A point equals 1/72 of inch, that is to say about 0.35 mm (an inch being 2.54 cm). This is a very common unit in typography; font sizes are expressed in that unit.
     * @param $format (mixed) The format used for pages. It can be either: one of the string values specified at getPageSizeFromFormat() or an array of parameters specified at setPageFormat().
     * @param $unicode (boolean) TRUE means that the input text is unicode (default = true)
     * @param $encoding (string) Charset encoding (used only when converting back html entities); default is UTF-8.
     * @param $diskcache (boolean) DEPRECATED FEATURE
     * @param $pdfa (boolean) If TRUE set the document to PDF/A mode.
     * @public
     * @see getPageSizeFromFormat(), setPageFormat()
     */
    public function __construct($orientation='P', $unit='mm', $format='A4', $unicode=true, $encoding='UTF-8', $diskcache=false, $pdfa=false) {
        /* Set internal character encoding to ASCII */
        if (function_exists('mb_internal_encoding') AND mb_internal_encoding()) {
            $this->internal_encoding = mb_internal_encoding();
            mb_internal_encoding('ASCII');
        }
        // set file ID for trailer
        $serformat = (is_array($format) ? json_encode($format) : $format);
        $this->file_id = md5(TCPDF_STATIC::getRandomSeed('TCPDF'.$orientation.$unit.$serformat.$encoding));
        $this->font_obj_ids = array();
        $this->page_obj_id = array();
        $this->form_obj_id = array();
        // set pdf/a mode
        $this->pdfa_mode = $pdfa;
        $this->force_srgb = false;
        // set language direction
        $this->rtl = false;
        $this->tmprtl = false;
        // some checks
        $this->_dochecks();
        // initialization of properties
        $this->isunicode = $unicode;
        $this->page = 0;
        $this->transfmrk[0] = array();
        $this->pagedim = array();
        $this->n = 2;
        $this->buffer = '';
        $this->pages = array();
        $this->state = 0;
        $this->fonts = array();
        $this->FontFiles = array();
        $this->diffs = array();
        $this->images = array();
        $this->links = array();
        $this->gradients = array();
        $this->InFooter = false;
        $this->lasth = 0;
        $this->FontFamily = defined('PDF_FONT_NAME_MAIN')?PDF_FONT_NAME_MAIN:'helvetica';
        $this->FontStyle = '';
        $this->FontSizePt = 12;
        $this->underline = false;
        $this->overline = false;
        $this->linethrough = false;
        $this->DrawColor = '0 G';
        $this->FillColor = '0 g';
        $this->TextColor = '0 g';
        $this->ColorFlag = false;
        $this->pdflayers = array();
        // encryption values
        $this->encrypted = false;
        $this->last_enc_key = '';
        // standard Unicode fonts
        $this->CoreFonts = array(
            'courier'=>'Courier',
            'courierB'=>'Courier-Bold',
            'courierI'=>'Courier-Oblique',
            'courierBI'=>'Courier-BoldOblique',
            'helvetica'=>'Helvetica',
            'helveticaB'=>'Helvetica-Bold',
            'helveticaI'=>'Helvetica-Oblique',
            'helveticaBI'=>'Helvetica-BoldOblique',
            'times'=>'Times-Roman',
            'timesB'=>'Times-Bold',
            'timesI'=>'Times-Italic',
            'timesBI'=>'Times-BoldItalic',
            'symbol'=>'Symbol',
            'zapfdingbats'=>'ZapfDingbats'
        );
        // set scale factor
        $this->setPageUnit($unit);
        // set page format and orientation
        $this->setPageFormat($format, $orientation);
        // page margins (1 cm)
        $margin = 28.35 / $this->k;
        $this->SetMargins($margin, $margin);
        $this->clMargin = $this->lMargin;
        $this->crMargin = $this->rMargin;
        // internal cell padding
        $cpadding = $margin / 10;
        $this->setCellPaddings($cpadding, 0, $cpadding, 0);
        // cell margins
        $this->setCellMargins(0, 0, 0, 0);
        // line width (0.2 mm)
        $this->LineWidth = 0.57 / $this->k;
        $this->linestyleWidth = sprintf('%F w', ($this->LineWidth * $this->k));
        $this->linestyleCap = '0 J';
        $this->linestyleJoin = '0 j';
        $this->linestyleDash = '[] 0 d';
        // automatic page break
        $this->SetAutoPageBreak(true, (2 * $margin));
        // full width display mode
        $this->SetDisplayMode('fullwidth');
        // compression
        $this->SetCompression();
        // set default PDF version number
        $this->setPDFVersion();
        $this->tcpdflink = true;
        $this->encoding = $encoding;
        $this->HREF = array();
        $this->getFontsList();
        $this->fgcolor = array('R' => 0, 'G' => 0, 'B' => 0);
        $this->strokecolor = array('R' => 0, 'G' => 0, 'B' => 0);
        $this->bgcolor = array('R' => 255, 'G' => 255, 'B' => 255);
        $this->extgstates = array();
        $this->setTextShadow();
        // signature
        $this->sign = false;
        $this->tsa_timestamp = false;
        $this->tsa_data = array();
        $this->signature_appearance = array('page' => 1, 'rect' => '0 0 0 0', 'name' => 'Signature');
        $this->empty_signature_appearance = array();
        // user's rights
        $this->ur['enabled'] = false;
        $this->ur['document'] = '/FullSave';
        $this->ur['annots'] = '/Create/Delete/Modify/Copy/Import/Export';
        $this->ur['form'] = '/Add/Delete/FillIn/Import/Export/SubmitStandalone/SpawnTemplate';
        $this->ur['signature'] = '/Modify';
        $this->ur['ef'] = '/Create/Delete/Modify/Import';
        $this->ur['formex'] = '';
        // set default JPEG quality
        $this->jpeg_quality = 75;
        // initialize some settings
        TCPDF_FONTS::utf8Bidi(array(''), '', false, $this->isunicode, $this->CurrentFont);
        // set default font
        $this->SetFont($this->FontFamily, $this->FontStyle, $this->FontSizePt);
        $this->setHeaderFont(array($this->FontFamily, $this->FontStyle, $this->FontSizePt));
        $this->setFooterFont(array($this->FontFamily, $this->FontStyle, $this->FontSizePt));
        // check if PCRE Unicode support is enabled
        if ($this->isunicode AND (@preg_match('/\pL/u', 'a') == 1)) {
            // PCRE unicode support is turned ON
            // \s     : any whitespace character
            // \p{Z}  : any separator
            // \p{Lo} : Unicode letter or ideograph that does not have lowercase and uppercase variants. Is used to chunk chinese words.
            // \xa0   : Unicode Character 'NO-BREAK SPACE' (U+00A0)
            //$this->setSpacesRE('/(?!\xa0)[\s\p{Z}\p{Lo}]/u');
            $this->setSpacesRE('/(?!\xa0)[\s\p{Z}]/u');
        } else {
            // PCRE unicode support is turned OFF
            $this->setSpacesRE('/[^\S\xa0]/');
        }
        $this->default_form_prop = array('lineWidth'=>1, 'borderStyle'=>'solid', 'fillColor'=>array(255, 255, 255), 'strokeColor'=>array(128, 128, 128));
        // set document creation and modification timestamp
        $this->doc_creation_timestamp = time();
        $this->doc_modification_timestamp = $this->doc_creation_timestamp;
        // get default graphic vars
        $this->default_graphic_vars = $this->getGraphicVars();
        $this->header_xobj_autoreset = false;
        $this->custom_xmp = '';
        // Call cleanup method after script execution finishes or exit() is called.
        // NOTE: This will not be executed if the process is killed with a SIGTERM or SIGKILL signal.
        register_shutdown_function(array($this, '_destroy'), true);
    }

    /**
     * Default destructor.
     * @public
     * @since 1.53.0.TC016
     */
    public function __destruct() {
        // restore internal encoding
        if (isset($this->internal_encoding) AND !empty($this->internal_encoding)) {
            mb_internal_encoding($this->internal_encoding);
        }
        // cleanup
        $this->_destroy(true);
    }

    /**
     * Set the units of measure for the document.
     * @param $unit (string) User measure unit. Possible values are:<ul><li>pt: point</li><li>mm: millimeter (default)</li><li>cm: centimeter</li><li>in: inch</li></ul><br />A point equals 1/72 of inch, that is to say about 0.35 mm (an inch being 2.54 cm). This is a very common unit in typography; font sizes are expressed in that unit.
     * @public
     * @since 3.0.015 (2008-06-06)
     */
    public function setPageUnit($unit) {
        $unit = strtolower($unit);
        //Set scale factor
        switch ($unit) {
            // points
            case 'px':
            case 'pt': {
                $this->k = 1;
                break;
            }
            // millimeters
            case 'mm': {
                $this->k = $this->dpi / 25.4;
                break;
            }
            // centimeters
            case 'cm': {
                $this->k = $this->dpi / 2.54;
                break;
            }
            // inches
            case 'in': {
                $this->k = $this->dpi;
                break;
            }
            // unsupported unit
            default : {
                $this->Error('Incorrect unit: '.$unit);
                break;
            }
        }
        $this->pdfunit = $unit;
        if (isset($this->CurOrientation)) {
            $this->setPageOrientation($this->CurOrientation);
        }
    }

    /**
     * Change the format of the current page
     * @param $format (mixed) The format used for pages. It can be either: one of the string values specified at getPageSizeFromFormat() documentation or an array of two numbers (width, height) or an array containing the following measures and options:<ul>
     * <li>['format'] = page format name (one of the above);</li>
     * <li>['Rotate'] : The number of degrees by which the page shall be rotated clockwise when displayed or printed. The value shall be a multiple of 90.</li>
     * <li>['PZ'] : The page's preferred zoom (magnification) factor.</li>
     * <li>['MediaBox'] : the boundaries of the physical medium on which the page shall be displayed or printed:</li>
     * <li>['MediaBox']['llx'] : lower-left x coordinate</li>
     * <li>['MediaBox']['lly'] : lower-left y coordinate</li>
     * <li>['MediaBox']['urx'] : upper-right x coordinate</li>
     * <li>['MediaBox']['ury'] : upper-right y coordinate</li>
     * <li>['CropBox'] : the visible region of default user space:</li>
     * <li>['CropBox']['llx'] : lower-left x coordinate</li>
     * <li>['CropBox']['lly'] : lower-left y coordinate</li>
     * <li>['CropBox']['urx'] : upper-right x coordinate</li>
     * <li>['CropBox']['ury'] : upper-right y coordinate</li>
     * <li>['BleedBox'] : the region to which the contents of the page shall be clipped when output in a production environment:</li>
     * <li>['BleedBox']['llx'] : lower-left x coordinate</li>
     * <li>['BleedBox']['lly'] : lower-left y coordinate</li>
     * <li>['BleedBox']['urx'] : upper-right x coordinate</li>
     * <li>['BleedBox']['ury'] : upper-right y coordinate</li>
     * <li>['TrimBox'] : the intended dimensions of the finished page after trimming:</li>
     * <li>['TrimBox']['llx'] : lower-left x coordinate</li>
     * <li>['TrimBox']['lly'] : lower-left y coordinate</li>
     * <li>['TrimBox']['urx'] : upper-right x coordinate</li>
     * <li>['TrimBox']['ury'] : upper-right y coordinate</li>
     * <li>['ArtBox'] : the extent of the page's meaningful content:</li>
     * <li>['ArtBox']['llx'] : lower-left x coordinate</li>
     * <li>['ArtBox']['lly'] : lower-left y coordinate</li>
     * <li>['ArtBox']['urx'] : upper-right x coordinate</li>
     * <li>['ArtBox']['ury'] : upper-right y coordinate</li>
     * <li>['BoxColorInfo'] :specify the colours and other visual characteristics that should be used in displaying guidelines on the screen for each of the possible page boundaries other than the MediaBox:</li>
     * <li>['BoxColorInfo'][BOXTYPE]['C'] : an array of three numbers in the range 0-255, representing the components in the DeviceRGB colour space.</li>
     * <li>['BoxColorInfo'][BOXTYPE]['W'] : the guideline width in default user units</li>
     * <li>['BoxColorInfo'][BOXTYPE]['S'] : the guideline style: S = Solid; D = Dashed</li>
     * <li>['BoxColorInfo'][BOXTYPE]['D'] : dash array defining a pattern of dashes and gaps to be used in drawing dashed guidelines</li>
     * <li>['trans'] : the style and duration of the visual transition to use when moving from another page to the given page during a presentation</li>
     * <li>['trans']['Dur'] : The page's display duration (also called its advance timing): the maximum length of time, in seconds, that the page shall be displayed during presentations before the viewer application shall automatically advance to the next page.</li>
     * <li>['trans']['S'] : transition style : Split, Blinds, Box, Wipe, Dissolve, Glitter, R, Fly, Push, Cover, Uncover, Fade</li>
     * <li>['trans']['D'] : The duration of the transition effect, in seconds.</li>
     * <li>['trans']['Dm'] : (Split and Blinds transition styles only) The dimension in which the specified transition effect shall occur: H = Horizontal, V = Vertical. Default value: H.</li>
     * <li>['trans']['M'] : (Split, Box and Fly transition styles only) The direction of motion for the specified transition effect: I = Inward from the edges of the page, O = Outward from the center of the pageDefault value: I.</li>
     * <li>['trans']['Di'] : (Wipe, Glitter, Fly, Cover, Uncover and Push transition styles only) The direction in which the specified transition effect shall moves, expressed in degrees counterclockwise starting from a left-to-right direction. If the value is a number, it shall be one of: 0 = Left to right, 90 = Bottom to top (Wipe only), 180 = Right to left (Wipe only), 270 = Top to bottom, 315 = Top-left to bottom-right (Glitter only). If the value is a name, it shall be None, which is relevant only for the Fly transition when the value of SS is not 1.0. Default value: 0.</li>
     * <li>['trans']['SS'] : (Fly transition style only) The starting or ending scale at which the changes shall be drawn. If M specifies an inward transition, the scale of the changes drawn shall progress from SS to 1.0 over the course of the transition. If M specifies an outward transition, the scale of the changes drawn shall progress from 1.0 to SS over the course of the transition. Default: 1.0.</li>
     * <li>['trans']['B'] : (Fly transition style only) If true, the area that shall be flown in is rectangular and opaque. Default: false.</li>
     * </ul>
     * @param $orientation (string) page orientation. Possible values are (case insensitive):<ul>
     * <li>P or Portrait (default)</li>
     * <li>L or Landscape</li>
     * <li>'' (empty string) for automatic orientation</li>
     * </ul>
     * @protected
     * @since 3.0.015 (2008-06-06)
     * @see getPageSizeFromFormat()
     */
    protected function setPageFormat($format, $orientation='P') {
        if (!empty($format) AND isset($this->pagedim[$this->page])) {
            // remove inherited values
            unset($this->pagedim[$this->page]);
        }
        if (is_string($format)) {
            // get page measures from format name
            $pf = TCPDF_STATIC::getPageSizeFromFormat($format);
            $this->fwPt = $pf[0];
            $this->fhPt = $pf[1];
        } else {
            // the boundaries of the physical medium on which the page shall be displayed or printed
            if (isset($format['MediaBox'])) {
                $this->pagedim = TCPDF_STATIC::setPageBoxes($this->page, 'MediaBox', $format['MediaBox']['llx'], $format['MediaBox']['lly'], $format['MediaBox']['urx'], $format['MediaBox']['ury'], false, $this->k, $this->pagedim);
                $this->fwPt = (($format['MediaBox']['urx'] - $format['MediaBox']['llx']) * $this->k);
                $this->fhPt = (($format['MediaBox']['ury'] - $format['MediaBox']['lly']) * $this->k);
            } else {
                if (isset($format[0]) AND is_numeric($format[0]) AND isset($format[1]) AND is_numeric($format[1])) {
                    $pf = array(($format[0] * $this->k), ($format[1] * $this->k));
                } else {
                    if (!isset($format['format'])) {
                        // default value
                        $format['format'] = 'A4';
                    }
                    $pf = TCPDF_STATIC::getPageSizeFromFormat($format['format']);
                }
                $this->fwPt = $pf[0];
                $this->fhPt = $pf[1];
                $this->pagedim = TCPDF_STATIC::setPageBoxes($this->page, 'MediaBox', 0, 0, $this->fwPt, $this->fhPt, true, $this->k, $this->pagedim);
            }
            // the visible region of default user space
            if (isset($format['CropBox'])) {
                $this->pagedim = TCPDF_STATIC::setPageBoxes($this->page, 'CropBox', $format['CropBox']['llx'], $format['CropBox']['lly'], $format['CropBox']['urx'], $format['CropBox']['ury'], false, $this->k, $this->pagedim);
            }
            // the region to which the contents of the page shall be clipped when output in a production environment
            if (isset($format['BleedBox'])) {
                $this->pagedim = TCPDF_STATIC::setPageBoxes($this->page, 'BleedBox', $format['BleedBox']['llx'], $format['BleedBox']['lly'], $format['BleedBox']['urx'], $format['BleedBox']['ury'], false, $this->k, $this->pagedim);
            }
            // the intended dimensions of the finished page after trimming
            if (isset($format['TrimBox'])) {
                $this->pagedim = TCPDF_STATIC::setPageBoxes($this->page, 'TrimBox', $format['TrimBox']['llx'], $format['TrimBox']['lly'], $format['TrimBox']['urx'], $format['TrimBox']['ury'], false, $this->k, $this->pagedim);
            }
            // the page's meaningful content (including potential white space)
            if (isset($format['ArtBox'])) {
                $this->pagedim = TCPDF_STATIC::setPageBoxes($this->page, 'ArtBox', $format['ArtBox']['llx'], $format['ArtBox']['lly'], $format['ArtBox']['urx'], $format['ArtBox']['ury'], false, $this->k, $this->pagedim);
            }
            // specify the colours and other visual characteristics that should be used in displaying guidelines on the screen for the various page boundaries
            if (isset($format['BoxColorInfo'])) {
                $this->pagedim[$this->page]['BoxColorInfo'] = $format['BoxColorInfo'];
            }
            if (isset($format['Rotate']) AND (($format['Rotate'] % 90) == 0)) {
                // The number of degrees by which the page shall be rotated clockwise when displayed or printed. The value shall be a multiple of 90.
                $this->pagedim[$this->page]['Rotate'] = intval($format['Rotate']);
            }
            if (isset($format['PZ'])) {
                // The page's preferred zoom (magnification) factor
                $this->pagedim[$this->page]['PZ'] = floatval($format['PZ']);
            }
            if (isset($format['trans'])) {
                // The style and duration of the visual transition to use when moving from another page to the given page during a presentation
                if (isset($format['trans']['Dur'])) {
                    // The page's display duration
                    $this->pagedim[$this->page]['trans']['Dur'] = floatval($format['trans']['Dur']);
                }
                $stansition_styles = array('Split', 'Blinds', 'Box', 'Wipe', 'Dissolve', 'Glitter', 'R', 'Fly', 'Push', 'Cover', 'Uncover', 'Fade');
                if (isset($format['trans']['S']) AND in_array($format['trans']['S'], $stansition_styles)) {
                    // The transition style that shall be used when moving to this page from another during a presentation
                    $this->pagedim[$this->page]['trans']['S'] = $format['trans']['S'];
                    $valid_effect = array('Split', 'Blinds');
                    $valid_vals = array('H', 'V');
                    if (isset($format['trans']['Dm']) AND in_array($format['trans']['S'], $valid_effect) AND in_array($format['trans']['Dm'], $valid_vals)) {
                        $this->pagedim[$this->page]['trans']['Dm'] = $format['trans']['Dm'];
                    }
                    $valid_effect = array('Split', 'Box', 'Fly');
                    $valid_vals = array('I', 'O');
                    if (isset($format['trans']['M']) AND in_array($format['trans']['S'], $valid_effect) AND in_array($format['trans']['M'], $valid_vals)) {
                        $this->pagedim[$this->page]['trans']['M'] = $format['trans']['M'];
                    }
                    $valid_effect = array('Wipe', 'Glitter', 'Fly', 'Cover', 'Uncover', 'Push');
                    if (isset($format['trans']['Di']) AND in_array($format['trans']['S'], $valid_effect)) {
                        if (((($format['trans']['Di'] == 90) OR ($format['trans']['Di'] == 180)) AND ($format['trans']['S'] == 'Wipe'))
                            OR (($format['trans']['Di'] == 315) AND ($format['trans']['S'] == 'Glitter'))
                            OR (($format['trans']['Di'] == 0) OR ($format['trans']['Di'] == 270))) {
                            $this->pagedim[$this->page]['trans']['Di'] = intval($format['trans']['Di']);
                        }
                    }
                    if (isset($format['trans']['SS']) AND ($format['trans']['S'] == 'Fly')) {
                        $this->pagedim[$this->page]['trans']['SS'] = floatval($format['trans']['SS']);
                    }
                    if (isset($format['trans']['B']) AND ($format['trans']['B'] === true) AND ($format['trans']['S'] == 'Fly')) {
                        $this->pagedim[$this->page]['trans']['B'] = 'true';
                    }
                } else {
                    $this->pagedim[$this->page]['trans']['S'] = 'R';
                }
                if (isset($format['trans']['D'])) {
                    // The duration of the transition effect, in seconds
                    $this->pagedim[$this->page]['trans']['D'] = floatval($format['trans']['D']);
                } else {
                    $this->pagedim[$this->page]['trans']['D'] = 1;
                }
            }
        }
        $this->setPageOrientation($orientation);
    }

    /**
     * Set page orientation.
     * @param $orientation (string) page orientation. Possible values are (case insensitive):<ul><li>P or Portrait (default)</li><li>L or Landscape</li><li>'' (empty string) for automatic orientation</li></ul>
     * @param $autopagebreak (boolean) Boolean indicating if auto-page-break mode should be on or off.
     * @param $bottommargin (float) bottom margin of the page.
     * @public
     * @since 3.0.015 (2008-06-06)
     */
    public function setPageOrientation($orientation, $autopagebreak='', $bottommargin='') {
        if (!isset($this->pagedim[$this->page]['MediaBox'])) {
            // the boundaries of the physical medium on which the page shall be displayed or printed
            $this->pagedim = TCPDF_STATIC::setPageBoxes($this->page, 'MediaBox', 0, 0, $this->fwPt, $this->fhPt, true, $this->k, $this->pagedim);
        }
        if (!isset($this->pagedim[$this->page]['CropBox'])) {
            // the visible region of default user space
            $this->pagedim = TCPDF_STATIC::setPageBoxes($this->page, 'CropBox', $this->pagedim[$this->page]['MediaBox']['llx'], $this->pagedim[$this->page]['MediaBox']['lly'], $this->pagedim[$this->page]['MediaBox']['urx'], $this->pagedim[$this->page]['MediaBox']['ury'], true, $this->k, $this->pagedim);
        }
        if (!isset($this->pagedim[$this->page]['BleedBox'])) {
            // the region to which the contents of the page shall be clipped when output in a production environment
            $this->pagedim = TCPDF_STATIC::setPageBoxes($this->page, 'BleedBox', $this->pagedim[$this->page]['CropBox']['llx'], $this->pagedim[$this->page]['CropBox']['lly'], $this->pagedim[$this->page]['CropBox']['urx'], $this->pagedim[$this->page]['CropBox']['ury'], true, $this->k, $this->pagedim);
        }
        if (!isset($this->pagedim[$this->page]['TrimBox'])) {
            // the intended dimensions of the finished page after trimming
            $this->pagedim = TCPDF_STATIC::setPageBoxes($this->page, 'TrimBox', $this->pagedim[$this->page]['CropBox']['llx'], $this->pagedim[$this->page]['CropBox']['lly'], $this->pagedim[$this->page]['CropBox']['urx'], $this->pagedim[$this->page]['CropBox']['ury'], true, $this->k, $this->pagedim);
        }
        if (!isset($this->pagedim[$this->page]['ArtBox'])) {
            // the page's meaningful content (including potential white space)
            $this->pagedim = TCPDF_STATIC::setPageBoxes($this->page, 'ArtBox', $this->pagedim[$this->page]['CropBox']['llx'], $this->pagedim[$this->page]['CropBox']['lly'], $this->pagedim[$this->page]['CropBox']['urx'], $this->pagedim[$this->page]['CropBox']['ury'], true, $this->k, $this->pagedim);
        }
        if (!isset($this->pagedim[$this->page]['Rotate'])) {
            // The number of degrees by which the page shall be rotated clockwise when displayed or printed. The value shall be a multiple of 90.
            $this->pagedim[$this->page]['Rotate'] = 0;
        }
        if (!isset($this->pagedim[$this->page]['PZ'])) {
            // The page's preferred zoom (magnification) factor
            $this->pagedim[$this->page]['PZ'] = 1;
        }
        if ($this->fwPt > $this->fhPt) {
            // landscape
            $default_orientation = 'L';
        } else {
            // portrait
            $default_orientation = 'P';
        }
        $valid_orientations = array('P', 'L');
        if (empty($orientation)) {
            $orientation = $default_orientation;
        } else {
            $orientation = strtoupper($orientation[0]);
        }
        if (in_array($orientation, $valid_orientations) AND ($orientation != $default_orientation)) {
            $this->CurOrientation = $orientation;
            $this->wPt = $this->fhPt;
            $this->hPt = $this->fwPt;
        } else {
            $this->CurOrientation = $default_orientation;
            $this->wPt = $this->fwPt;
            $this->hPt = $this->fhPt;
        }
        if ((abs($this->pagedim[$this->page]['MediaBox']['urx'] - $this->hPt) < $this->feps) AND (abs($this->pagedim[$this->page]['MediaBox']['ury'] - $this->wPt) < $this->feps)){
            // swap X and Y coordinates (change page orientation)
            $this->pagedim = TCPDF_STATIC::swapPageBoxCoordinates($this->page, $this->pagedim);
        }
        $this->w = ($this->wPt / $this->k);
        $this->h = ($this->hPt / $this->k);
        if (TCPDF_STATIC::empty_string($autopagebreak)) {
            if (isset($this->AutoPageBreak)) {
                $autopagebreak = $this->AutoPageBreak;
            } else {
                $autopagebreak = true;
            }
        }
        if (TCPDF_STATIC::empty_string($bottommargin)) {
            if (isset($this->bMargin)) {
                $bottommargin = $this->bMargin;
            } else {
                // default value = 2 cm
                $bottommargin = 2 * 28.35 / $this->k;
            }
        }
        $this->SetAutoPageBreak($autopagebreak, $bottommargin);
        // store page dimensions
        $this->pagedim[$this->page]['w'] = $this->wPt;
        $this->pagedim[$this->page]['h'] = $this->hPt;
        $this->pagedim[$this->page]['wk'] = $this->w;
        $this->pagedim[$this->page]['hk'] = $this->h;
        $this->pagedim[$this->page]['tm'] = $this->tMargin;
        $this->pagedim[$this->page]['bm'] = $bottommargin;
        $this->pagedim[$this->page]['lm'] = $this->lMargin;
        $this->pagedim[$this->page]['rm'] = $this->rMargin;
        $this->pagedim[$this->page]['pb'] = $autopagebreak;
        $this->pagedim[$this->page]['or'] = $this->CurOrientation;
        $this->pagedim[$this->page]['olm'] = $this->original_lMargin;
        $this->pagedim[$this->page]['orm'] = $this->original_rMargin;
    }

    /**
     * Set regular expression to detect withespaces or word separators.
     * The pattern delimiter must be the forward-slash character "/".
     * Some example patterns are:
     * <pre>
     * Non-Unicode or missing PCRE unicode support: "/[^\S\xa0]/"
     * Unicode and PCRE unicode support: "/(?!\xa0)[\s\p{Z}]/u"
     * Unicode and PCRE unicode support in Chinese mode: "/(?!\xa0)[\s\p{Z}\p{Lo}]/u"
     * if PCRE unicode support is turned ON ("\P" is the negate class of "\p"):
     *      \s     : any whitespace character
     *      \p{Z}  : any separator
     *      \p{Lo} : Unicode letter or ideograph that does not have lowercase and uppercase variants. Is used to chunk chinese words.
     *      \xa0   : Unicode Character 'NO-BREAK SPACE' (U+00A0)
     * </pre>
     * @param $re (string) regular expression (leave empty for default).
     * @public
     * @since 4.6.016 (2009-06-15)
     */
    public function setSpacesRE($re='/[^\S\xa0]/') {
        $this->re_spaces = $re;
        $re_parts = explode('/', $re);
        // get pattern parts
        $this->re_space = array();
        if (isset($re_parts[1]) AND !empty($re_parts[1])) {
            $this->re_space['p'] = $re_parts[1];
        } else {
            $this->re_space['p'] = '[\s]';
        }
        // set pattern modifiers
        if (isset($re_parts[2]) AND !empty($re_parts[2])) {
            $this->re_space['m'] = $re_parts[2];
        } else {
            $this->re_space['m'] = '';
        }
    }

    /**
     * Enable or disable Right-To-Left language mode
     * @param $enable (Boolean) if true enable Right-To-Left language mode.
     * @param $resetx (Boolean) if true reset the X position on direction change.
     * @public
     * @since 2.0.000 (2008-01-03)
     */
    public function setRTL($enable, $resetx=true) {
        $enable = $enable ? true : false;
        $resetx = ($resetx AND ($enable != $this->rtl));
        $this->rtl = $enable;
        $this->tmprtl = false;
        if ($resetx) {
            $this->Ln(0);
        }
    }

    /**
     * Return the RTL status
     * @return boolean
     * @public
     * @since 4.0.012 (2008-07-24)
     */
    public function getRTL() {
        return $this->rtl;
    }

    /**
     * Force temporary RTL language direction
     * @param $mode (mixed) can be false, 'L' for LTR or 'R' for RTL
     * @public
     * @since 2.1.000 (2008-01-09)
     */
    public function setTempRTL($mode) {
        $newmode = false;
        switch (strtoupper($mode)) {
            case 'LTR':
            case 'L': {
                if ($this->rtl) {
                    $newmode = 'L';
                }
                break;
            }
            case 'RTL':
            case 'R': {
                if (!$this->rtl) {
                    $newmode = 'R';
                }
                break;
            }
            case false:
            default: {
                $newmode = false;
                break;
            }
        }
        $this->tmprtl = $newmode;
    }

    /**
     * Return the current temporary RTL status
     * @return boolean
     * @public
     * @since 4.8.014 (2009-11-04)
     */
    public function isRTLTextDir() {
        return ($this->rtl OR ($this->tmprtl == 'R'));
    }

    /**
     * Set the last cell height.
     * @param $h (float) cell height.
     * @author Nicola Asuni
     * @public
     * @since 1.53.0.TC034
     */
    public function setLastH($h) {
        $this->lasth = $h;
    }

    /**
     * Return the cell height
     * @param $fontsize (int) Font size in internal units
     * @param $padding (boolean) If true add cell padding
     * @public
     */
    public function getCellHeight($fontsize, $padding=TRUE) {
        $height = ($fontsize * $this->cell_height_ratio);
        if ($padding) {
            $height += ($this->cell_padding['T'] + $this->cell_padding['B']);
        }
        return round($height, 6);
    }

    /**
     * Reset the last cell height.
     * @public
     * @since 5.9.000 (2010-10-03)
     */
    public function resetLastH() {
        $this->lasth = $this->getCellHeight($this->FontSize);
    }

    /**
     * Get the last cell height.
     * @return last cell height
     * @public
     * @since 4.0.017 (2008-08-05)
     */
    public function getLastH() {
        return $this->lasth;
    }

    /**
     * Set the adjusting factor to convert pixels to user units.
     * @param $scale (float) adjusting factor to convert pixels to user units.
     * @author Nicola Asuni
     * @public
     * @since 1.5.2
     */
    public function setImageScale($scale) {
        $this->imgscale = $scale;
    }

    /**
     * Returns the adjusting factor to convert pixels to user units.
     * @return float adjusting factor to convert pixels to user units.
     * @author Nicola Asuni
     * @public
     * @since 1.5.2
     */
    public function getImageScale() {
        return $this->imgscale;
    }

    /**
     * Returns an array of page dimensions:
     * <ul><li>$this->pagedim[$this->page]['w'] = page width in points</li><li>$this->pagedim[$this->page]['h'] = height in points</li><li>$this->pagedim[$this->page]['wk'] = page width in user units</li><li>$this->pagedim[$this->page]['hk'] = page height in user units</li><li>$this->pagedim[$this->page]['tm'] = top margin</li><li>$this->pagedim[$this->page]['bm'] = bottom margin</li><li>$this->pagedim[$this->page]['lm'] = left margin</li><li>$this->pagedim[$this->page]['rm'] = right margin</li><li>$this->pagedim[$this->page]['pb'] = auto page break</li><li>$this->pagedim[$this->page]['or'] = page orientation</li><li>$this->pagedim[$this->page]['olm'] = original left margin</li><li>$this->pagedim[$this->page]['orm'] = original right margin</li><li>$this->pagedim[$this->page]['Rotate'] = The number of degrees by which the page shall be rotated clockwise when displayed or printed. The value shall be a multiple of 90.</li><li>$this->pagedim[$this->page]['PZ'] = The page's preferred zoom (magnification) factor.</li><li>$this->pagedim[$this->page]['trans'] : the style and duration of the visual transition to use when moving from another page to the given page during a presentation<ul><li>$this->pagedim[$this->page]['trans']['Dur'] = The page's display duration (also called its advance timing): the maximum length of time, in seconds, that the page shall be displayed during presentations before the viewer application shall automatically advance to the next page.</li><li>$this->pagedim[$this->page]['trans']['S'] = transition style : Split, Blinds, Box, Wipe, Dissolve, Glitter, R, Fly, Push, Cover, Uncover, Fade</li><li>$this->pagedim[$this->page]['trans']['D'] = The duration of the transition effect, in seconds.</li><li>$this->pagedim[$this->page]['trans']['Dm'] = (Split and Blinds transition styles only) The dimension in which the specified transition effect shall occur: H = Horizontal, V = Vertical. Default value: H.</li><li>$this->pagedim[$this->page]['trans']['M'] = (Split, Box and Fly transition styles only) The direction of motion for the specified transition effect: I = Inward from the edges of the page, O = Outward from the center of the pageDefault value: I.</li><li>$this->pagedim[$this->page]['trans']['Di'] = (Wipe, Glitter, Fly, Cover, Uncover and Push transition styles only) The direction in which the specified transition effect shall moves, expressed in degrees counterclockwise starting from a left-to-right direction. If the value is a number, it shall be one of: 0 = Left to right, 90 = Bottom to top (Wipe only), 180 = Right to left (Wipe only), 270 = Top to bottom, 315 = Top-left to bottom-right (Glitter only). If the value is a name, it shall be None, which is relevant only for the Fly transition when the value of SS is not 1.0. Default value: 0.</li><li>$this->pagedim[$this->page]['trans']['SS'] = (Fly transition style only) The starting or ending scale at which the changes shall be drawn. If M specifies an inward transition, the scale of the changes drawn shall progress from SS to 1.0 over the course of the transition. If M specifies an outward transition, the scale of the changes drawn shall progress from 1.0 to SS over the course of the transition. Default: 1.0. </li><li>$this->pagedim[$this->page]['trans']['B'] = (Fly transition style only) If true, the area that shall be flown in is rectangular and opaque. Default: false.</li></ul></li><li>$this->pagedim[$this->page]['MediaBox'] : the boundaries of the physical medium on which the page shall be displayed or printed<ul><li>$this->pagedim[$this->page]['MediaBox']['llx'] = lower-left x coordinate in points</li><li>$this->pagedim[$this->page]['MediaBox']['lly'] = lower-left y coordinate in points</li><li>$this->pagedim[$this->page]['MediaBox']['urx'] = upper-right x coordinate in points</li><li>$this->pagedim[$this->page]['MediaBox']['ury'] = upper-right y coordinate in points</li></ul></li><li>$this->pagedim[$this->page]['CropBox'] : the visible region of default user space<ul><li>$this->pagedim[$this->page]['CropBox']['llx'] = lower-left x coordinate in points</li><li>$this->pagedim[$this->page]['CropBox']['lly'] = lower-left y coordinate in points</li><li>$this->pagedim[$this->page]['CropBox']['urx'] = upper-right x coordinate in points</li><li>$this->pagedim[$this->page]['CropBox']['ury'] = upper-right y coordinate in points</li></ul></li><li>$this->pagedim[$this->page]['BleedBox'] : the region to which the contents of the page shall be clipped when output in a production environment<ul><li>$this->pagedim[$this->page]['BleedBox']['llx'] = lower-left x coordinate in points</li><li>$this->pagedim[$this->page]['BleedBox']['lly'] = lower-left y coordinate in points</li><li>$this->pagedim[$this->page]['BleedBox']['urx'] = upper-right x coordinate in points</li><li>$this->pagedim[$this->page]['BleedBox']['ury'] = upper-right y coordinate in points</li></ul></li><li>$this->pagedim[$this->page]['TrimBox'] : the intended dimensions of the finished page after trimming<ul><li>$this->pagedim[$this->page]['TrimBox']['llx'] = lower-left x coordinate in points</li><li>$this->pagedim[$this->page]['TrimBox']['lly'] = lower-left y coordinate in points</li><li>$this->pagedim[$this->page]['TrimBox']['urx'] = upper-right x coordinate in points</li><li>$this->pagedim[$this->page]['TrimBox']['ury'] = upper-right y coordinate in points</li></ul></li><li>$this->pagedim[$this->page]['ArtBox'] : the extent of the page's meaningful content<ul><li>$this->pagedim[$this->page]['ArtBox']['llx'] = lower-left x coordinate in points</li><li>$this->pagedim[$this->page]['ArtBox']['lly'] = lower-left y coordinate in points</li><li>$this->pagedim[$this->page]['ArtBox']['urx'] = upper-right x coordinate in points</li><li>$this->pagedim[$this->page]['ArtBox']['ury'] = upper-right y coordinate in points</li></ul></li></ul>
     * @param $pagenum (int) page number (empty = current page)
     * @return array of page dimensions.
     * @author Nicola Asuni
     * @public
     * @since 4.5.027 (2009-03-16)
     */
    public function getPageDimensions($pagenum='') {
        if (empty($pagenum)) {
            $pagenum = $this->page;
        }
        return $this->pagedim[$pagenum];
    }

    /**
     * Returns the page width in units.
     * @param $pagenum (int) page number (empty = current page)
     * @return int page width.
     * @author Nicola Asuni
     * @public
     * @since 1.5.2
     * @see getPageDimensions()
     */
    public function getPageWidth($pagenum='') {
        if (empty($pagenum)) {
            return $this->w;
        }
        return $this->pagedim[$pagenum]['w'];
    }

    /**
     * Returns the page height in units.
     * @param $pagenum (int) page number (empty = current page)
     * @return int page height.
     * @author Nicola Asuni
     * @public
     * @since 1.5.2
     * @see getPageDimensions()
     */
    public function getPageHeight($pagenum='') {
        if (empty($pagenum)) {
            return $this->h;
        }
        return $this->pagedim[$pagenum]['h'];
    }

    /**
     * Returns the page break margin.
     * @param $pagenum (int) page number (empty = current page)
     * @return int page break margin.
     * @author Nicola Asuni
     * @public
     * @since 1.5.2
     * @see getPageDimensions()
     */
    public function getBreakMargin($pagenum='') {
        if (empty($pagenum)) {
            return $this->bMargin;
        }
        return $this->pagedim[$pagenum]['bm'];
    }

    /**
     * Returns the scale factor (number of points in user unit).
     * @return int scale factor.
     * @author Nicola Asuni
     * @public
     * @since 1.5.2
     */
    public function getScaleFactor() {
        return $this->k;
    }

    /**
     * Defines the left, top and right margins.
     * @param $left (float) Left margin.
     * @param $top (float) Top margin.
     * @param $right (float) Right margin. Default value is the left one.
     * @param $keepmargins (boolean) if true overwrites the default page margins
     * @public
     * @since 1.0
     * @see SetLeftMargin(), SetTopMargin(), SetRightMargin(), SetAutoPageBreak()
     */
    public function SetMargins($left, $top, $right=-1, $keepmargins=false) {
        //Set left, top and right margins
        $this->lMargin = $left;
        $this->tMargin = $top;
        if ($right == -1) {
            $right = $left;
        }
        $this->rMargin = $right;
        if ($keepmargins) {
            // overwrite original values
            $this->original_lMargin = $this->lMargin;
            $this->original_rMargin = $this->rMargin;
        }
    }

    /**
     * Defines the left margin. The method can be called before creating the first page. If the current abscissa gets out of page, it is brought back to the margin.
     * @param $margin (float) The margin.
     * @public
     * @since 1.4
     * @see SetTopMargin(), SetRightMargin(), SetAutoPageBreak(), SetMargins()
     */
    public function SetLeftMargin($margin) {
        //Set left margin
        $this->lMargin = $margin;
        if (($this->page > 0) AND ($this->x < $margin)) {
            $this->x = $margin;
        }
    }

    /**
     * Defines the top margin. The method can be called before creating the first page.
     * @param $margin (float) The margin.
     * @public
     * @since 1.5
     * @see SetLeftMargin(), SetRightMargin(), SetAutoPageBreak(), SetMargins()
     */
    public function SetTopMargin($margin) {
        //Set top margin
        $this->tMargin = $margin;
        if (($this->page > 0) AND ($this->y < $margin)) {
            $this->y = $margin;
        }
    }

    /**
     * Defines the right margin. The method can be called before creating the first page.
     * @param $margin (float) The margin.
     * @public
     * @since 1.5
     * @see SetLeftMargin(), SetTopMargin(), SetAutoPageBreak(), SetMargins()
     */
    public function SetRightMargin($margin) {
        $this->rMargin = $margin;
        if (($this->page > 0) AND ($this->x > ($this->w - $margin))) {
            $this->x = $this->w - $margin;
        }
    }

    /**
     * Set the same internal Cell padding for top, right, bottom, left-
     * @param $pad (float) internal padding.
     * @public
     * @since 2.1.000 (2008-01-09)
     * @see getCellPaddings(), setCellPaddings()
     */
    public function SetCellPadding($pad) {
        if ($pad >= 0) {
            $this->cell_padding['L'] = $pad;
            $this->cell_padding['T'] = $pad;
            $this->cell_padding['R'] = $pad;
            $this->cell_padding['B'] = $pad;
        }
    }

    /**
     * Set the internal Cell paddings.
     * @param $left (float) left padding
     * @param $top (float) top padding
     * @param $right (float) right padding
     * @param $bottom (float) bottom padding
     * @public
     * @since 5.9.000 (2010-10-03)
     * @see getCellPaddings(), SetCellPadding()
     */
    public function setCellPaddings($left='', $top='', $right='', $bottom='') {
        if (($left !== '') AND ($left >= 0)) {
            $this->cell_padding['L'] = $left;
        }
        if (($top !== '') AND ($top >= 0)) {
            $this->cell_padding['T'] = $top;
        }
        if (($right !== '') AND ($right >= 0)) {
            $this->cell_padding['R'] = $right;
        }
        if (($bottom !== '') AND ($bottom >= 0)) {
            $this->cell_padding['B'] = $bottom;
        }
    }

    /**
     * Get the internal Cell padding array.
     * @return array of padding values
     * @public
     * @since 5.9.000 (2010-10-03)
     * @see setCellPaddings(), SetCellPadding()
     */
    public function getCellPaddings() {
        return $this->cell_padding;
    }

    /**
     * Set the internal Cell margins.
     * @param $left (float) left margin
     * @param $top (float) top margin
     * @param $right (float) right margin
     * @param $bottom (float) bottom margin
     * @public
     * @since 5.9.000 (2010-10-03)
     * @see getCellMargins()
     */
    public function setCellMargins($left='', $top='', $right='', $bottom='') {
        if (($left !== '') AND ($left >= 0)) {
            $this->cell_margin['L'] = $left;
        }
        if (($top !== '') AND ($top >= 0)) {
            $this->cell_margin['T'] = $top;
        }
        if (($right !== '') AND ($right >= 0)) {
            $this->cell_margin['R'] = $right;
        }
        if (($bottom !== '') AND ($bottom >= 0)) {
            $this->cell_margin['B'] = $bottom;
        }
    }

    /**
     * Get the internal Cell margin array.
     * @return array of margin values
     * @public
     * @since 5.9.000 (2010-10-03)
     * @see setCellMargins()
     */
    public function getCellMargins() {
        return $this->cell_margin;
    }

    /**
     * Adjust the internal Cell padding array to take account of the line width.
     * @param $brd (mixed) Indicates if borders must be drawn around the cell. The value can be a number:<ul><li>0: no border (default)</li><li>1: frame</li></ul> or a string containing some or all of the following characters (in any order):<ul><li>L: left</li><li>T: top</li><li>R: right</li><li>B: bottom</li></ul> or an array of line styles for each border group - for example: array('LTRB' => array('width' => 2, 'cap' => 'butt', 'join' => 'miter', 'dash' => 0, 'color' => array(0, 0, 0)))
     * @return array of adjustments
     * @public
     * @since 5.9.000 (2010-10-03)
     */
    protected function adjustCellPadding($brd=0) {
        if (empty($brd)) {
            return;
        }
        if (is_string($brd)) {
            // convert string to array
            $slen = strlen($brd);
            $newbrd = array();
            for ($i = 0; $i < $slen; ++$i) {
                $newbrd[$brd[$i]] = true;
            }
            $brd = $newbrd;
        } elseif (($brd === 1) OR ($brd === true) OR (is_numeric($brd) AND (intval($brd) > 0))) {
            $brd = array('LRTB' => true);
        }
        if (!is_array($brd)) {
            return;
        }
        // store current cell padding
        $cp = $this->cell_padding;
        // select border mode
        if (isset($brd['mode'])) {
            $mode = $brd['mode'];
            unset($brd['mode']);
        } else {
            $mode = 'normal';
        }
        // process borders
        foreach ($brd as $border => $style) {
            $line_width = $this->LineWidth;
            if (is_array($style) AND isset($style['width'])) {
                // get border width
                $line_width = $style['width'];
            }
            $adj = 0; // line width inside the cell
            switch ($mode) {
                case 'ext': {
                    $adj = 0;
                    break;
                }
                case 'int': {
                    $adj = $line_width;
                    break;
                }
                case 'normal':
                default: {
                    $adj = ($line_width / 2);
                    break;
                }
            }
            // correct internal cell padding if required to avoid overlap between text and lines
            if ((strpos($border,'T') !== false) AND ($this->cell_padding['T'] < $adj)) {
                $this->cell_padding['T'] = $adj;
            }
            if ((strpos($border,'R') !== false) AND ($this->cell_padding['R'] < $adj)) {
                $this->cell_padding['R'] = $adj;
            }
            if ((strpos($border,'B') !== false) AND ($this->cell_padding['B'] < $adj)) {
                $this->cell_padding['B'] = $adj;
            }
            if ((strpos($border,'L') !== false) AND ($this->cell_padding['L'] < $adj)) {
                $this->cell_padding['L'] = $adj;
            }
        }
        return array('T' => ($this->cell_padding['T'] - $cp['T']), 'R' => ($this->cell_padding['R'] - $cp['R']), 'B' => ($this->cell_padding['B'] - $cp['B']), 'L' => ($this->cell_padding['L'] - $cp['L']));
    }

    /**
     * Enables or disables the automatic page breaking mode. When enabling, the second parameter is the distance from the bottom of the page that defines the triggering limit. By default, the mode is on and the margin is 2 cm.
     * @param $auto (boolean) Boolean indicating if mode should be on or off.
     * @param $margin (float) Distance from the bottom of the page.
     * @public
     * @since 1.0
     * @see Cell(), MultiCell(), AcceptPageBreak()
     */
    public function SetAutoPageBreak($auto, $margin=0) {
        $this->AutoPageBreak = $auto ? true : false;
        $this->bMargin = $margin;
        $this->PageBreakTrigger = $this->h - $margin;
    }

    /**
     * Return the auto-page-break mode (true or false).
     * @return boolean auto-page-break mode
     * @public
     * @since 5.9.088
     */
    public function getAutoPageBreak() {
        return $this->AutoPageBreak;
    }

    /**
     * Defines the way the document is to be displayed by the viewer.
     * @param $zoom (mixed) The zoom to use. It can be one of the following string values or a number indicating the zooming factor to use. <ul><li>fullpage: displays the entire page on screen </li><li>fullwidth: uses maximum width of window</li><li>real: uses real size (equivalent to 100% zoom)</li><li>default: uses viewer default mode</li></ul>
     * @param $layout (string) The page layout. Possible values are:<ul><li>SinglePage Display one page at a time</li><li>OneColumn Display the pages in one column</li><li>TwoColumnLeft Display the pages in two columns, with odd-numbered pages on the left</li><li>TwoColumnRight Display the pages in two columns, with odd-numbered pages on the right</li><li>TwoPageLeft (PDF 1.5) Display the pages two at a time, with odd-numbered pages on the left</li><li>TwoPageRight (PDF 1.5) Display the pages two at a time, with odd-numbered pages on the right</li></ul>
     * @param $mode (string) A name object specifying how the document should be displayed when opened:<ul><li>UseNone Neither document outline nor thumbnail images visible</li><li>UseOutlines Document outline visible</li><li>UseThumbs Thumbnail images visible</li><li>FullScreen Full-screen mode, with no menu bar, window controls, or any other window visible</li><li>UseOC (PDF 1.5) Optional content group panel visible</li><li>UseAttachments (PDF 1.6) Attachments panel visible</li></ul>
     * @public
     * @since 1.2
     */
    public function SetDisplayMode($zoom, $layout='SinglePage', $mode='UseNone') {
        if (($zoom == 'fullpage') OR ($zoom == 'fullwidth') OR ($zoom == 'real') OR ($zoom == 'default') OR (!is_string($zoom))) {
            $this->ZoomMode = $zoom;
        } else {
            $this->Error('Incorrect zoom display mode: '.$zoom);
        }
        $this->LayoutMode = TCPDF_STATIC::getPageLayoutMode($layout);
        $this->PageMode = TCPDF_STATIC::getPageMode($mode);
    }

    /**
     * Activates or deactivates page compression. When activated, the internal representation of each page is compressed, which leads to a compression ratio of about 2 for the resulting document. Compression is on by default.
     * Note: the Zlib extension is required for this feature. If not present, compression will be turned off.
     * @param $compress (boolean) Boolean indicating if compression must be enabled.
     * @public
     * @since 1.4
     */
    public function SetCompression($compress=true) {
        if (function_exists('gzcompress')) {
            $this->compress = $compress ? true : false;
        } else {
            $this->compress = false;
        }
    }

    /**
     * Set flag to force sRGB_IEC61966-2.1 black scaled ICC color profile for the whole document.
     * @param $mode (boolean) If true force sRGB output intent.
     * @public
     * @since 5.9.121 (2011-09-28)
     */
    public function setSRGBmode($mode=false) {
        $this->force_srgb = $mode ? true : false;
    }

    /**
     * Turn on/off Unicode mode for document information dictionary (meta tags).
     * This has effect only when unicode mode is set to false.
     * @param $unicode (boolean) if true set the meta information in Unicode
     * @since 5.9.027 (2010-12-01)
     * @public
     */
    public function SetDocInfoUnicode($unicode=true) {
        $this->docinfounicode = $unicode ? true : false;
    }

    /**
     * Defines the title of the document.
     * @param $title (string) The title.
     * @public
     * @since 1.2
     * @see SetAuthor(), SetCreator(), SetKeywords(), SetSubject()
     */
    public function SetTitle($title) {
        $this->title = $title;
    }

    /**
     * Defines the subject of the document.
     * @param $subject (string) The subject.
     * @public
     * @since 1.2
     * @see SetAuthor(), SetCreator(), SetKeywords(), SetTitle()
     */
    public function SetSubject($subject) {
        $this->subject = $subject;
    }

    /**
     * Defines the author of the document.
     * @param $author (string) The name of the author.
     * @public
     * @since 1.2
     * @see SetCreator(), SetKeywords(), SetSubject(), SetTitle()
     */
    public function SetAuthor($author) {
        $this->author = $author;
    }

    /**
     * Associates keywords with the document, generally in the form 'keyword1 keyword2 ...'.
     * @param $keywords (string) The list of keywords.
     * @public
     * @since 1.2
     * @see SetAuthor(), SetCreator(), SetSubject(), SetTitle()
     */
    public function SetKeywords($keywords) {
        $this->keywords = $keywords;
    }

    /**
     * Defines the creator of the document. This is typically the name of the application that generates the PDF.
     * @param $creator (string) The name of the creator.
     * @public
     * @since 1.2
     * @see SetAuthor(), SetKeywords(), SetSubject(), SetTitle()
     */
    public function SetCreator($creator) {
        $this->creator = $creator;
    }

    /**
     * Throw an exception or print an error message and die if the K_TCPDF_PARSER_THROW_EXCEPTION_ERROR constant is set to true.
     * @param $msg (string) The error message
     * @public
     * @since 1.0
     */
    public function Error($msg) {
        // unset all class variables
        $this->_destroy(true);
        if (defined('K_TCPDF_THROW_EXCEPTION_ERROR') AND !K_TCPDF_THROW_EXCEPTION_ERROR) {
            die('<strong>TCPDF ERROR: </strong>'.$msg);
        } else {
            throw new Exception('TCPDF ERROR: '.$msg);
        }
    }

    /**
     * This method begins the generation of the PDF document.
     * It is not necessary to call it explicitly because AddPage() does it automatically.
     * Note: no page is created by this method
     * @public
     * @since 1.0
     * @see AddPage(), Close()
     */
    public function Open() {
        $this->state = 1;
    }

    /**
     * Terminates the PDF document.
     * It is not necessary to call this method explicitly because Output() does it automatically.
     * If the document contains no page, AddPage() is called to prevent from getting an invalid document.
     * @public
     * @since 1.0
     * @see Open(), Output()
     */
    public function Close() {
        if ($this->state == 3) {
            return;
        }
        if ($this->page == 0) {
            $this->AddPage();
        }
        $this->endLayer();
        if ($this->tcpdflink) {
            // save current graphic settings
            $gvars = $this->getGraphicVars();
            $this->setEqualColumns();
            $this->lastpage(true);
            $this->SetAutoPageBreak(false);
            $this->x = 0;
            $this->y = $this->h - (1 / $this->k);
            $this->lMargin = 0;
            $this->_outSaveGraphicsState();
            $font = defined('PDF_FONT_NAME_MAIN')?PDF_FONT_NAME_MAIN:'helvetica';
            $this->SetFont($font, '', 1);
            $this->setTextRenderingMode(0, false, false);
            $msg = "\x50\x6f\x77\x65\x72\x65\x64\x20\x62\x79\x20\x54\x43\x50\x44\x46\x20\x28\x77\x77\x77\x2e\x74\x63\x70\x64\x66\x2e\x6f\x72\x67\x29";
            $lnk = "\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\x77\x2e\x74\x63\x70\x64\x66\x2e\x6f\x72\x67";
            $this->Cell(0, 0, $msg, 0, 0, 'L', 0, $lnk, 0, false, 'D', 'B');
            $this->_outRestoreGraphicsState();
            // restore graphic settings
            $this->setGraphicVars($gvars);
        }
        // close page
        $this->endPage();
        // close document
        $this->_enddoc();
        // unset all class variables (except critical ones)
        $this->_destroy(false);
    }

    /**
     * Move pointer at the specified document page and update page dimensions.
     * @param $pnum (int) page number (1 ... numpages)
     * @param $resetmargins (boolean) if true reset left, right, top margins and Y position.
     * @public
     * @since 2.1.000 (2008-01-07)
     * @see getPage(), lastpage(), getNumPages()
     */
    public function setPage($pnum, $resetmargins=false) {
        if (($pnum == $this->page) AND ($this->state == 2)) {
            return;
        }
        if (($pnum > 0) AND ($pnum <= $this->numpages)) {
            $this->state = 2;
            // save current graphic settings
            //$gvars = $this->getGraphicVars();
            $oldpage = $this->page;
            $this->page = $pnum;
            $this->wPt = $this->pagedim[$this->page]['w'];
            $this->hPt = $this->pagedim[$this->page]['h'];
            $this->w = $this->pagedim[$this->page]['wk'];
            $this->h = $this->pagedim[$this->page]['hk'];
            $this->tMargin = $this->pagedim[$this->page]['tm'];
            $this->bMargin = $this->pagedim[$this->page]['bm'];
            $this->original_lMargin = $this->pagedim[$this->page]['olm'];
            $this->original_rMargin = $this->pagedim[$this->page]['orm'];
            $this->AutoPageBreak = $this->pagedim[$this->page]['pb'];
            $this->CurOrientation = $this->pagedim[$this->page]['or'];
            $this->SetAutoPageBreak($this->AutoPageBreak, $this->bMargin);
            // restore graphic settings
            //$this->setGraphicVars($gvars);
            if ($resetmargins) {
                $this->lMargin = $this->pagedim[$this->page]['olm'];
                $this->rMargin = $this->pagedim[$this->page]['orm'];
                $this->SetY($this->tMargin);
            } else {
                // account for booklet mode
                if ($this->pagedim[$this->page]['olm'] != $this->pagedim[$oldpage]['olm']) {
                    $deltam = $this->pagedim[$this->page]['olm'] - $this->pagedim[$this->page]['orm'];
                    $this->lMargin += $deltam;
                    $this->rMargin -= $deltam;
                }
            }
        } else {
            $this->Error('Wrong page number on setPage() function: '.$pnum);
        }
    }

    /**
     * Reset pointer to the last document page.
     * @param $resetmargins (boolean) if true reset left, right, top margins and Y position.
     * @public
     * @since 2.0.000 (2008-01-04)
     * @see setPage(), getPage(), getNumPages()
     */
    public function lastPage($resetmargins=false) {
        $this->setPage($this->getNumPages(), $resetmargins);
    }

    /**
     * Get current document page number.
     * @return int page number
     * @public
     * @since 2.1.000 (2008-01-07)
     * @see setPage(), lastpage(), getNumPages()
     */
    public function getPage() {
        return $this->page;
    }

    /**
     * Get the total number of insered pages.
     * @return int number of pages
     * @public
     * @since 2.1.000 (2008-01-07)
     * @see setPage(), getPage(), lastpage()
     */
    public function getNumPages() {
        return $this->numpages;
    }

    /**
     * Adds a new TOC (Table Of Content) page to the document.
     * @param $orientation (string) page orientation.
     * @param $format (mixed) The format used for pages. It can be either: one of the string values specified at getPageSizeFromFormat() or an array of parameters specified at setPageFormat().
     * @param $keepmargins (boolean) if true overwrites the default page margins with the current margins
     * @public
     * @since 5.0.001 (2010-05-06)
     * @see AddPage(), startPage(), endPage(), endTOCPage()
     */
    public function addTOCPage($orientation='', $format='', $keepmargins=false) {
        $this->AddPage($orientation, $format, $keepmargins, true);
    }

    /**
     * Terminate the current TOC (Table Of Content) page
     * @public
     * @since 5.0.001 (2010-05-06)
     * @see AddPage(), startPage(), endPage(), addTOCPage()
     */
    public function endTOCPage() {
        $this->endPage(true);
    }

    /**
     * Adds a new page to the document. If a page is already present, the Footer() method is called first to output the footer (if enabled). Then the page is added, the current position set to the top-left corner according to the left and top margins (or top-right if in RTL mode), and Header() is called to display the header (if enabled).
     * The origin of the coordinate system is at the top-left corner (or top-right for RTL) and increasing ordinates go downwards.
     * @param $orientation (string) page orientation. Possible values are (case insensitive):<ul><li>P or PORTRAIT (default)</li><li>L or LANDSCAPE</li></ul>
     * @param $format (mixed) The format used for pages. It can be either: one of the string values specified at getPageSizeFromFormat() or an array of parameters specified at setPageFormat().
     * @param $keepmargins (boolean) if true overwrites the default page margins with the current margins
     * @param $tocpage (boolean) if true set the tocpage state to true (the added page will be used to display Table Of Content).
     * @public
     * @since 1.0
     * @see startPage(), endPage(), addTOCPage(), endTOCPage(), getPageSizeFromFormat(), setPageFormat()
     */
    public function AddPage($orientation='', $format='', $keepmargins=false, $tocpage=false) {
        if ($this->inxobj) {
            // we are inside an XObject template
            return;
        }
        if (!isset($this->original_lMargin) OR $keepmargins) {
            $this->original_lMargin = $this->lMargin;
        }
        if (!isset($this->original_rMargin) OR $keepmargins) {
            $this->original_rMargin = $this->rMargin;
        }
        // terminate previous page
        $this->endPage();
        // start new page
        $this->startPage($orientation, $format, $tocpage);
    }

    /**
     * Terminate the current page
     * @param $tocpage (boolean) if true set the tocpage state to false (end the page used to display Table Of Content).
     * @public
     * @since 4.2.010 (2008-11-14)
     * @see AddPage(), startPage(), addTOCPage(), endTOCPage()
     */
    public function endPage($tocpage=false) {
        // check if page is already closed
        if (($this->page == 0) OR ($this->numpages > $this->page) OR (!$this->pageopen[$this->page])) {
            return;
        }
        // print page footer
        $this->setFooter();
        // close page
        $this->_endpage();
        // mark page as closed
        $this->pageopen[$this->page] = false;
        if ($tocpage) {
            $this->tocpage = false;
        }
    }

    /**
     * Starts a new page to the document. The page must be closed using the endPage() function.
     * The origin of the coordinate system is at the top-left corner and increasing ordinates go downwards.
     * @param $orientation (string) page orientation. Possible values are (case insensitive):<ul><li>P or PORTRAIT (default)</li><li>L or LANDSCAPE</li></ul>
     * @param $format (mixed) The format used for pages. It can be either: one of the string values specified at getPageSizeFromFormat() or an array of parameters specified at setPageFormat().
     * @param $tocpage (boolean) if true the page is designated to contain the Table-Of-Content.
     * @since 4.2.010 (2008-11-14)
     * @see AddPage(), endPage(), addTOCPage(), endTOCPage(), getPageSizeFromFormat(), setPageFormat()
     * @public
     */
    public function startPage($orientation='', $format='', $tocpage=false) {
        if ($tocpage) {
            $this->tocpage = true;
        }
        // move page numbers of documents to be attached
        if ($this->tocpage) {
            // move reference to unexistent pages (used for page attachments)
            // adjust outlines
            $tmpoutlines = $this->outlines;
            foreach ($tmpoutlines as $key => $outline) {
                if (!$outline['f'] AND ($outline['p'] > $this->numpages)) {
                    $this->outlines[$key]['p'] = ($outline['p'] + 1);
                }
            }
            // adjust dests
            $tmpdests = $this->dests;
            foreach ($tmpdests as $key => $dest) {
                if (!$dest['f'] AND ($dest['p'] > $this->numpages)) {
                    $this->dests[$key]['p'] = ($dest['p'] + 1);
                }
            }
            // adjust links
            $tmplinks = $this->links;
            foreach ($tmplinks as $key => $link) {
                if (!$link['f'] AND ($link['p'] > $this->numpages)) {
                    $this->links[$key]['p'] = ($link['p'] + 1);
                }
            }
        }
        if ($this->numpages > $this->page) {
            // this page has been already added
            $this->setPage($this->page + 1);
            $this->SetY($this->tMargin);
            return;
        }
        // start a new page
        if ($this->state == 0) {
            $this->Open();
        }
        ++$this->numpages;
        $this->swapMargins($this->booklet);
        // save current graphic settings
        $gvars = $this->getGraphicVars();
        // start new page
        $this->_beginpage($orientation, $format);
        // mark page as open
        $this->pageopen[$this->page] = true;
        // restore graphic settings
        $this->setGraphicVars($gvars);
        // mark this point
        $this->setPageMark();
        // print page header
        $this->setHeader();
        // restore graphic settings
        $this->setGraphicVars($gvars);
        // mark this point
        $this->setPageMark();
        // print table header (if any)
        $this->setTableHeader();
        // set mark for empty page check
        $this->emptypagemrk[$this->page]= $this->pagelen[$this->page];
    }

    /**
     * Set start-writing mark on current page stream used to put borders and fills.
     * Borders and fills are always created after content and inserted on the position marked by this method.
     * This function must be called after calling Image() function for a background image.
     * Background images must be always inserted before calling Multicell() or WriteHTMLCell() or WriteHTML() functions.
     * @public
     * @since 4.0.016 (2008-07-30)
     */
    public function setPageMark() {
        $this->intmrk[$this->page] = $this->pagelen[$this->page];
        $this->bordermrk[$this->page] = $this->intmrk[$this->page];
        $this->setContentMark();
    }

    /**
     * Set start-writing mark on selected page.
     * Borders and fills are always created after content and inserted on the position marked by this method.
     * @param $page (int) page number (default is the current page)
     * @protected
     * @since 4.6.021 (2009-07-20)
     */
    protected function setContentMark($page=0) {
        if ($page <= 0) {
            $page = $this->page;
        }
        if (isset($this->footerlen[$page])) {
            $this->cntmrk[$page] = $this->pagelen[$page] - $this->footerlen[$page];
        } else {
            $this->cntmrk[$page] = $this->pagelen[$page];
        }
    }

    /**
     * Set header data.
     * @param $ln (string) header image logo
     * @param $lw (string) header image logo width in mm
     * @param $ht (string) string to print as title on document header
     * @param $hs (string) string to print on document header
     * @param $tc (array) RGB array color for text.
     * @param $lc (array) RGB array color for line.
     * @public
     */
    public function setHeaderData($ln='', $lw=0, $ht='', $hs='', $tc=array(0,0,0), $lc=array(0,0,0)) {
        $this->header_logo = $ln;
        $this->header_logo_width = $lw;
        $this->header_title = $ht;
        $this->header_string = $hs;
        $this->header_text_color = $tc;
        $this->header_line_color = $lc;
    }

    /**
     * Set footer data.
     * @param $tc (array) RGB array color for text.
     * @param $lc (array) RGB array color for line.
     * @public
     */
    public function setFooterData($tc=array(0,0,0), $lc=array(0,0,0)) {
        $this->footer_text_color = $tc;
        $this->footer_line_color = $lc;
    }

    /**
     * Returns header data:
     * <ul><li>$ret['logo'] = logo image</li><li>$ret['logo_width'] = width of the image logo in user units</li><li>$ret['title'] = header title</li><li>$ret['string'] = header description string</li></ul>
     * @return array()
     * @public
     * @since 4.0.012 (2008-07-24)
     */
    public function getHeaderData() {
        $ret = array();
        $ret['logo'] = $this->header_logo;
        $ret['logo_width'] = $this->header_logo_width;
        $ret['title'] = $this->header_title;
        $ret['string'] = $this->header_string;
        $ret['text_color'] = $this->header_text_color;
        $ret['line_color'] = $this->header_line_color;
        return $ret;
    }

    /**
     * Set header margin.
     * (minimum distance between header and top page margin)
     * @param $hm (int) distance in user units
     * @public
     */
    public function setHeaderMargin($hm=10) {
        $this->header_margin = $hm;
    }

    /**
     * Returns header margin in user units.
     * @return float
     * @since 4.0.012 (2008-07-24)
     * @public
     */
    public function getHeaderMargin() {
        return $this->header_margin;
    }

    /**
     * Set footer margin.
     * (minimum distance between footer and bottom page margin)
     * @param $fm (int) distance in user units
     * @public
     */
    public function setFooterMargin($fm=10) {
        $this->footer_margin = $fm;
    }

    /**
     * Returns footer margin in user units.
     * @return float
     * @since 4.0.012 (2008-07-24)
     * @public
     */
    public function getFooterMargin() {
        return $this->footer_margin;
    }
    /**
     * Set a flag to print page header.
     * @param $val (boolean) set to true to print the page header (default), false otherwise.
     * @public
     */
    public function setPrintHeader($val=true) {
        $this->print_header = $val ? true : false;
    }

    /**
     * Set a flag to print page footer.
     * @param $val (boolean) set to true to print the page footer (default), false otherwise.
     * @public
     */
    public function setPrintFooter($val=true) {
        $this->print_footer = $val ? true : false;
    }

    /**
     * Return the right-bottom (or left-bottom for RTL) corner X coordinate of last inserted image
     * @return float
     * @public
     */
    public function getImageRBX() {
        return $this->img_rb_x;
    }

    /**
     * Return the right-bottom (or left-bottom for RTL) corner Y coordinate of last inserted image
     * @return float
     * @public
     */
    public function getImageRBY() {
        return $this->img_rb_y;
    }

    /**
     * Reset the xobject template used by Header() method.
     * @public
     */
    public function resetHeaderTemplate() {
        $this->header_xobjid = false;
    }

    /**
     * Set a flag to automatically reset the xobject template used by Header() method at each page.
     * @param $val (boolean) set to true to reset Header xobject template at each page, false otherwise.
     * @public
     */
    public function setHeaderTemplateAutoreset($val=true) {
        $this->header_xobj_autoreset = $val ? true : false;
    }

    /**
     * This method is used to render the page header.
     * It is automatically called by AddPage() and could be overwritten in your own inherited class.
     * @public
     */
    public function Header() {
        if ($this->header_xobjid === false) {
            // start a new XObject Template
            $this->header_xobjid = $this->startTemplate($this->w, $this->tMargin);
            $headerfont = $this->getHeaderFont();
            $headerdata = $this->getHeaderData();
            $this->y = $this->header_margin;
            if ($this->rtl) {
                $this->x = $this->w - $this->original_rMargin;
            } else {
                $this->x = $this->original_lMargin;
            }
            if (($headerdata['logo']) AND ($headerdata['logo'] != K_BLANK_IMAGE)) {
                $imgtype = TCPDF_IMAGES::getImageFileType(K_PATH_IMAGES.$headerdata['logo']);
                if (($imgtype == 'eps') OR ($imgtype == 'ai')) {
                    $this->ImageEps(K_PATH_IMAGES.$headerdata['logo'], '', '', $headerdata['logo_width']);
                } elseif ($imgtype == 'svg') {
                    $this->ImageSVG(K_PATH_IMAGES.$headerdata['logo'], '', '', $headerdata['logo_width']);
                } else {
                    $this->Image(K_PATH_IMAGES.$headerdata['logo'], '', '', $headerdata['logo_width']);
                }
                $imgy = $this->getImageRBY();
            } else {
                $imgy = $this->y;
            }
            $cell_height = $this->getCellHeight($headerfont[2] / $this->k);
            // set starting margin for text data cell
            if ($this->getRTL()) {
                $header_x = $this->original_rMargin + ($headerdata['logo_width'] * 1.1);
            } else {
                $header_x = $this->original_lMargin + ($headerdata['logo_width'] * 1.1);
            }
            $cw = $this->w - $this->original_lMargin - $this->original_rMargin - ($headerdata['logo_width'] * 1.1);
            $this->SetTextColorArray($this->header_text_color);
            // header title
            $this->SetFont($headerfont[0], 'B', $headerfont[2] + 1);
            $this->SetX($header_x);
            $this->Cell($cw, $cell_height, $headerdata['title'], 0, 1, '', 0, '', 0);
            // header string
            $this->SetFont($headerfont[0], $headerfont[1], $headerfont[2]);
            $this->SetX($header_x);
            $this->MultiCell($cw, $cell_height, $headerdata['string'], 0, '', 0, 1, '', '', true, 0, false, true, 0, 'T', false);
            // print an ending header line
            $this->SetLineStyle(array('width' => 0.85 / $this->k, 'cap' => 'butt', 'join' => 'miter', 'dash' => 0, 'color' => $headerdata['line_color']));
            $this->SetY((2.835 / $this->k) + max($imgy, $this->y));
            if ($this->rtl) {
                $this->SetX($this->original_rMargin);
            } else {
                $this->SetX($this->original_lMargin);
            }
            $this->Cell(($this->w - $this->original_lMargin - $this->original_rMargin), 0, '', 'T', 0, 'C');
            $this->endTemplate();
        }
        // print header template
        $x = 0;
        $dx = 0;
        if (!$this->header_xobj_autoreset AND $this->booklet AND (($this->page % 2) == 0)) {
            // adjust margins for booklet mode
            $dx = ($this->original_lMargin - $this->original_rMargin);
        }
        if ($this->rtl) {
            $x = $this->w + $dx;
        } else {
            $x = 0 + $dx;
        }
        $this->printTemplate($this->header_xobjid, $x, 0, 0, 0, '', '', false);
        if ($this->header_xobj_autoreset) {
            // reset header xobject template at each page
            $this->header_xobjid = false;
        }
    }

    /**
     * This method is used to render the page footer.
     * It is automatically called by AddPage() and could be overwritten in your own inherited class.
     * @public
     */
    public function Footer() {
        $cur_y = $this->y;
        $this->SetTextColorArray($this->footer_text_color);
        //set style for cell border
        $line_width = (0.85 / $this->k);
        $this->SetLineStyle(array('width' => $line_width, 'cap' => 'butt', 'join' => 'miter', 'dash' => 0, 'color' => $this->footer_line_color));
        //print document barcode
        $barcode = $this->getBarcode();
        if (!empty($barcode)) {
            $this->Ln($line_width);
            $barcode_width = round(($this->w - $this->original_lMargin - $this->original_rMargin) / 3);
            $style = array(
                'position' => $this->rtl?'R':'L',
                'align' => $this->rtl?'R':'L',
                'stretch' => false,
                'fitwidth' => true,
                'cellfitalign' => '',
                'border' => false,
                'padding' => 0,
                'fgcolor' => array(0,0,0),
                'bgcolor' => false,
                'text' => false
            );
            $this->write1DBarcode($barcode, 'C128', '', $cur_y + $line_width, '', (($this->footer_margin / 3) - $line_width), 0.3, $style, '');
        }
        $w_page = isset($this->l['w_page']) ? $this->l['w_page'].' ' : '';
        if (empty($this->pagegroups)) {
            $pagenumtxt = $w_page.$this->getAliasNumPage().' / '.$this->getAliasNbPages();
        } else {
            $pagenumtxt = $w_page.$this->getPageNumGroupAlias().' / '.$this->getPageGroupAlias();
        }
        $this->SetY($cur_y);
        //Print page number
        if ($this->getRTL()) {
            $this->SetX($this->original_rMargin);
            $this->Cell(0, 0, $pagenumtxt, 'T', 0, 'L');
        } else {
            $this->SetX($this->original_lMargin);
            $this->Cell(0, 0, $this->getAliasRightShift().$pagenumtxt, 'T', 0, 'R');
        }
    }

    /**
     * This method is used to render the page header.
     * @protected
     * @since 4.0.012 (2008-07-24)
     */
    protected function setHeader() {
        if (!$this->print_header OR ($this->state != 2)) {
            return;
        }
        $this->InHeader = true;
        $this->setGraphicVars($this->default_graphic_vars);
        $temp_thead = $this->thead;
        $temp_theadMargins = $this->theadMargins;
        $lasth = $this->lasth;
        $newline = $this->newline;
        $this->_outSaveGraphicsState();
        $this->rMargin = $this->original_rMargin;
        $this->lMargin = $this->original_lMargin;
        $this->SetCellPadding(0);
        //set current position
        if ($this->rtl) {
            $this->SetXY($this->original_rMargin, $this->header_margin);
        } else {
            $this->SetXY($this->original_lMargin, $this->header_margin);
        }
        $this->SetFont($this->header_font[0], $this->header_font[1], $this->header_font[2]);
        $this->Header();
        //restore position
        if ($this->rtl) {
            $this->SetXY($this->original_rMargin, $this->tMargin);
        } else {
            $this->SetXY($this->original_lMargin, $this->tMargin);
        }
        $this->_outRestoreGraphicsState();
        $this->lasth = $lasth;
        $this->thead = $temp_thead;
        $this->theadMargins = $temp_theadMargins;
        $this->newline = $newline;
        $this->InHeader = false;
    }

    /**
     * This method is used to render the page footer.
     * @protected
     * @since 4.0.012 (2008-07-24)
     */
    protected function setFooter() {
        if ($this->state != 2) {
            return;
        }
        $this->InFooter = true;
        // save current graphic settings
        $gvars = $this->getGraphicVars();
        // mark this point
        $this->footerpos[$this->page] = $this->pagelen[$this->page];
        $this->_out("\n");
        if ($this->print_footer) {
            $this->setGraphicVars($this->default_graphic_vars);
            $this->current_column = 0;
            $this->num_columns = 1;
            $temp_thead = $this->thead;
            $temp_theadMargins = $this->theadMargins;
            $lasth = $this->lasth;
            $this->_outSaveGraphicsState();
            $this->rMargin = $this->original_rMargin;
            $this->lMargin = $this->original_lMargin;
            $this->SetCellPadding(0);
            //set current position
            $footer_y = $this->h - $this->footer_margin;
            if ($this->rtl) {
                $this->SetXY($this->original_rMargin, $footer_y);
            } else {
                $this->SetXY($this->original_lMargin, $footer_y);
            }
            $this->SetFont($this->footer_font[0], $this->footer_font[1], $this->footer_font[2]);
            $this->Footer();
            //restore position
            if ($this->rtl) {
                $this->SetXY($this->original_rMargin, $this->tMargin);
            } else {
                $this->SetXY($this->original_lMargin, $this->tMargin);
            }
            $this->_outRestoreGraphicsState();
            $this->lasth = $lasth;
            $this->thead = $temp_thead;
            $this->theadMargins = $temp_theadMargins;
        }
        // restore graphic settings
        $this->setGraphicVars($gvars);
        $this->current_column = $gvars['current_column'];
        $this->num_columns = $gvars['num_columns'];
        // calculate footer length
        $this->footerlen[$this->page] = $this->pagelen[$this->page] - $this->footerpos[$this->page] + 1;
        $this->InFooter = false;
    }

    /**
     * Check if we are on the page body (excluding page header and footer).
     * @return true if we are not in page header nor in page footer, false otherwise.
     * @protected
     * @since 5.9.091 (2011-06-15)
     */
    protected function inPageBody() {
        return (($this->InHeader === false) AND ($this->InFooter === false));
    }

    /**
     * This method is used to render the table header on new page (if any).
     * @protected
     * @since 4.5.030 (2009-03-25)
     */
    protected function setTableHeader() {
        if ($this->num_columns > 1) {
            // multi column mode
            return;
        }
        if (isset($this->theadMargins['top'])) {
            // restore the original top-margin
            $this->tMargin = $this->theadMargins['top'];
            $this->pagedim[$this->page]['tm'] = $this->tMargin;
            $this->y = $this->tMargin;
        }
        if (!TCPDF_STATIC::empty_string($this->thead) AND (!$this->inthead)) {
            // set margins
            $prev_lMargin = $this->lMargin;
            $prev_rMargin = $this->rMargin;
            $prev_cell_padding = $this->cell_padding;
            $this->lMargin = $this->theadMargins['lmargin'] + ($this->pagedim[$this->page]['olm'] - $this->pagedim[$this->theadMargins['page']]['olm']);
            $this->rMargin = $this->theadMargins['rmargin'] + ($this->pagedim[$this->page]['orm'] - $this->pagedim[$this->theadMargins['page']]['orm']);
            $this->cell_padding = $this->theadMargins['cell_padding'];
            if ($this->rtl) {
                $this->x = $this->w - $this->rMargin;
            } else {
                $this->x = $this->lMargin;
            }
            // account for special "cell" mode
            if ($this->theadMargins['cell']) {
                if ($this->rtl) {
                    $this->x -= $this->cell_padding['R'];
                } else {
                    $this->x += $this->cell_padding['L'];
                }
            }
            $gvars = $this->getGraphicVars();
            if (!empty($this->theadMargins['gvars'])) {
                // set the correct graphic style
                $this->setGraphicVars($this->theadMargins['gvars']);
                $this->rMargin = $gvars['rMargin'];
                $this->lMargin = $gvars['lMargin'];
            }
            // print table header
            $this->writeHTML($this->thead, false, false, false, false, '');
            $this->setGraphicVars($gvars);
            // set new top margin to skip the table headers
            if (!isset($this->theadMargins['top'])) {
                $this->theadMargins['top'] = $this->tMargin;
            }
            // store end of header position
            if (!isset($this->columns[0]['th'])) {
                $this->columns[0]['th'] = array();
            }
            $this->columns[0]['th']['\''.$this->page.'\''] = $this->y;
            $this->tMargin = $this->y;
            $this->pagedim[$this->page]['tm'] = $this->tMargin;
            $this->lasth = 0;
            $this->lMargin = $prev_lMargin;
            $this->rMargin = $prev_rMargin;
            $this->cell_padding = $prev_cell_padding;
        }
    }

    /**
     * Returns the current page number.
     * @return int page number
     * @public
     * @since 1.0
     * @see getAliasNbPages()
     */
    public function PageNo() {
        return $this->page;
    }

    /**
     * Returns the array of spot colors.
     * @return (array) Spot colors array.
     * @public
     * @since 6.0.038 (2013-09-30)
     */
    public function getAllSpotColors() {
        return $this->spot_colors;
    }

    /**
     * Defines a new spot color.
     * It can be expressed in RGB components or gray scale.
     * The method can be called before the first page is created and the value is retained from page to page.
     * @param $name (string) Full name of the spot color.
     * @param $c (float) Cyan color for CMYK. Value between 0 and 100.
     * @param $m (float) Magenta color for CMYK. Value between 0 and 100.
     * @param $y (float) Yellow color for CMYK. Value between 0 and 100.
     * @param $k (float) Key (Black) color for CMYK. Value between 0 and 100.
     * @public
     * @since 4.0.024 (2008-09-12)
     * @see SetDrawSpotColor(), SetFillSpotColor(), SetTextSpotColor()
     */
    public function AddSpotColor($name, $c, $m, $y, $k) {
        if (!isset($this->spot_colors[$name])) {
            $i = (1 + count($this->spot_colors));
            $this->spot_colors[$name] = array('C' => $c, 'M' => $m, 'Y' => $y, 'K' => $k, 'name' => $name, 'i' => $i);
        }
    }

    /**
     * Set the spot color for the specified type ('draw', 'fill', 'text').
     * @param $type (string) Type of object affected by this color: ('draw', 'fill', 'text').
     * @param $name (string) Name of the spot color.
     * @param $tint (float) Intensity of the color (from 0 to 100 ; 100 = full intensity by default).
     * @return (string) PDF color command.
     * @public
     * @since 5.9.125 (2011-10-03)
     */
    public function setSpotColor($type, $name, $tint=100) {
        $spotcolor = TCPDF_COLORS::getSpotColor($name, $this->spot_colors);
        if ($spotcolor === false) {
            $this->Error('Undefined spot color: '.$name.', you must add it using the AddSpotColor() method.');
        }
        $tint = (max(0, min(100, $tint)) / 100);
        $pdfcolor = sprintf('/CS%d ', $this->spot_colors[$name]['i']);
        switch ($type) {
            case 'draw': {
                $pdfcolor .= sprintf('CS %F SCN', $tint);
                $this->DrawColor = $pdfcolor;
                $this->strokecolor = $spotcolor;
                break;
            }
            case 'fill': {
                $pdfcolor .= sprintf('cs %F scn', $tint);
                $this->FillColor = $pdfcolor;
                $this->bgcolor = $spotcolor;
                break;
            }
            case 'text': {
                $pdfcolor .= sprintf('cs %F scn', $tint);
                $this->TextColor = $pdfcolor;
                $this->fgcolor = $spotcolor;
                break;
            }
        }
        $this->ColorFlag = ($this->FillColor != $this->TextColor);
        if ($this->state == 2) {
            $this->_out($pdfcolor);
        }
        if ($this->inxobj) {
            // we are inside an XObject template
            $this->xobjects[$this->xobjid]['spot_colors'][$name] = $this->spot_colors[$name];
        }
        return $pdfcolor;
    }

    /**
     * Defines the spot color used for all drawing operations (lines, rectangles and cell borders).
     * @param $name (string) Name of the spot color.
     * @param $tint (float) Intensity of the color (from 0 to 100 ; 100 = full intensity by default).
     * @public
     * @since 4.0.024 (2008-09-12)
     * @see AddSpotColor(), SetFillSpotColor(), SetTextSpotColor()
     */
    public function SetDrawSpotColor($name, $tint=100) {
        $this->setSpotColor('draw', $name, $tint);
    }

    /**
     * Defines the spot color used for all filling operations (filled rectangles and cell backgrounds).
     * @param $name (string) Name of the spot color.
     * @param $tint (float) Intensity of the color (from 0 to 100 ; 100 = full intensity by default).
     * @public
     * @since 4.0.024 (2008-09-12)
     * @see AddSpotColor(), SetDrawSpotColor(), SetTextSpotColor()
     */
    public function SetFillSpotColor($name, $tint=100) {
        $this->setSpotColor('fill', $name, $tint);
    }

    /**
     * Defines the spot color used for text.
     * @param $name (string) Name of the spot color.
     * @param $tint (int) Intensity of the color (from 0 to 100 ; 100 = full intensity by default).
     * @public
     * @since 4.0.024 (2008-09-12)
     * @see AddSpotColor(), SetDrawSpotColor(), SetFillSpotColor()
     */
    public function SetTextSpotColor($name, $tint=100) {
        $this->setSpotColor('text', $name, $tint);
    }

    /**
     * Set the color array for the specified type ('draw', 'fill', 'text').
     * It can be expressed in RGB, CMYK or GRAY SCALE components.
     * The method can be called before the first page is created and the value is retained from page to page.
     * @param $type (string) Type of object affected by this color: ('draw', 'fill', 'text').
     * @param $color (array) Array of colors (1=gray, 3=RGB, 4=CMYK or 5=spotcolor=CMYK+name values).
     * @param $ret (boolean) If true do not send the PDF command.
     * @return (string) The PDF command or empty string.
     * @public
     * @since 3.1.000 (2008-06-11)
     */
    public function setColorArray($type, $color, $ret=false) {
        if (is_array($color)) {
            $color = array_values($color);
            // component: grey, RGB red or CMYK cyan
            $c = isset($color[0]) ? $color[0] : -1;
            // component: RGB green or CMYK magenta
            $m = isset($color[1]) ? $color[1] : -1;
            // component: RGB blue or CMYK yellow
            $y = isset($color[2]) ? $color[2] : -1;
            // component: CMYK black
            $k = isset($color[3]) ? $color[3] : -1;
            // color name
            $name = isset($color[4]) ? $color[4] : '';
            if ($c >= 0) {
                return $this->setColor($type, $c, $m, $y, $k, $ret, $name);
            }
        }
        return '';
    }

    /**
     * Defines the color used for all drawing operations (lines, rectangles and cell borders).
     * It can be expressed in RGB, CMYK or GRAY SCALE components.
     * The method can be called before the first page is created and the value is retained from page to page.
     * @param $color (array) Array of colors (1, 3 or 4 values).
     * @param $ret (boolean) If true do not send the PDF command.
     * @return string the PDF command
     * @public
     * @since 3.1.000 (2008-06-11)
     * @see SetDrawColor()
     */
    public function SetDrawColorArray($color, $ret=false) {
        return $this->setColorArray('draw', $color, $ret);
    }

    /**
     * Defines the color used for all filling operations (filled rectangles and cell backgrounds).
     * It can be expressed in RGB, CMYK or GRAY SCALE components.
     * The method can be called before the first page is created and the value is retained from page to page.
     * @param $color (array) Array of colors (1, 3 or 4 values).
     * @param $ret (boolean) If true do not send the PDF command.
     * @public
     * @since 3.1.000 (2008-6-11)
     * @see SetFillColor()
     */
    public function SetFillColorArray($color, $ret=false) {
        return $this->setColorArray('fill', $color, $ret);
    }

    /**
     * Defines the color used for text. It can be expressed in RGB components or gray scale.
     * The method can be called before the first page is created and the value is retained from page to page.
     * @param $color (array) Array of colors (1, 3 or 4 values).
     * @param $ret (boolean) If true do not send the PDF command.
     * @public
     * @since 3.1.000 (2008-6-11)
     * @see SetFillColor()
     */
    public function SetTextColorArray($color, $ret=false) {
        return $this->setColorArray('text', $color, $ret);
    }

    /**
     * Defines the color used by the specified type ('draw', 'fill', 'text').
     * @param $type (string) Type of object affected by this color: ('draw', 'fill', 'text').
     * @param $col1 (float) GRAY level for single color, or Red color for RGB (0-255), or CYAN color for CMYK (0-100).
     * @param $col2 (float) GREEN color for RGB (0-255), or MAGENTA color for CMYK (0-100).
     * @param $col3 (float) BLUE color for RGB (0-255), or YELLOW color for CMYK (0-100).
     * @param $col4 (float) KEY (BLACK) color for CMYK (0-100).
     * @param $ret (boolean) If true do not send the command.
     * @param $name (string) spot color name (if any)
     * @return (string) The PDF command or empty string.
     * @public
     * @since 5.9.125 (2011-10-03)
     */
    public function setColor($type, $col1=0, $col2=-1, $col3=-1, $col4=-1, $ret=false, $name='') {
        // set default values
        if (!is_numeric($col1)) {
            $col1 = 0;
        }
        if (!is_numeric($col2)) {
            $col2 = -1;
        }
        if (!is_numeric($col3)) {
            $col3 = -1;
        }
        if (!is_numeric($col4)) {
            $col4 = -1;
        }
        // set color by case
        $suffix = '';
        if (($col2 == -1) AND ($col3 == -1) AND ($col4 == -1)) {
            // Grey scale
            $col1 = max(0, min(255, $col1));
            $intcolor = array('G' => $col1);
            $pdfcolor = sprintf('%F ', ($col1 / 255));
            $suffix = 'g';
        } elseif ($col4 == -1) {
            // RGB
            $col1 = max(0, min(255, $col1));
            $col2 = max(0, min(255, $col2));
            $col3 = max(0, min(255, $col3));
            $intcolor = array('R' => $col1, 'G' => $col2, 'B' => $col3);
            $pdfcolor = sprintf('%F %F %F ', ($col1 / 255), ($col2 / 255), ($col3 / 255));
            $suffix = 'rg';
        } else {
            $col1 = max(0, min(100, $col1));
            $col2 = max(0, min(100, $col2));
            $col3 = max(0, min(100, $col3));
            $col4 = max(0, min(100, $col4));
            if (empty($name)) {
                // CMYK
                $intcolor = array('C' => $col1, 'M' => $col2, 'Y' => $col3, 'K' => $col4);
                $pdfcolor = sprintf('%F %F %F %F ', ($col1 / 100), ($col2 / 100), ($col3 / 100), ($col4 / 100));
                $suffix = 'k';
            } else {
                // SPOT COLOR
                $intcolor = array('C' => $col1, 'M' => $col2, 'Y' => $col3, 'K' => $col4, 'name' => $name);
                $this->AddSpotColor($name, $col1, $col2, $col3, $col4);
                $pdfcolor = $this->setSpotColor($type, $name, 100);
            }
        }
        switch ($type) {
            case 'draw': {
                $pdfcolor .= strtoupper($suffix);
                $this->DrawColor = $pdfcolor;
                $this->strokecolor = $intcolor;
                break;
            }
            case 'fill': {
                $pdfcolor .= $suffix;
                $this->FillColor = $pdfcolor;
                $this->bgcolor = $intcolor;
                break;
            }
            case 'text': {
                $pdfcolor .= $suffix;
                $this->TextColor = $pdfcolor;
                $this->fgcolor = $intcolor;
                break;
            }
        }
        $this->ColorFlag = ($this->FillColor != $this->TextColor);
        if (($type != 'text') AND ($this->state == 2)) {
            if (!$ret) {
                $this->_out($pdfcolor);
            }
            return $pdfcolor;
        }
        return '';
    }

    /**
     * Defines the color used for all drawing operations (lines, rectangles and cell borders). It can be expressed in RGB components or gray scale. The method can be called before the first page is created and the value is retained from page to page.
     * @param $col1 (float) GRAY level for single color, or Red color for RGB (0-255), or CYAN color for CMYK (0-100).
     * @param $col2 (float) GREEN color for RGB (0-255), or MAGENTA color for CMYK (0-100).
     * @param $col3 (float) BLUE color for RGB (0-255), or YELLOW color for CMYK (0-100).
     * @param $col4 (float) KEY (BLACK) color for CMYK (0-100).
     * @param $ret (boolean) If true do not send the command.
     * @param $name (string) spot color name (if any)
     * @return string the PDF command
     * @public
     * @since 1.3
     * @see SetDrawColorArray(), SetFillColor(), SetTextColor(), Line(), Rect(), Cell(), MultiCell()
     */
    public function SetDrawColor($col1=0, $col2=-1, $col3=-1, $col4=-1, $ret=false, $name='') {
        return $this->setColor('draw', $col1, $col2, $col3, $col4, $ret, $name);
    }

    /**
     * Defines the color used for all filling operations (filled rectangles and cell backgrounds). It can be expressed in RGB components or gray scale. The method can be called before the first page is created and the value is retained from page to page.
     * @param $col1 (float) GRAY level for single color, or Red color for RGB (0-255), or CYAN color for CMYK (0-100).
     * @param $col2 (float) GREEN color for RGB (0-255), or MAGENTA color for CMYK (0-100).
     * @param $col3 (float) BLUE color for RGB (0-255), or YELLOW color for CMYK (0-100).
     * @param $col4 (float) KEY (BLACK) color for CMYK (0-100).
     * @param $ret (boolean) If true do not send the command.
     * @param $name (string) Spot color name (if any).
     * @return (string) The PDF command.
     * @public
     * @since 1.3
     * @see SetFillColorArray(), SetDrawColor(), SetTextColor(), Rect(), Cell(), MultiCell()
     */
    public function SetFillColor($col1=0, $col2=-1, $col3=-1, $col4=-1, $ret=false, $name='') {
        return $this->setColor('fill', $col1, $col2, $col3, $col4, $ret, $name);
    }

    /**
     * Defines the color used for text. It can be expressed in RGB components or gray scale. The method can be called before the first page is created and the value is retained from page to page.
     * @param $col1 (float) GRAY level for single color, or Red color for RGB (0-255), or CYAN color for CMYK (0-100).
     * @param $col2 (float) GREEN color for RGB (0-255), or MAGENTA color for CMYK (0-100).
     * @param $col3 (float) BLUE color for RGB (0-255), or YELLOW color for CMYK (0-100).
     * @param $col4 (float) KEY (BLACK) color for CMYK (0-100).
     * @param $ret (boolean) If true do not send the command.
     * @param $name (string) Spot color name (if any).
     * @return (string) Empty string.
     * @public
     * @since 1.3
     * @see SetTextColorArray(), SetDrawColor(), SetFillColor(), Text(), Cell(), MultiCell()
     */
    public function SetTextColor($col1=0, $col2=-1, $col3=-1, $col4=-1, $ret=false, $name='') {
        return $this->setColor('text', $col1, $col2, $col3, $col4, $ret, $name);
    }

    /**
     * Returns the length of a string in user unit. A font must be selected.<br>
     * @param $s (string) The string whose length is to be computed
     * @param $fontname (string) Family font. It can be either a name defined by AddFont() or one of the standard families. It is also possible to pass an empty string, in that case, the current family is retained.
     * @param $fontstyle (string) Font style. Possible values are (case insensitive):<ul><li>empty string: regular</li><li>B: bold</li><li>I: italic</li><li>U: underline</li><li>D: line-through</li><li>O: overline</li></ul> or any combination. The default value is regular.
     * @param $fontsize (float) Font size in points. The default value is the current size.
     * @param $getarray (boolean) if true returns an array of characters widths, if false returns the total length.
     * @return mixed int total string length or array of characted widths
     * @author Nicola Asuni
     * @public
     * @since 1.2
     */
    public function GetStringWidth($s, $fontname='', $fontstyle='', $fontsize=0, $getarray=false) {
        return $this->GetArrStringWidth(TCPDF_FONTS::utf8Bidi(TCPDF_FONTS::UTF8StringToArray($s, $this->isunicode, $this->CurrentFont), $s, $this->tmprtl, $this->isunicode, $this->CurrentFont), $fontname, $fontstyle, $fontsize, $getarray);
    }

    /**
     * Returns the string length of an array of chars in user unit or an array of characters widths. A font must be selected.<br>
     * @param $sa (string) The array of chars whose total length is to be computed
     * @param $fontname (string) Family font. It can be either a name defined by AddFont() or one of the standard families. It is also possible to pass an empty string, in that case, the current family is retained.
     * @param $fontstyle (string) Font style. Possible values are (case insensitive):<ul><li>empty string: regular</li><li>B: bold</li><li>I: italic</li><li>U: underline</li><li>D: line through</li><li>O: overline</li></ul> or any combination. The default value is regular.
     * @param $fontsize (float) Font size in points. The default value is the current size.
     * @param $getarray (boolean) if true returns an array of characters widths, if false returns the total length.
     * @return mixed int total string length or array of characted widths
     * @author Nicola Asuni
     * @public
     * @since 2.4.000 (2008-03-06)
     */
    public function GetArrStringWidth($sa, $fontname='', $fontstyle='', $fontsize=0, $getarray=false) {
        // store current values
        if (!TCPDF_STATIC::empty_string($fontname)) {
            $prev_FontFamily = $this->FontFamily;
            $prev_FontStyle = $this->FontStyle;
            $prev_FontSizePt = $this->FontSizePt;
            $this->SetFont($fontname, $fontstyle, $fontsize, '', 'default', false);
        }
        // convert UTF-8 array to Latin1 if required
        if ($this->isunicode AND (!$this->isUnicodeFont())) {
            $sa = TCPDF_FONTS::UTF8ArrToLatin1Arr($sa);
        }
        $w = 0; // total width
        $wa = array(); // array of characters widths
        foreach ($sa as $ck => $char) {
            // character width
            $cw = $this->GetCharWidth($char, isset($sa[($ck + 1)]));
            $wa[] = $cw;
            $w += $cw;
        }
        // restore previous values
        if (!TCPDF_STATIC::empty_string($fontname)) {
            $this->SetFont($prev_FontFamily, $prev_FontStyle, $prev_FontSizePt, '', 'default', false);
        }
        if ($getarray) {
            return $wa;
        }
        return $w;
    }

    /**
     * Returns the length of the char in user unit for the current font considering current stretching and spacing (tracking).
     * @param $char (int) The char code whose length is to be returned
     * @param $notlast (boolean) If false ignore the font-spacing.
     * @return float char width
     * @author Nicola Asuni
     * @public
     * @since 2.4.000 (2008-03-06)
     */
    public function GetCharWidth($char, $notlast=true) {
        // get raw width
        $chw = $this->getRawCharWidth($char);
        if (($this->font_spacing < 0) OR (($this->font_spacing > 0) AND $notlast)) {
            // increase/decrease font spacing
            $chw += $this->font_spacing;
        }
        if ($this->font_stretching != 100) {
            // fixed stretching mode
            $chw *= ($this->font_stretching / 100);
        }
        return $chw;
    }

    /**
     * Returns the length of the char in user unit for the current font.
     * @param $char (int) The char code whose length is to be returned
     * @return float char width
     * @author Nicola Asuni
     * @public
     * @since 5.9.000 (2010-09-28)
     */
    public function getRawCharWidth($char) {
        if ($char == 173) {
            // SHY character will not be printed
            return (0);
        }
        if (isset($this->CurrentFont['cw'][$char])) {
            $w = $this->CurrentFont['cw'][$char];
        } elseif (isset($this->CurrentFont['dw'])) {
            // default width
            $w = $this->CurrentFont['dw'];
        } elseif (isset($this->CurrentFont['cw'][32])) {
            // default width
            $w = $this->CurrentFont['cw'][32];
        } else {
            $w = 600;
        }
        return $this->getAbsFontMeasure($w);
    }

    /**
     * Returns the numbero of characters in a string.
     * @param $s (string) The input string.
     * @return int number of characters
     * @public
     * @since 2.0.0001 (2008-01-07)
     */
    public function GetNumChars($s) {
        if ($this->isUnicodeFont()) {
            return count(TCPDF_FONTS::UTF8StringToArray($s, $this->isunicode, $this->CurrentFont));
        }
        return strlen($s);
    }

    /**
     * Fill the list of available fonts ($this->fontlist).
     * @protected
     * @since 4.0.013 (2008-07-28)
     */
    protected function getFontsList() {
        if (($fontsdir = opendir(TCPDF_FONTS::_getfontpath())) !== false) {
            while (($file = readdir($fontsdir)) !== false) {
                if (substr($file, -4) == '.php') {
                    array_push($this->fontlist, strtolower(basename($file, '.php')));
                }
            }
            closedir($fontsdir);
        }
    }

    /**
     * Imports a TrueType, Type1, core, or CID0 font and makes it available.
     * It is necessary to generate a font definition file first (read /fonts/utils/README.TXT).
     * The definition file (and the font file itself when embedding) must be present either in the current directory or in the one indicated by K_PATH_FONTS if the constant is defined. If it could not be found, the error "Could not include font definition file" is generated.
     * @param $family (string) Font family. The name can be chosen arbitrarily. If it is a standard family name, it will override the corresponding font.
     * @param $style (string) Font style. Possible values are (case insensitive):<ul><li>empty string: regular (default)</li><li>B: bold</li><li>I: italic</li><li>BI or IB: bold italic</li></ul>
     * @param $fontfile (string) The font definition file. By default, the name is built from the family and style, in lower case with no spaces.
     * @return array containing the font data, or false in case of error.
     * @param $subset (mixed) if true embedd only a subset of the font (stores only the information related to the used characters); if false embedd full font; if 'default' uses the default value set using setFontSubsetting(). This option is valid only for TrueTypeUnicode fonts. If you want to enable users to change the document, set this parameter to false. If you subset the font, the person who receives your PDF would need to have your same font in order to make changes to your PDF. The file size of the PDF would also be smaller because you are embedding only part of a font.
     * @public
     * @since 1.5
     * @see SetFont(), setFontSubsetting()
     */
    public function AddFont($family, $style='', $fontfile='', $subset='default') {
        if ($subset === 'default') {
            $subset = $this->font_subsetting;
        }
        if ($this->pdfa_mode) {
            $subset = false;
        }
        if (TCPDF_STATIC::empty_string($family)) {
            if (!TCPDF_STATIC::empty_string($this->FontFamily)) {
                $family = $this->FontFamily;
            } else {
                $this->Error('Empty font family');
            }
        }
        // move embedded styles on $style
        if (substr($family, -1) == 'I') {
            $style .= 'I';
            $family = substr($family, 0, -1);
        }
        if (substr($family, -1) == 'B') {
            $style .= 'B';
            $family = substr($family, 0, -1);
        }
        // normalize family name
        $family = strtolower($family);
        if ((!$this->isunicode) AND ($family == 'arial')) {
            $family = 'helvetica';
        }
        if (($family == 'symbol') OR ($family == 'zapfdingbats')) {
            $style = '';
        }
        if ($this->pdfa_mode AND (isset($this->CoreFonts[$family]))) {
            // all fonts must be embedded
            $family = 'pdfa'.$family;
        }
        $tempstyle = strtoupper($style);
        $style = '';
        // underline
        if (strpos($tempstyle, 'U') !== false) {
            $this->underline = true;
        } else {
            $this->underline = false;
        }
        // line-through (deleted)
        if (strpos($tempstyle, 'D') !== false) {
            $this->linethrough = true;
        } else {
            $this->linethrough = false;
        }
        // overline
        if (strpos($tempstyle, 'O') !== false) {
            $this->overline = true;
        } else {
            $this->overline = false;
        }
        // bold
        if (strpos($tempstyle, 'B') !== false) {
            $style .= 'B';
        }
        // oblique
        if (strpos($tempstyle, 'I') !== false) {
            $style .= 'I';
        }
        $bistyle = $style;
        $fontkey = $family.$style;
        $font_style = $style.($this->underline ? 'U' : '').($this->linethrough ? 'D' : '').($this->overline ? 'O' : '');
        $fontdata = array('fontkey' => $fontkey, 'family' => $family, 'style' => $font_style);
        // check if the font has been already added
        $fb = $this->getFontBuffer($fontkey);
        if ($fb !== false) {
            if ($this->inxobj) {
                // we are inside an XObject template
                $this->xobjects[$this->xobjid]['fonts'][$fontkey] = $fb['i'];
            }
            return $fontdata;
        }
        // get specified font directory (if any)
        $fontdir = false;
        if (!TCPDF_STATIC::empty_string($fontfile)) {
            $fontdir = dirname($fontfile);
            if (TCPDF_STATIC::empty_string($fontdir) OR ($fontdir == '.')) {
                $fontdir = '';
            } else {
                $fontdir .= '/';
            }
        }
        // true when the font style variation is missing
        $missing_style = false;
        // search and include font file
        if (TCPDF_STATIC::empty_string($fontfile) OR (!@file_exists($fontfile))) {
            // build a standard filenames for specified font
            $tmp_fontfile = str_replace(' ', '', $family).strtolower($style).'.php';
            $fontfile = TCPDF_FONTS::getFontFullPath($tmp_fontfile, $fontdir);
            if (TCPDF_STATIC::empty_string($fontfile)) {
                $missing_style = true;
                // try to remove the style part
                $tmp_fontfile = str_replace(' ', '', $family).'.php';
                $fontfile = TCPDF_FONTS::getFontFullPath($tmp_fontfile, $fontdir);
            }
        }
        // include font file
        if (!TCPDF_STATIC::empty_string($fontfile) AND (@file_exists($fontfile))) {
            include($fontfile);
        } else {
            $this->Error('Could not include font definition file: '.$family.'');
        }
        // check font parameters
        if ((!isset($type)) OR (!isset($cw))) {
            $this->Error('The font definition file has a bad format: '.$fontfile.'');
        }
        // SET default parameters
        if (!isset($file) OR TCPDF_STATIC::empty_string($file)) {
            $file = '';
        }
        if (!isset($enc) OR TCPDF_STATIC::empty_string($enc)) {
            $enc = '';
        }
        if (!isset($cidinfo) OR TCPDF_STATIC::empty_string($cidinfo)) {
            $cidinfo = array('Registry'=>'Adobe', 'Ordering'=>'Identity', 'Supplement'=>0);
            $cidinfo['uni2cid'] = array();
        }
        if (!isset($ctg) OR TCPDF_STATIC::empty_string($ctg)) {
            $ctg = '';
        }
        if (!isset($desc) OR TCPDF_STATIC::empty_string($desc)) {
            $desc = array();
        }
        if (!isset($up) OR TCPDF_STATIC::empty_string($up)) {
            $up = -100;
        }
        if (!isset($ut) OR TCPDF_STATIC::empty_string($ut)) {
            $ut = 50;
        }
        if (!isset($cw) OR TCPDF_STATIC::empty_string($cw)) {
            $cw = array();
        }
        if (!isset($dw) OR TCPDF_STATIC::empty_string($dw)) {
            // set default width
            if (isset($desc['MissingWidth']) AND ($desc['MissingWidth'] > 0)) {
                $dw = $desc['MissingWidth'];
            } elseif (isset($cw[32])) {
                $dw = $cw[32];
            } else {
                $dw = 600;
            }
        }
        ++$this->numfonts;
        if ($type == 'core') {
            $name = $this->CoreFonts[$fontkey];
            $subset = false;
        } elseif (($type == 'TrueType') OR ($type == 'Type1')) {
            $subset = false;
        } elseif ($type == 'TrueTypeUnicode') {
            $enc = 'Identity-H';
        } elseif ($type == 'cidfont0') {
            if ($this->pdfa_mode) {
                $this->Error('All fonts must be embedded in PDF/A mode!');
            }
        } else {
            $this->Error('Unknow font type: '.$type.'');
        }
        // set name if unset
        if (!isset($name) OR empty($name)) {
            $name = $fontkey;
        }
        // create artificial font style variations if missing (only works with non-embedded fonts)
        if (($type != 'core') AND $missing_style) {
            // style variations
            $styles = array('' => '', 'B' => ',Bold', 'I' => ',Italic', 'BI' => ',BoldItalic');
            $name .= $styles[$bistyle];
            // artificial bold
            if (strpos($bistyle, 'B') !== false) {
                if (isset($desc['StemV'])) {
                    // from normal to bold
                    $desc['StemV'] = round($desc['StemV'] * 1.75);
                } else {
                    // bold
                    $desc['StemV'] = 123;
                }
            }
            // artificial italic
            if (strpos($bistyle, 'I') !== false) {
                if (isset($desc['ItalicAngle'])) {
                    $desc['ItalicAngle'] -= 11;
                } else {
                    $desc['ItalicAngle'] = -11;
                }
                if (isset($desc['Flags'])) {
                    $desc['Flags'] |= 64; //bit 7
                } else {
                    $desc['Flags'] = 64;
                }
            }
        }
        // check if the array of characters bounding boxes is defined
        if (!isset($cbbox)) {
            $cbbox = array();
        }
        // initialize subsetchars
        $subsetchars = array_fill(0, 255, true);
        $this->setFontBuffer($fontkey, array('fontkey' => $fontkey, 'i' => $this->numfonts, 'type' => $type, 'name' => $name, 'desc' => $desc, 'up' => $up, 'ut' => $ut, 'cw' => $cw, 'cbbox' => $cbbox, 'dw' => $dw, 'enc' => $enc, 'cidinfo' => $cidinfo, 'file' => $file, 'ctg' => $ctg, 'subset' => $subset, 'subsetchars' => $subsetchars));
        if ($this->inxobj) {
            // we are inside an XObject template
            $this->xobjects[$this->xobjid]['fonts'][$fontkey] = $this->numfonts;
        }
        if (isset($diff) AND (!empty($diff))) {
            //Search existing encodings
            $d = 0;
            $nb = count($this->diffs);
            for ($i=1; $i <= $nb; ++$i) {
                if ($this->diffs[$i] == $diff) {
                    $d = $i;
                    break;
                }
            }
            if ($d == 0) {
                $d = $nb + 1;
                $this->diffs[$d] = $diff;
            }
            $this->setFontSubBuffer($fontkey, 'diff', $d);
        }
        if (!TCPDF_STATIC::empty_string($file)) {
            if (!isset($this->FontFiles[$file])) {
                if ((strcasecmp($type,'TrueType') == 0) OR (strcasecmp($type, 'TrueTypeUnicode') == 0)) {
                    $this->FontFiles[$file] = array('length1' => $originalsize, 'fontdir' => $fontdir, 'subset' => $subset, 'fontkeys' => array($fontkey));
                } elseif ($type != 'core') {
                    $this->FontFiles[$file] = array('length1' => $size1, 'length2' => $size2, 'fontdir' => $fontdir, 'subset' => $subset, 'fontkeys' => array($fontkey));
                }
            } else {
                // update fontkeys that are sharing this font file
                $this->FontFiles[$file]['subset'] = ($this->FontFiles[$file]['subset'] AND $subset);
                if (!in_array($fontkey, $this->FontFiles[$file]['fontkeys'])) {
                    $this->FontFiles[$file]['fontkeys'][] = $fontkey;
                }
            }
        }
        return $fontdata;
    }

    /**
     * Sets the font used to print character strings.
     * The font can be either a standard one or a font added via the AddFont() method. Standard fonts use Windows encoding cp1252 (Western Europe).
     * The method can be called before the first page is created and the font is retained from page to page.
     * If you just wish to change the current font size, it is simpler to call SetFontSize().
     * Note: for the standard fonts, the font metric files must be accessible. There are three possibilities for this:<ul><li>They are in the current directory (the one where the running script lies)</li><li>They are in one of the directories defined by the include_path parameter</li><li>They are in the directory defined by the K_PATH_FONTS constant</li></ul><br />
     * @param $family (string) Family font. It can be either a name defined by AddFont() or one of the standard Type1 families (case insensitive):<ul><li>times (Times-Roman)</li><li>timesb (Times-Bold)</li><li>timesi (Times-Italic)</li><li>timesbi (Times-BoldItalic)</li><li>helvetica (Helvetica)</li><li>helveticab (Helvetica-Bold)</li><li>helveticai (Helvetica-Oblique)</li><li>helveticabi (Helvetica-BoldOblique)</li><li>courier (Courier)</li><li>courierb (Courier-Bold)</li><li>courieri (Courier-Oblique)</li><li>courierbi (Courier-BoldOblique)</li><li>symbol (Symbol)</li><li>zapfdingbats (ZapfDingbats)</li></ul> It is also possible to pass an empty string. In that case, the current family is retained.
     * @param $style (string) Font style. Possible values are (case insensitive):<ul><li>empty string: regular</li><li>B: bold</li><li>I: italic</li><li>U: underline</li><li>D: line through</li><li>O: overline</li></ul> or any combination. The default value is regular. Bold and italic styles do not apply to Symbol and ZapfDingbats basic fonts or other fonts when not defined.
     * @param $size (float) Font size in points. The default value is the current size. If no size has been specified since the beginning of the document, the value taken is 12
     * @param $fontfile (string) The font definition file. By default, the name is built from the family and style, in lower case with no spaces.
     * @param $subset (mixed) if true embedd only a subset of the font (stores only the information related to the used characters); if false embedd full font; if 'default' uses the default value set using setFontSubsetting(). This option is valid only for TrueTypeUnicode fonts. If you want to enable users to change the document, set this parameter to false. If you subset the font, the person who receives your PDF would need to have your same font in order to make changes to your PDF. The file size of the PDF would also be smaller because you are embedding only part of a font.
     * @param $out (boolean) if true output the font size command, otherwise only set the font properties.
     * @author Nicola Asuni
     * @public
     * @since 1.0
     * @see AddFont(), SetFontSize()
     */
    public function SetFont($family, $style='', $size=null, $fontfile='', $subset='default', $out=true) {
        //Select a font; size given in points
        if ($size === null) {
            $size = $this->FontSizePt;
        }
        if ($size < 0) {
            $size = 0;
        }
        // try to add font (if not already added)
        $fontdata = $this->AddFont($family, $style, $fontfile, $subset);
        $this->FontFamily = $fontdata['family'];
        $this->FontStyle = $fontdata['style'];
        if (isset($this->CurrentFont['fontkey']) AND isset($this->CurrentFont['subsetchars'])) {
            // save subset chars of the previous font
            $this->setFontSubBuffer($this->CurrentFont['fontkey'], 'subsetchars', $this->CurrentFont['subsetchars']);
        }
        $this->CurrentFont = $this->getFontBuffer($fontdata['fontkey']);
        $this->SetFontSize($size, $out);
    }

    /**
     * Defines the size of the current font.
     * @param $size (float) The font size in points.
     * @param $out (boolean) if true output the font size command, otherwise only set the font properties.
     * @public
     * @since 1.0
     * @see SetFont()
     */
    public function SetFontSize($size, $out=true) {
        // font size in points
        $this->FontSizePt = $size;
        // font size in user units
        $this->FontSize = $size / $this->k;
        // calculate some font metrics
        if (isset($this->CurrentFont['desc']['FontBBox'])) {
            $bbox = explode(' ', substr($this->CurrentFont['desc']['FontBBox'], 1, -1));
            $font_height = ((intval($bbox[3]) - intval($bbox[1])) * $size / 1000);
        } else {
            $font_height = $size * 1.219;
        }
        if (isset($this->CurrentFont['desc']['Ascent']) AND ($this->CurrentFont['desc']['Ascent'] > 0)) {
            $font_ascent = ($this->CurrentFont['desc']['Ascent'] * $size / 1000);
        }
        if (isset($this->CurrentFont['desc']['Descent']) AND ($this->CurrentFont['desc']['Descent'] <= 0)) {
            $font_descent = (- $this->CurrentFont['desc']['Descent'] * $size / 1000);
        }
        if (!isset($font_ascent) AND !isset($font_descent)) {
            // core font
            $font_ascent = 0.76 * $font_height;
            $font_descent = $font_height - $font_ascent;
        } elseif (!isset($font_descent)) {
            $font_descent = $font_height - $font_ascent;
        } elseif (!isset($font_ascent)) {
            $font_ascent = $font_height - $font_descent;
        }
        $this->FontAscent = ($font_ascent / $this->k);
        $this->FontDescent = ($font_descent / $this->k);
        if ($out AND ($this->page > 0) AND (isset($this->CurrentFont['i'])) AND ($this->state == 2)) {
            $this->_out(sprintf('BT /F%d %F Tf ET', $this->CurrentFont['i'], $this->FontSizePt));
        }
    }

    /**
     * Returns the bounding box of the current font in user units.
     * @return array
     * @public
     * @since 5.9.152 (2012-03-23)
     */
    public function getFontBBox() {
        $fbbox = array();
        if (isset($this->CurrentFont['desc']['FontBBox'])) {
            $tmpbbox = explode(' ', substr($this->CurrentFont['desc']['FontBBox'], 1, -1));
            $fbbox = array_map(array($this,'getAbsFontMeasure'), $tmpbbox);
        } else {
            // Find max width
            if (isset($this->CurrentFont['desc']['MaxWidth'])) {
                $maxw = $this->getAbsFontMeasure(intval($this->CurrentFont['desc']['MaxWidth']));
            } else {
                $maxw = 0;
                if (isset($this->CurrentFont['desc']['MissingWidth'])) {
                    $maxw = max($maxw, $this->CurrentFont['desc']['MissingWidth']);
                }
                if (isset($this->CurrentFont['desc']['AvgWidth'])) {
                    $maxw = max($maxw, $this->CurrentFont['desc']['AvgWidth']);
                }
                if (isset($this->CurrentFont['dw'])) {
                    $maxw = max($maxw, $this->CurrentFont['dw']);
                }
                foreach ($this->CurrentFont['cw'] as $char => $w) {
                    $maxw = max($maxw, $w);
                }
                if ($maxw == 0) {
                    $maxw = 600;
                }
                $maxw = $this->getAbsFontMeasure($maxw);
            }
            $fbbox = array(0, (0 - $this->FontDescent), $maxw, $this->FontAscent);
        }
        return $fbbox;
    }

    /**
     * Convert a relative font measure into absolute value.
     * @param $s (int) Font measure.
     * @return float Absolute measure.
     * @since 5.9.186 (2012-09-13)
     */
    public function getAbsFontMeasure($s) {
        return ($s * $this->FontSize / 1000);
    }

    /**
     * Returns the glyph bounding box of the specified character in the current font in user units.
     * @param $char (int) Input character code.
     * @return mixed array(xMin, yMin, xMax, yMax) or FALSE if not defined.
     * @since 5.9.186 (2012-09-13)
     */
    public function getCharBBox($char) {
        $c = intval($char);
        if (isset($this->CurrentFont['cw'][$c])) {
            // glyph is defined ... use zero width & height for glyphs without outlines
            $result = array(0,0,0,0);
            if (isset($this->CurrentFont['cbbox'][$c])) {
                $result = $this->CurrentFont['cbbox'][$c];
            }
            return array_map(array($this,'getAbsFontMeasure'), $result);
        }
        return false;
    }

    /**
     * Return the font descent value
     * @param $font (string) font name
     * @param $style (string) font style
     * @param $size (float) The size (in points)
     * @return int font descent
     * @public
     * @author Nicola Asuni
     * @since 4.9.003 (2010-03-30)
     */
    public function getFontDescent($font, $style='', $size=0) {
        $fontdata = $this->AddFont($font, $style);
        $fontinfo = $this->getFontBuffer($fontdata['fontkey']);
        if (isset($fontinfo['desc']['Descent']) AND ($fontinfo['desc']['Descent'] <= 0)) {
            $descent = (- $fontinfo['desc']['Descent'] * $size / 1000);
        } else {
            $descent = (1.219 * 0.24 * $size);
        }
        return ($descent / $this->k);
    }

    /**
     * Return the font ascent value.
     * @param $font (string) font name
     * @param $style (string) font style
     * @param $size (float) The size (in points)
     * @return int font ascent
     * @public
     * @author Nicola Asuni
     * @since 4.9.003 (2010-03-30)
     */
    public function getFontAscent($font, $style='', $size=0) {
        $fontdata = $this->AddFont($font, $style);
        $fontinfo = $this->getFontBuffer($fontdata['fontkey']);
        if (isset($fontinfo['desc']['Ascent']) AND ($fontinfo['desc']['Ascent'] > 0)) {
            $ascent = ($fontinfo['desc']['Ascent'] * $size / 1000);
        } else {
            $ascent = 1.219 * 0.76 * $size;
        }
        return ($ascent / $this->k);
    }

    /**
     * Return true in the character is present in the specified font.
     * @param $char (mixed) Character to check (integer value or string)
     * @param $font (string) Font name (family name).
     * @param $style (string) Font style.
     * @return (boolean) true if the char is defined, false otherwise.
     * @public
     * @since 5.9.153 (2012-03-28)
     */
    public function isCharDefined($char, $font='', $style='') {
        if (is_string($char)) {
            // get character code
            $char = TCPDF_FONTS::UTF8StringToArray($char, $this->isunicode, $this->CurrentFont);
            $char = $char[0];
        }
        if (TCPDF_STATIC::empty_string($font)) {
            if (TCPDF_STATIC::empty_string($style)) {
                return (isset($this->CurrentFont['cw'][intval($char)]));
            }
            $font = $this->FontFamily;
        }
        $fontdata = $this->AddFont($font, $style);
        $fontinfo = $this->getFontBuffer($fontdata['fontkey']);
        return (isset($fontinfo['cw'][intval($char)]));
    }

    /**
     * Replace missing font characters on selected font with specified substitutions.
     * @param $text (string) Text to process.
     * @param $font (string) Font name (family name).
     * @param $style (string) Font style.
     * @param $subs (array) Array of possible character substitutions. The key is the character to check (integer value) and the value is a single intege value or an array of possible substitutes.
     * @return (string) Processed text.
     * @public
     * @since 5.9.153 (2012-03-28)
     */
    public function replaceMissingChars($text, $font='', $style='', $subs=array()) {
        if (empty($subs)) {
            return $text;
        }
        if (TCPDF_STATIC::empty_string($font)) {
            $font = $this->FontFamily;
        }
        $fontdata = $this->AddFont($font, $style);
        $fontinfo = $this->getFontBuffer($fontdata['fontkey']);
        $uniarr = TCPDF_FONTS::UTF8StringToArray($text, $this->isunicode, $this->CurrentFont);
        foreach ($uniarr as $k => $chr) {
            if (!isset($fontinfo['cw'][$chr])) {
                // this character is missing on the selected font
                if (isset($subs[$chr])) {
                    // we have available substitutions
                    if (is_array($subs[$chr])) {
                        foreach($subs[$chr] as $s) {
                            if (isset($fontinfo['cw'][$s])) {
                                $uniarr[$k] = $s;
                                break;
                            }
                        }
                    } elseif (isset($fontinfo['cw'][$subs[$chr]])) {
                        $uniarr[$k] = $subs[$chr];
                    }
                }
            }
        }
        return TCPDF_FONTS::UniArrSubString(TCPDF_FONTS::UTF8ArrayToUniArray($uniarr, $this->isunicode));
    }

    /**
     * Defines the default monospaced font.
     * @param $font (string) Font name.
     * @public
     * @since 4.5.025
     */
    public function SetDefaultMonospacedFont($font) {
        $this->default_monospaced_font = $font;
    }

    /**
     * Creates a new internal link and returns its identifier. An internal link is a clickable area which directs to another place within the document.<br />
     * The identifier can then be passed to Cell(), Write(), Image() or Link(). The destination is defined with SetLink().
     * @public
     * @since 1.5
     * @see Cell(), Write(), Image(), Link(), SetLink()
     */
    public function AddLink() {
        // create a new internal link
        $n = count($this->links) + 1;
        $this->links[$n] = array('p' => 0, 'y' => 0, 'f' => false);
        return $n;
    }

    /**
     * Defines the page and position a link points to.
     * @param $link (int) The link identifier returned by AddLink()
     * @param $y (float) Ordinate of target position; -1 indicates the current position. The default value is 0 (top of page)
     * @param $page (int) Number of target page; -1 indicates the current page (default value). If you prefix a page number with the * character, then this page will not be changed when adding/deleting/moving pages. 
     * @public
     * @since 1.5
     * @see AddLink()
     */
    public function SetLink($link, $y=0, $page=-1) {
        $fixed = false;
        if (!empty($page) AND ($page[0] == '*')) {
            $page = intval(substr($page, 1));
            // this page number will not be changed when moving/add/deleting pages
            $fixed = true;
        }
        if ($page < 0) {
            $page = $this->page;
        }
        if ($y == -1) {
            $y = $this->y;
        }
        $this->links[$link] = array('p' => $page, 'y' => $y, 'f' => $fixed);
    }

    /**
     * Puts a link on a rectangular area of the page.
     * Text or image links are generally put via Cell(), Write() or Image(), but this method can be useful for instance to define a clickable area inside an image.
     * @param $x (float) Abscissa of the upper-left corner of the rectangle
     * @param $y (float) Ordinate of the upper-left corner of the rectangle
     * @param $w (float) Width of the rectangle
     * @param $h (float) Height of the rectangle
     * @param $link (mixed) URL or identifier returned by AddLink()
     * @param $spaces (int) number of spaces on the text to link
     * @public
     * @since 1.5
     * @see AddLink(), Annotation(), Cell(), Write(), Image()
     */
    public function Link($x, $y, $w, $h, $link, $spaces=0) {
        $this->Annotation($x, $y, $w, $h, $link, array('Subtype'=>'Link'), $spaces);
    }

    /**
     * Puts a markup annotation on a rectangular area of the page.
     * !!!!THE ANNOTATION SUPPORT IS NOT YET FULLY IMPLEMENTED !!!!
     * @param $x (float) Abscissa of the upper-left corner of the rectangle
     * @param $y (float) Ordinate of the upper-left corner of the rectangle
     * @param $w (float) Width of the rectangle
     * @param $h (float) Height of the rectangle
     * @param $text (string) annotation text or alternate content
     * @param $opt (array) array of options (see section 8.4 of PDF reference 1.7).
     * @param $spaces (int) number of spaces on the text to link
     * @public
     * @since 4.0.018 (2008-08-06)
     */
    public function Annotation($x, $y, $w, $h, $text, $opt=array('Subtype'=>'Text'), $spaces=0) {
        if ($this->inxobj) {
            // store parameters for later use on template
            $this->xobjects[$this->xobjid]['annotations'][] = array('x' => $x, 'y' => $y, 'w' => $w, 'h' => $h, 'text' => $text, 'opt' => $opt, 'spaces' => $spaces);
            return;
        }
        if ($x === '') {
            $x = $this->x;
        }
        if ($y === '') {
            $y = $this->y;
        }
        // check page for no-write regions and adapt page margins if necessary
        list($x, $y) = $this->checkPageRegions($h, $x, $y);
        // recalculate coordinates to account for graphic transformations
        if (isset($this->transfmatrix) AND !empty($this->transfmatrix)) {
            for ($i=$this->transfmatrix_key; $i > 0; --$i) {
                $maxid = count($this->transfmatrix[$i]) - 1;
                for ($j=$maxid; $j >= 0; --$j) {
                    $ctm = $this->transfmatrix[$i][$j];
                    if (isset($ctm['a'])) {
                        $x = $x * $this->k;
                        $y = ($this->h - $y) * $this->k;
                        $w = $w * $this->k;
                        $h = $h * $this->k;
                        // top left
                        $xt = $x;
                        $yt = $y;
                        $x1 = ($ctm['a'] * $xt) + ($ctm['c'] * $yt) + $ctm['e'];
                        $y1 = ($ctm['b'] * $xt) + ($ctm['d'] * $yt) + $ctm['f'];
                        // top right
                        $xt = $x + $w;
                        $yt = $y;
                        $x2 = ($ctm['a'] * $xt) + ($ctm['c'] * $yt) + $ctm['e'];
                        $y2 = ($ctm['b'] * $xt) + ($ctm['d'] * $yt) + $ctm['f'];
                        // bottom left
                        $xt = $x;
                        $yt = $y - $h;
                        $x3 = ($ctm['a'] * $xt) + ($ctm['c'] * $yt) + $ctm['e'];
                        $y3 = ($ctm['b'] * $xt) + ($ctm['d'] * $yt) + $ctm['f'];
                        // bottom right
                        $xt = $x + $w;
                        $yt = $y - $h;
                        $x4 = ($ctm['a'] * $xt) + ($ctm['c'] * $yt) + $ctm['e'];
                        $y4 = ($ctm['b'] * $xt) + ($ctm['d'] * $yt) + $ctm['f'];
                        // new coordinates (rectangle area)
                        $x = min($x1, $x2, $x3, $x4);
                        $y = max($y1, $y2, $y3, $y4);
                        $w = (max($x1, $x2, $x3, $x4) - $x) / $this->k;
                        $h = ($y - min($y1, $y2, $y3, $y4)) / $this->k;
                        $x = $x / $this->k;
                        $y = $this->h - ($y / $this->k);
                    }
                }
            }
        }
        if ($this->page <= 0) {
            $page = 1;
        } else {
            $page = $this->page;
        }
        if (!isset($this->PageAnnots[$page])) {
            $this->PageAnnots[$page] = array();
        }
        $this->PageAnnots[$page][] = array('n' => ++$this->n, 'x' => $x, 'y' => $y, 'w' => $w, 'h' => $h, 'txt' => $text, 'opt' => $opt, 'numspaces' => $spaces);
        if (!$this->pdfa_mode) {
            if ((($opt['Subtype'] == 'FileAttachment') OR ($opt['Subtype'] == 'Sound')) AND (!TCPDF_STATIC::empty_string($opt['FS']))
                AND (@file_exists($opt['FS']) OR TCPDF_STATIC::isValidURL($opt['FS']))
                AND (!isset($this->embeddedfiles[basename($opt['FS'])]))) {
                $this->embeddedfiles[basename($opt['FS'])] = array('f' => ++$this->n, 'n' => ++$this->n, 'file' => $opt['FS']);
            }
        }
        // Add widgets annotation's icons
        if (isset($opt['mk']['i']) AND @file_exists($opt['mk']['i'])) {
            $this->Image($opt['mk']['i'], '', '', 10, 10, '', '', '', false, 300, '', false, false, 0, false, true);
        }
        if (isset($opt['mk']['ri']) AND @file_exists($opt['mk']['ri'])) {
            $this->Image($opt['mk']['ri'], '', '', 0, 0, '', '', '', false, 300, '', false, false, 0, false, true);
        }
        if (isset($opt['mk']['ix']) AND @file_exists($opt['mk']['ix'])) {
            $this->Image($opt['mk']['ix'], '', '', 0, 0, '', '', '', false, 300, '', false, false, 0, false, true);
        }
    }

    /**
     * Embedd the attached files.
     * @since 4.4.000 (2008-12-07)
     * @protected
     * @see Annotation()
     */
    protected function _putEmbeddedFiles() {
        if ($this->pdfa_mode) {
            // embedded files are not allowed in PDF/A mode
            return;
        }
        reset($this->embeddedfiles);
        foreach ($this->embeddedfiles as $filename => $filedata) {
            $data = TCPDF_STATIC::fileGetContents($filedata['file']);
            if ($data !== FALSE) {
                $rawsize = strlen($data);
                if ($rawsize > 0) {
                    // update name tree
                    $this->efnames[$filename] = $filedata['f'].' 0 R';
                    // embedded file specification object
                    $out = $this->_getobj($filedata['f'])."\n";
                    $out .= '<</Type /Filespec /F '.$this->_datastring($filename, $filedata['f']).' /EF <</F '.$filedata['n'].' 0 R>> >>';
                    $out .= "\n".'endobj';
                    $this->_out($out);
                    // embedded file object
                    $filter = '';
                    if ($this->compress) {
                        $data = gzcompress($data);
                        $filter = ' /Filter /FlateDecode';
                    }
                    $stream = $this->_getrawstream($data, $filedata['n']);
                    $out = $this->_getobj($filedata['n'])."\n";
                    $out .= '<< /Type /EmbeddedFile'.$filter.' /Length '.strlen($stream).' /Params <</Size '.$rawsize.'>> >>';
                    $out .= ' stream'."\n".$stream."\n".'endstream';
                    $out .= "\n".'endobj';
                    $this->_out($out);
                }
            }
        }
    }

    /**
     * Prints a text cell at the specified position.
     * This method allows to place a string precisely on the page.
     * @param $x (float) Abscissa of the cell origin
     * @param $y (float) Ordinate of the cell origin
     * @param $txt (string) String to print
     * @param $fstroke (int) outline size in user units (false = disable)
     * @param $fclip (boolean) if true activate clipping mode (you must call StartTransform() before this function and StopTransform() to stop the clipping tranformation).
     * @param $ffill (boolean) if true fills the text
     * @param $border (mixed) Indicates if borders must be drawn around the cell. The value can be a number:<ul><li>0: no border (default)</li><li>1: frame</li></ul> or a string containing some or all of the following characters (in any order):<ul><li>L: left</li><li>T: top</li><li>R: right</li><li>B: bottom</li></ul> or an array of line styles for each border group - for example: array('LTRB' => array('width' => 2, 'cap' => 'butt', 'join' => 'miter', 'dash' => 0, 'color' => array(0, 0, 0)))
     * @param $ln (int) Indicates where the current position should go after the call. Possible values are:<ul><li>0: to the right (or left for RTL languages)</li><li>1: to the beginning of the next line</li><li>2: below</li></ul>Putting 1 is equivalent to putting 0 and calling Ln() just after. Default value: 0.
     * @param $align (string) Allows to center or align the text. Possible values are:<ul><li>L or empty string: left align (default value)</li><li>C: center</li><li>R: right align</li><li>J: justify</li></ul>
     * @param $fill (boolean) Indicates if the cell background must be painted (true) or transparent (false).
     * @param $link (mixed) URL or identifier returned by AddLink().
     * @param $stretch (int) font stretch mode: <ul><li>0 = disabled</li><li>1 = horizontal scaling only if text is larger than cell width</li><li>2 = forced horizontal scaling to fit cell width</li><li>3 = character spacing only if text is larger than cell width</li><li>4 = forced character spacing to fit cell width</li></ul> General font stretching and scaling values will be preserved when possible.
     * @param $ignore_min_height (boolean) if true ignore automatic minimum height value.
     * @param $calign (string) cell vertical alignment relative to the specified Y value. Possible values are:<ul><li>T : cell top</li><li>A : font top</li><li>L : font baseline</li><li>D : font bottom</li><li>B : cell bottom</li></ul>
     * @param $valign (string) text vertical alignment inside the cell. Possible values are:<ul><li>T : top</li><li>C : center</li><li>B : bottom</li></ul>
     * @param $rtloff (boolean) if true uses the page top-left corner as origin of axis for $x and $y initial position.
     * @public
     * @since 1.0
     * @see Cell(), Write(), MultiCell(), WriteHTML(), WriteHTMLCell()
     */
    public function Text($x, $y, $txt, $fstroke=false, $fclip=false, $ffill=true, $border=0, $ln=0, $align='', $fill=false, $link='', $stretch=0, $ignore_min_height=false, $calign='T', $valign='M', $rtloff=false) {
        $textrendermode = $this->textrendermode;
        $textstrokewidth = $this->textstrokewidth;
        $this->setTextRenderingMode($fstroke, $ffill, $fclip);
        $this->SetXY($x, $y, $rtloff);
        $this->Cell(0, 0, $txt, $border, $ln, $align, $fill, $link, $stretch, $ignore_min_height, $calign, $valign);
        // restore previous rendering mode
        $this->textrendermode = $textrendermode;
        $this->textstrokewidth = $textstrokewidth;
    }

    /**
     * Whenever a page break condition is met, the method is called, and the break is issued or not depending on the returned value.
     * The default implementation returns a value according to the mode selected by SetAutoPageBreak().<br />
     * This method is called automatically and should not be called directly by the application.
     * @return boolean
     * @public
     * @since 1.4
     * @see SetAutoPageBreak()
     */
    public function AcceptPageBreak() {
        if ($this->num_columns > 1) {
            // multi column mode
            if ($this->current_column < ($this->num_columns - 1)) {
                // go to next column
                $this->selectColumn($this->current_column + 1);
            } elseif ($this->AutoPageBreak) {
                // add a new page
                $this->AddPage();
                // set first column
                $this->selectColumn(0);
            }
            // avoid page breaking from checkPageBreak()
            return false;
        }
        return $this->AutoPageBreak;
    }

    /**
     * Add page if needed.
     * @param $h (float) Cell height. Default value: 0.
     * @param $y (mixed) starting y position, leave empty for current position.
     * @param $addpage (boolean) if true add a page, otherwise only return the true/false state
     * @return boolean true in case of page break, false otherwise.
     * @since 3.2.000 (2008-07-01)
     * @protected
     */
    protected function checkPageBreak($h=0, $y='', $addpage=true) {
        if (TCPDF_STATIC::empty_string($y)) {
            $y = $this->y;
        }
        $current_page = $this->page;
        if ((($y + $h) > $this->PageBreakTrigger) AND ($this->inPageBody()) AND ($this->AcceptPageBreak())) {
            if ($addpage) {
                //Automatic page break
                $x = $this->x;
                $this->AddPage($this->CurOrientation);
                $this->y = $this->tMargin;
                $oldpage = $this->page - 1;
                if ($this->rtl) {
                    if ($this->pagedim[$this->page]['orm'] != $this->pagedim[$oldpage]['orm']) {
                        $this->x = $x - ($this->pagedim[$this->page]['orm'] - $this->pagedim[$oldpage]['orm']);
                    } else {
                        $this->x = $x;
                    }
                } else {
                    if ($this->pagedim[$this->page]['olm'] != $this->pagedim[$oldpage]['olm']) {
                        $this->x = $x + ($this->pagedim[$this->page]['olm'] - $this->pagedim[$oldpage]['olm']);
                    } else {
                        $this->x = $x;
                    }
                }
            }
            return true;
        }
        if ($current_page != $this->page) {
            // account for columns mode
            return true;
        }
        return false;
    }

    /**
     * Prints a cell (rectangular area) with optional borders, background color and character string. The upper-left corner of the cell corresponds to the current position. The text can be aligned or centered. After the call, the current position moves to the right or to the next line. It is possible to put a link on the text.<br />
     * If automatic page breaking is enabled and the cell goes beyond the limit, a page break is done before outputting.
     * @param $w (float) Cell width. If 0, the cell extends up to the right margin.
     * @param $h (float) Cell height. Default value: 0.
     * @param $txt (string) String to print. Default value: empty string.
     * @param $border (mixed) Indicates if borders must be drawn around the cell. The value can be a number:<ul><li>0: no border (default)</li><li>1: frame</li></ul> or a string containing some or all of the following characters (in any order):<ul><li>L: left</li><li>T: top</li><li>R: right</li><li>B: bottom</li></ul> or an array of line styles for each border group - for example: array('LTRB' => array('width' => 2, 'cap' => 'butt', 'join' => 'miter', 'dash' => 0, 'color' => array(0, 0, 0)))
     * @param $ln (int) Indicates where the current position should go after the call. Possible values are:<ul><li>0: to the right (or left for RTL languages)</li><li>1: to the beginning of the next line</li><li>2: below</li></ul> Putting 1 is equivalent to putting 0 and calling Ln() just after. Default value: 0.
     * @param $align (string) Allows to center or align the text. Possible values are:<ul><li>L or empty string: left align (default value)</li><li>C: center</li><li>R: right align</li><li>J: justify</li></ul>
     * @param $fill (boolean) Indicates if the cell background must be painted (true) or transparent (false).
     * @param $link (mixed) URL or identifier returned by AddLink().
     * @param $stretch (int) font stretch mode: <ul><li>0 = disabled</li><li>1 = horizontal scaling only if text is larger than cell width</li><li>2 = forced horizontal scaling to fit cell width</li><li>3 = character spacing only if text is larger than cell width</li><li>4 = forced character spacing to fit cell width</li></ul> General font stretching and scaling values will be preserved when possible.
     * @param $ignore_min_height (boolean) if true ignore automatic minimum height value.
     * @param $calign (string) cell vertical alignment relative to the specified Y value. Possible values are:<ul><li>T : cell top</li><li>C : center</li><li>B : cell bottom</li><li>A : font top</li><li>L : font baseline</li><li>D : font bottom</li></ul>
     * @param $valign (string) text vertical alignment inside the cell. Possible values are:<ul><li>T : top</li><li>C : center</li><li>B : bottom</li></ul>
     * @public
     * @since 1.0
     * @see SetFont(), SetDrawColor(), SetFillColor(), SetTextColor(), SetLineWidth(), AddLink(), Ln(), MultiCell(), Write(), SetAutoPageBreak()
     */
    public function Cell($w, $h=0, $txt='', $border=0, $ln=0, $align='', $fill=false, $link='', $stretch=0, $ignore_min_height=false, $calign='T', $valign='M') {
        $prev_cell_margin = $this->cell_margin;
        $prev_cell_padding = $this->cell_padding;
        $this->adjustCellPadding($border);
        if (!$ignore_min_height) {
            $min_cell_height = $this->getCellHeight($this->FontSize);
            if ($h < $min_cell_height) {
                $h = $min_cell_height;
            }
        }
        $this->checkPageBreak($h + $this->cell_margin['T'] + $this->cell_margin['B']);
        // apply text shadow if enabled
        if ($this->txtshadow['enabled']) {
            // save data
            $x = $this->x;
            $y = $this->y;
            $bc = $this->bgcolor;
            $fc = $this->fgcolor;
            $sc = $this->strokecolor;
            $alpha = $this->alpha;
            // print shadow
            $this->x += $this->txtshadow['depth_w'];
            $this->y += $this->txtshadow['depth_h'];
            $this->SetFillColorArray($this->txtshadow['color']);
            $this->SetTextColorArray($this->txtshadow['color']);
            $this->SetDrawColorArray($this->txtshadow['color']);
            if ($this->txtshadow['opacity'] != $alpha['CA']) {
                $this->setAlpha($this->txtshadow['opacity'], $this->txtshadow['blend_mode']);
            }
            if ($this->state == 2) {
                $this->_out($this->getCellCode($w, $h, $txt, $border, $ln, $align, $fill, $link, $stretch, true, $calign, $valign));
            }
            //restore data
            $this->x = $x;
            $this->y = $y;
            $this->SetFillColorArray($bc);
            $this->SetTextColorArray($fc);
            $this->SetDrawColorArray($sc);
            if ($this->txtshadow['opacity'] != $alpha['CA']) {
                $this->setAlpha($alpha['CA'], $alpha['BM'], $alpha['ca'], $alpha['AIS']);
            }
        }
        if ($this->state == 2) {
            $this->_out($this->getCellCode($w, $h, $txt, $border, $ln, $align, $fill, $link, $stretch, true, $calign, $valign));
        }
        $this->cell_padding = $prev_cell_padding;
        $this->cell_margin = $prev_cell_margin;
    }

    /**
     * Returns the PDF string code to print a cell (rectangular area) with optional borders, background color and character string. The upper-left corner of the cell corresponds to the current position. The text can be aligned or centered. After the call, the current position moves to the right or to the next line. It is possible to put a link on the text.<br />
     * If automatic page breaking is enabled and the cell goes beyond the limit, a page break is done before outputting.
     * @param $w (float) Cell width. If 0, the cell extends up to the right margin.
     * @param $h (float) Cell height. Default value: 0.
     * @param $txt (string) String to print. Default value: empty string.
     * @param $border (mixed) Indicates if borders must be drawn around the cell. The value can be a number:<ul><li>0: no border (default)</li><li>1: frame</li></ul> or a string containing some or all of the following characters (in any order):<ul><li>L: left</li><li>T: top</li><li>R: right</li><li>B: bottom</li></ul> or an array of line styles for each border group - for example: array('LTRB' => array('width' => 2, 'cap' => 'butt', 'join' => 'miter', 'dash' => 0, 'color' => array(0, 0, 0)))
     * @param $ln (int) Indicates where the current position should go after the call. Possible values are:<ul><li>0: to the right (or left for RTL languages)</li><li>1: to the beginning of the next line</li><li>2: below</li></ul>Putting 1 is equivalent to putting 0 and calling Ln() just after. Default value: 0.
     * @param $align (string) Allows to center or align the text. Possible values are:<ul><li>L or empty string: left align (default value)</li><li>C: center</li><li>R: right align</li><li>J: justify</li></ul>
     * @param $fill (boolean) Indicates if the cell background must be painted (true) or transparent (false).
     * @param $link (mixed) URL or identifier returned by AddLink().
     * @param $stretch (int) font stretch mode: <ul><li>0 = disabled</li><li>1 = horizontal scaling only if text is larger than cell width</li><li>2 = forced horizontal scaling to fit cell width</li><li>3 = character spacing only if text is larger than cell width</li><li>4 = forced character spacing to fit cell width</li></ul> General font stretching and scaling values will be preserved when possible.
     * @param $ignore_min_height (boolean) if true ignore automatic minimum height value.
     * @param $calign (string) cell vertical alignment relative to the specified Y value. Possible values are:<ul><li>T : cell top</li><li>C : center</li><li>B : cell bottom</li><li>A : font top</li><li>L : font baseline</li><li>D : font bottom</li></ul>
     * @param $valign (string) text vertical alignment inside the cell. Possible values are:<ul><li>T : top</li><li>M : middle</li><li>B : bottom</li></ul>
     * @return string containing cell code
     * @protected
     * @since 1.0
     * @see Cell()
     */
    protected function getCellCode($w, $h=0, $txt='', $border=0, $ln=0, $align='', $fill=false, $link='', $stretch=0, $ignore_min_height=false, $calign='T', $valign='M') {
        // replace 'NO-BREAK SPACE' (U+00A0) character with a simple space
        $txt = str_replace(TCPDF_FONTS::unichr(160, $this->isunicode), ' ', $txt);
        $prev_cell_margin = $this->cell_margin;
        $prev_cell_padding = $this->cell_padding;
        $txt = TCPDF_STATIC::removeSHY($txt, $this->isunicode);
        $rs = ''; //string to be returned
        $this->adjustCellPadding($border);
        if (!$ignore_min_height) {
            $min_cell_height = $this->getCellHeight($this->FontSize);
            if ($h < $min_cell_height) {
                $h = $min_cell_height;
            }
        }
        $k = $this->k;
        // check page for no-write regions and adapt page margins if necessary
        list($this->x, $this->y) = $this->checkPageRegions($h, $this->x, $this->y);
        if ($this->rtl) {
            $x = $this->x - $this->cell_margin['R'];
        } else {
            $x = $this->x + $this->cell_margin['L'];
        }
        $y = $this->y + $this->cell_margin['T'];
        $prev_font_stretching = $this->font_stretching;
        $prev_font_spacing = $this->font_spacing;
        // cell vertical alignment
        switch ($calign) {
            case 'A': {
                // font top
                switch ($valign) {
                    case 'T': {
                        // top
                        $y -= $this->cell_padding['T'];
                        break;
                    }
                    case 'B': {
                        // bottom
                        $y -= ($h - $this->cell_padding['B'] - $this->FontAscent - $this->FontDescent);
                        break;
                    }
                    default:
                    case 'C':
                    case 'M': {
                        // center
                        $y -= (($h - $this->FontAscent - $this->FontDescent) / 2);
                        break;
                    }
                }
                break;
            }
            case 'L': {
                // font baseline
                switch ($valign) {
                    case 'T': {
                        // top
                        $y -= ($this->cell_padding['T'] + $this->FontAscent);
                        break;
                    }
                    case 'B': {
                        // bottom
                        $y -= ($h - $this->cell_padding['B'] - $this->FontDescent);
                        break;
                    }
                    default:
                    case 'C':
                    case 'M': {
                        // center
                        $y -= (($h + $this->FontAscent - $this->FontDescent) / 2);
                        break;
                    }
                }
                break;
            }
            case 'D': {
                // font bottom
                switch ($valign) {
                    case 'T': {
                        // top
                        $y -= ($this->cell_padding['T'] + $this->FontAscent + $this->FontDescent);
                        break;
                    }
                    case 'B': {
                        // bottom
                        $y -= ($h - $this->cell_padding['B']);
                        break;
                    }
                    default:
                    case 'C':
                    case 'M': {
                        // center
                        $y -= (($h + $this->FontAscent + $this->FontDescent) / 2);
                        break;
                    }
                }
                break;
            }
            case 'B': {
                // cell bottom
                $y -= $h;
                break;
            }
            case 'C':
            case 'M': {
                // cell center
                $y -= ($h / 2);
                break;
            }
            default:
            case 'T': {
                // cell top
                break;
            }
        }
        // text vertical alignment
        switch ($valign) {
            case 'T': {
                // top
                $yt = $y + $this->cell_padding['T'];
                break;
            }
            case 'B': {
                // bottom
                $yt = $y + $h - $this->cell_padding['B'] - $this->FontAscent - $this->FontDescent;
                break;
            }
            default:
            case 'C':
            case 'M': {
                // center
                $yt = $y + (($h - $this->FontAscent - $this->FontDescent) / 2);
                break;
            }
        }
        $basefonty = $yt + $this->FontAscent;
        if (TCPDF_STATIC::empty_string($w) OR ($w <= 0)) {
            if ($this->rtl) {
                $w = $x - $this->lMargin;
            } else {
                $w = $this->w - $this->rMargin - $x;
            }
        }
        $s = '';
        // fill and borders
        if (is_string($border) AND (strlen($border) == 4)) {
            // full border
            $border = 1;
        }
        if ($fill OR ($border == 1)) {
            if ($fill) {
                $op = ($border == 1) ? 'B' : 'f';
            } else {
                $op = 'S';
            }
            if ($this->rtl) {
                $xk = (($x - $w) * $k);
            } else {
                $xk = ($x * $k);
            }
            $s .= sprintf('%F %F %F %F re %s ', $xk, (($this->h - $y) * $k), ($w * $k), (-$h * $k), $op);
        }
        // draw borders
        $s .= $this->getCellBorder($x, $y, $w, $h, $border);
        if ($txt != '') {
            $txt2 = $txt;
            if ($this->isunicode) {
                if (($this->CurrentFont['type'] == 'core') OR ($this->CurrentFont['type'] == 'TrueType') OR ($this->CurrentFont['type'] == 'Type1')) {
                    $txt2 = TCPDF_FONTS::UTF8ToLatin1($txt2, $this->isunicode, $this->CurrentFont);
                } else {
                    $unicode = TCPDF_FONTS::UTF8StringToArray($txt, $this->isunicode, $this->CurrentFont); // array of UTF-8 unicode values
                    $unicode = TCPDF_FONTS::utf8Bidi($unicode, '', $this->tmprtl, $this->isunicode, $this->CurrentFont);
                    // replace thai chars (if any)
                    if (defined('K_THAI_TOPCHARS') AND (K_THAI_TOPCHARS == true)) {
                        // number of chars
                        $numchars = count($unicode);
                        // po pla, for far, for fan
                        $longtail = array(0x0e1b, 0x0e1d, 0x0e1f);
                        // do chada, to patak
                        $lowtail = array(0x0e0e, 0x0e0f);
                        // mai hun arkad, sara i, sara ii, sara ue, sara uee
                        $upvowel = array(0x0e31, 0x0e34, 0x0e35, 0x0e36, 0x0e37);
                        // mai ek, mai tho, mai tri, mai chattawa, karan
                        $tonemark = array(0x0e48, 0x0e49, 0x0e4a, 0x0e4b, 0x0e4c);
                        // sara u, sara uu, pinthu
                        $lowvowel = array(0x0e38, 0x0e39, 0x0e3a);
                        $output = array();
                        for ($i = 0; $i < $numchars; $i++) {
                            if (($unicode[$i] >= 0x0e00) && ($unicode[$i] <= 0x0e5b)) {
                                $ch0 = $unicode[$i];
                                $ch1 = ($i > 0) ? $unicode[($i - 1)] : 0;
                                $ch2 = ($i > 1) ? $unicode[($i - 2)] : 0;
                                $chn = ($i < ($numchars - 1)) ? $unicode[($i + 1)] : 0;
                                if (in_array($ch0, $tonemark)) {
                                    if ($chn == 0x0e33) {
                                        // sara um
                                        if (in_array($ch1, $longtail)) {
                                            // tonemark at upper left
                                            $output[] = $this->replaceChar($ch0, (0xf713 + $ch0 - 0x0e48));
                                        } else {
                                            // tonemark at upper right (normal position)
                                            $output[] = $ch0;
                                        }
                                    } elseif (in_array($ch1, $longtail) OR (in_array($ch2, $longtail) AND in_array($ch1, $lowvowel))) {
                                        // tonemark at lower left
                                        $output[] = $this->replaceChar($ch0, (0xf705 + $ch0 - 0x0e48));
                                    } elseif (in_array($ch1, $upvowel)) {
                                        if (in_array($ch2, $longtail)) {
                                            // tonemark at upper left
                                            $output[] = $this->replaceChar($ch0, (0xf713 + $ch0 - 0x0e48));
                                        } else {
                                            // tonemark at upper right (normal position)
                                            $output[] = $ch0;
                                        }
                                    } else {
                                        // tonemark at lower right
                                        $output[] = $this->replaceChar($ch0, (0xf70a + $ch0 - 0x0e48));
                                    }
                                } elseif (($ch0 == 0x0e33) AND (in_array($ch1, $longtail) OR (in_array($ch2, $longtail) AND in_array($ch1, $tonemark)))) {
                                    // add lower left nikhahit and sara aa
                                    if ($this->isCharDefined(0xf711) AND $this->isCharDefined(0x0e32)) {
                                        $output[] = 0xf711;
                                        $this->CurrentFont['subsetchars'][0xf711] = true;
                                        $output[] = 0x0e32;
                                        $this->CurrentFont['subsetchars'][0x0e32] = true;
                                    } else {
                                        $output[] = $ch0;
                                    }
                                } elseif (in_array($ch1, $longtail)) {
                                    if ($ch0 == 0x0e31) {
                                        // lower left mai hun arkad
                                        $output[] = $this->replaceChar($ch0, 0xf710);
                                    } elseif (in_array($ch0, $upvowel)) {
                                        // lower left
                                        $output[] = $this->replaceChar($ch0, (0xf701 + $ch0 - 0x0e34));
                                    } elseif ($ch0 == 0x0e47) {
                                        // lower left mai tai koo
                                        $output[] = $this->replaceChar($ch0, 0xf712);
                                    } else {
                                        // normal character
                                        $output[] = $ch0;
                                    }
                                } elseif (in_array($ch1, $lowtail) AND in_array($ch0, $lowvowel)) {
                                    // lower vowel
                                    $output[] = $this->replaceChar($ch0, (0xf718 + $ch0 - 0x0e38));
                                } elseif (($ch0 == 0x0e0d) AND in_array($chn, $lowvowel)) {
                                    // yo ying without lower part
                                    $output[] = $this->replaceChar($ch0, 0xf70f);
                                } elseif (($ch0 == 0x0e10) AND in_array($chn, $lowvowel)) {
                                    // tho santan without lower part
                                    $output[] = $this->replaceChar($ch0, 0xf700);
                                } else {
                                    $output[] = $ch0;
                                }
                            } else {
                                // non-thai character
                                $output[] = $unicode[$i];
                            }
                        }
                        $unicode = $output;
                        // update font subsetchars
                        $this->setFontSubBuffer($this->CurrentFont['fontkey'], 'subsetchars', $this->CurrentFont['subsetchars']);
                    } // end of K_THAI_TOPCHARS
                    $txt2 = TCPDF_FONTS::arrUTF8ToUTF16BE($unicode, false);
                }
            }
            $txt2 = TCPDF_STATIC::_escape($txt2);
            // get current text width (considering general font stretching and spacing)
            $txwidth = $this->GetStringWidth($txt);
            $width = $txwidth;
            // check for stretch mode
            if ($stretch > 0) {
                // calculate ratio between cell width and text width
                if ($width <= 0) {
                    $ratio = 1;
                } else {
                    $ratio = (($w - $this->cell_padding['L'] - $this->cell_padding['R']) / $width);
                }
                // check if stretching is required
                if (($ratio < 1) OR (($ratio > 1) AND (($stretch % 2) == 0))) {
                    // the text will be stretched to fit cell width
                    if ($stretch > 2) {
                        // set new character spacing
                        $this->font_spacing += ($w - $this->cell_padding['L'] - $this->cell_padding['R'] - $width) / (max(($this->GetNumChars($txt) - 1), 1) * ($this->font_stretching / 100));
                    } else {
                        // set new horizontal stretching
                        $this->font_stretching *= $ratio;
                    }
                    // recalculate text width (the text fills the entire cell)
                    $width = $w - $this->cell_padding['L'] - $this->cell_padding['R'];
                    // reset alignment
                    $align = '';
                }
            }
            if ($this->font_stretching != 100) {
                // apply font stretching
                $rs .= sprintf('BT %F Tz ET ', $this->font_stretching);
            }
            if ($this->font_spacing != 0) {
                // increase/decrease font spacing
                $rs .= sprintf('BT %F Tc ET ', ($this->font_spacing * $this->k));
            }
            if ($this->ColorFlag AND ($this->textrendermode < 4)) {
                $s .= 'q '.$this->TextColor.' ';
            }
            // rendering mode
            $s .= sprintf('BT %d Tr %F w ET ', $this->textrendermode, ($this->textstrokewidth * $this->k));
            // count number of spaces
            $ns = substr_count($txt, chr(32));
            // Justification
            $spacewidth = 0;
            if (($align == 'J') AND ($ns > 0)) {
                if ($this->isUnicodeFont()) {
                    // get string width without spaces
                    $width = $this->GetStringWidth(str_replace(' ', '', $txt));
                    // calculate average space width
                    $spacewidth = -1000 * ($w - $width - $this->cell_padding['L'] - $this->cell_padding['R']) / ($ns?$ns:1) / ($this->FontSize?$this->FontSize:1);
                    if ($this->font_stretching != 100) {
                        // word spacing is affected by stretching
                        $spacewidth /= ($this->font_stretching / 100);
                    }
                    // set word position to be used with TJ operator
                    $txt2 = str_replace(chr(0).chr(32), ') '.sprintf('%F', $spacewidth).' (', $txt2);
                    $unicode_justification = true;
                } else {
                    // get string width
                    $width = $txwidth;
                    // new space width
                    $spacewidth = (($w - $width - $this->cell_padding['L'] - $this->cell_padding['R']) / ($ns?$ns:1)) * $this->k;
                    if ($this->font_stretching != 100) {
                        // word spacing (Tw) is affected by stretching
                        $spacewidth /= ($this->font_stretching / 100);
                    }
                    // set word spacing
                    $rs .= sprintf('BT %F Tw ET ', $spacewidth);
                }
                $width = $w - $this->cell_padding['L'] - $this->cell_padding['R'];
            }
            // replace carriage return characters
            $txt2 = str_replace("\r", ' ', $txt2);
            switch ($align) {
                case 'C': {
                    $dx = ($w - $width) / 2;
                    break;
                }
                case 'R': {
                    if ($this->rtl) {
                        $dx = $this->cell_padding['R'];
                    } else {
                        $dx = $w - $width - $this->cell_padding['R'];
                    }
                    break;
                }
                case 'L': {
                    if ($this->rtl) {
                        $dx = $w - $width - $this->cell_padding['L'];
                    } else {
                        $dx = $this->cell_padding['L'];
                    }
                    break;
                }
                case 'J':
                default: {
                    if ($this->rtl) {
                        $dx = $this->cell_padding['R'];
                    } else {
                        $dx = $this->cell_padding['L'];
                    }
                    break;
                }
            }
            if ($this->rtl) {
                $xdx = $x - $dx - $width;
            } else {
                $xdx = $x + $dx;
            }
            $xdk = $xdx * $k;
            // print text
            $s .= sprintf('BT %F %F Td [(%s)] TJ ET', $xdk, (($this->h - $basefonty) * $k), $txt2);
            if (isset($uniblock)) {
                // print overlapping characters as separate string
                $xshift = 0; // horizontal shift
                $ty = (($this->h - $basefonty + (0.2 * $this->FontSize)) * $k);
                $spw = (($w - $txwidth - $this->cell_padding['L'] - $this->cell_padding['R']) / ($ns?$ns:1));
                foreach ($uniblock as $uk => $uniarr) {
                    if (($uk % 2) == 0) {
                        // x space to skip
                        if ($spacewidth != 0) {
                            // justification shift
                            $xshift += (count(array_keys($uniarr, 32)) * $spw);
                        }
                        $xshift += $this->GetArrStringWidth($uniarr); // + shift justification
                    } else {
                        // character to print
                        $topchr = TCPDF_FONTS::arrUTF8ToUTF16BE($uniarr, false);
                        $topchr = TCPDF_STATIC::_escape($topchr);
                        $s .= sprintf(' BT %F %F Td [(%s)] TJ ET', ($xdk + ($xshift * $k)), $ty, $topchr);
                    }
                }
            }
            if ($this->underline) {
                $s .= ' '.$this->_dounderlinew($xdx, $basefonty, $width);
            }
            if ($this->linethrough) {
                $s .= ' '.$this->_dolinethroughw($xdx, $basefonty, $width);
            }
            if ($this->overline) {
                $s .= ' '.$this->_dooverlinew($xdx, $basefonty, $width);
            }
            if ($this->ColorFlag AND ($this->textrendermode < 4)) {
                $s .= ' Q';
            }
            if ($link) {
                $this->Link($xdx, $yt, $width, ($this->FontAscent + $this->FontDescent), $link, $ns);
            }
        }
        // output cell
        if ($s) {
            // output cell
            $rs .= $s;
            if ($this->font_spacing != 0) {
                // reset font spacing mode
                $rs .= ' BT 0 Tc ET';
            }
            if ($this->font_stretching != 100) {
                // reset font stretching mode
                $rs .= ' BT 100 Tz ET';
            }
        }
        // reset word spacing
        if (!$this->isUnicodeFont() AND ($align == 'J')) {
            $rs .= ' BT 0 Tw ET';
        }
        // reset stretching and spacing
        $this->font_stretching = $prev_font_stretching;
        $this->font_spacing = $prev_font_spacing;
        $this->lasth = $h;
        if ($ln > 0) {
            //Go to the beginning of the next line
            $this->y = $y + $h + $this->cell_margin['B'];
            if ($ln == 1) {
                if ($this->rtl) {
                    $this->x = $this->w - $this->rMargin;
                } else {
                    $this->x = $this->lMargin;
                }
            }
        } else {
            // go left or right by case
            if ($this->rtl) {
                $this->x = $x - $w - $this->cell_margin['L'];
            } else {
                $this->x = $x + $w + $this->cell_margin['R'];
            }
        }
        $gstyles = ''.$this->linestyleWidth.' '.$this->linestyleCap.' '.$this->linestyleJoin.' '.$this->linestyleDash.' '.$this->DrawColor.' '.$this->FillColor."\n";
        $rs = $gstyles.$rs;
        $this->cell_padding = $prev_cell_padding;
        $this->cell_margin = $prev_cell_margin;
        return $rs;
    }

    /**
     * Replace a char if is defined on the current font.
     * @param $oldchar (int) Integer code (unicode) of the character to replace.
     * @param $newchar (int) Integer code (unicode) of the new character.
     * @return int the replaced char or the old char in case the new char i not defined
     * @protected
     * @since 5.9.167 (2012-06-22)
     */
    protected function replaceChar($oldchar, $newchar) {
        if ($this->isCharDefined($newchar)) {
            // add the new char on the subset list
            $this->CurrentFont['subsetchars'][$newchar] = true;
            // return the new character
            return $newchar;
        }
        // return the old char
        return $oldchar;
    }

    /**
     * Returns the code to draw the cell border
     * @param $x (float) X coordinate.
     * @param $y (float) Y coordinate.
     * @param $w (float) Cell width.
     * @param $h (float) Cell height.
     * @param $brd (mixed) Indicates if borders must be drawn around the cell. The value can be a number:<ul><li>0: no border (default)</li><li>1: frame</li></ul> or a string containing some or all of the following characters (in any order):<ul><li>L: left</li><li>T: top</li><li>R: right</li><li>B: bottom</li></ul> or an array of line styles for each border group - for example: array('LTRB' => array('width' => 2, 'cap' => 'butt', 'join' => 'miter', 'dash' => 0, 'color' => array(0, 0, 0)))
     * @return string containing cell border code
     * @protected
     * @see SetLineStyle()
     * @since 5.7.000 (2010-08-02)
     */
    protected function getCellBorder($x, $y, $w, $h, $brd) {
        $s = ''; // string to be returned
        if (empty($brd)) {
            return $s;
        }
        if ($brd == 1) {
            $brd = array('LRTB' => true);
        }
        // calculate coordinates for border
        $k = $this->k;
        if ($this->rtl) {
            $xeL = ($x - $w) * $k;
            $xeR = $x * $k;
        } else {
            $xeL = $x * $k;
            $xeR = ($x + $w) * $k;
        }
        $yeL = (($this->h - ($y + $h)) * $k);
        $yeT = (($this->h - $y) * $k);
        $xeT = $xeL;
        $xeB = $xeR;
        $yeR = $yeT;
        $yeB = $yeL;
        if (is_string($brd)) {
            // convert string to array
            $slen = strlen($brd);
            $newbrd = array();
            for ($i = 0; $i < $slen; ++$i) {
                $newbrd[$brd[$i]] = array('cap' => 'square', 'join' => 'miter');
            }
            $brd = $newbrd;
        }
        if (isset($brd['mode'])) {
            $mode = $brd['mode'];
            unset($brd['mode']);
        } else {
            $mode = 'normal';
        }
        foreach ($brd as $border => $style) {
            if (is_array($style) AND !empty($style)) {
                // apply border style
                $prev_style = $this->linestyleWidth.' '.$this->linestyleCap.' '.$this->linestyleJoin.' '.$this->linestyleDash.' '.$this->DrawColor.' ';
                $s .= $this->SetLineStyle($style, true)."\n";
            }
            switch ($mode) {
                case 'ext': {
                    $off = (($this->LineWidth / 2) * $k);
                    $xL = $xeL - $off;
                    $xR = $xeR + $off;
                    $yT = $yeT + $off;
                    $yL = $yeL - $off;
                    $xT = $xL;
                    $xB = $xR;
                    $yR = $yT;
                    $yB = $yL;
                    $w += $this->LineWidth;
                    $h += $this->LineWidth;
                    break;
                }
                case 'int': {
                    $off = ($this->LineWidth / 2) * $k;
                    $xL = $xeL + $off;
                    $xR = $xeR - $off;
                    $yT = $yeT - $off;
                    $yL = $yeL + $off;
                    $xT = $xL;
                    $xB = $xR;
                    $yR = $yT;
                    $yB = $yL;
                    $w -= $this->LineWidth;
                    $h -= $this->LineWidth;
                    break;
                }
                case 'normal':
                default: {
                    $xL = $xeL;
                    $xT = $xeT;
                    $xB = $xeB;
                    $xR = $xeR;
                    $yL = $yeL;
                    $yT = $yeT;
                    $yB = $yeB;
                    $yR = $yeR;
                    break;
                }
            }
            // draw borders by case
            if (strlen($border) == 4) {
                $s .= sprintf('%F %F %F %F re S ', $xT, $yT, ($w * $k), (-$h * $k));
            } elseif (strlen($border) == 3) {
                if (strpos($border,'B') === false) { // LTR
                    $s .= sprintf('%F %F m ', $xL, $yL);
                    $s .= sprintf('%F %F l ', $xT, $yT);
                    $s .= sprintf('%F %F l ', $xR, $yR);
                    $s .= sprintf('%F %F l ', $xB, $yB);
                    $s .= 'S ';
                } elseif (strpos($border,'L') === false) { // TRB
                    $s .= sprintf('%F %F m ', $xT, $yT);
                    $s .= sprintf('%F %F l ', $xR, $yR);
                    $s .= sprintf('%F %F l ', $xB, $yB);
                    $s .= sprintf('%F %F l ', $xL, $yL);
                    $s .= 'S ';
                } elseif (strpos($border,'T') === false) { // RBL
                    $s .= sprintf('%F %F m ', $xR, $yR);
                    $s .= sprintf('%F %F l ', $xB, $yB);
                    $s .= sprintf('%F %F l ', $xL, $yL);
                    $s .= sprintf('%F %F l ', $xT, $yT);
                    $s .= 'S ';
                } elseif (strpos($border,'R') === false) { // BLT
                    $s .= sprintf('%F %F m ', $xB, $yB);
                    $s .= sprintf('%F %F l ', $xL, $yL);
                    $s .= sprintf('%F %F l ', $xT, $yT);
                    $s .= sprintf('%F %F l ', $xR, $yR);
                    $s .= 'S ';
                }
            } elseif (strlen($border) == 2) {
                if ((strpos($border,'L') !== false) AND (strpos($border,'T') !== false)) { // LT
                    $s .= sprintf('%F %F m ', $xL, $yL);
                    $s .= sprintf('%F %F l ', $xT, $yT);
                    $s .= sprintf('%F %F l ', $xR, $yR);
                    $s .= 'S ';
                } elseif ((strpos($border,'T') !== false) AND (strpos($border,'R') !== false)) { // TR
                    $s .= sprintf('%F %F m ', $xT, $yT);
                    $s .= sprintf('%F %F l ', $xR, $yR);
                    $s .= sprintf('%F %F l ', $xB, $yB);
                    $s .= 'S ';
                } elseif ((strpos($border,'R') !== false) AND (strpos($border,'B') !== false)) { // RB
                    $s .= sprintf('%F %F m ', $xR, $yR);
                    $s .= sprintf('%F %F l ', $xB, $yB);
                    $s .= sprintf('%F %F l ', $xL, $yL);
                    $s .= 'S ';
                } elseif ((strpos($border,'B') !== false) AND (strpos($border,'L') !== false)) { // BL
                    $s .= sprintf('%F %F m ', $xB, $yB);
                    $s .= sprintf('%F %F l ', $xL, $yL);
                    $s .= sprintf('%F %F l ', $xT, $yT);
                    $s .= 'S ';
                } elseif ((strpos($border,'L') !== false) AND (strpos($border,'R') !== false)) { // LR
                    $s .= sprintf('%F %F m ', $xL, $yL);
                    $s .= sprintf('%F %F l ', $xT, $yT);
                    $s .= 'S ';
                    $s .= sprintf('%F %F m ', $xR, $yR);
                    $s .= sprintf('%F %F l ', $xB, $yB);
                    $s .= 'S ';
                } elseif ((strpos($border,'T') !== false) AND (strpos($border,'B') !== false)) { // TB
                    $s .= sprintf('%F %F m ', $xT, $yT);
                    $s .= sprintf('%F %F l ', $xR, $yR);
                    $s .= 'S ';
                    $s .= sprintf('%F %F m ', $xB, $yB);
                    $s .= sprintf('%F %F l ', $xL, $yL);
                    $s .= 'S ';
                }
            } else { // strlen($border) == 1
                if (strpos($border,'L') !== false) { // L
                    $s .= sprintf('%F %F m ', $xL, $yL);
                    $s .= sprintf('%F %F l ', $xT, $yT);
                    $s .= 'S ';
                } elseif (strpos($border,'T') !== false) { // T
                    $s .= sprintf('%F %F m ', $xT, $yT);
                    $s .= sprintf('%F %F l ', $xR, $yR);
                    $s .= 'S ';
                } elseif (strpos($border,'R') !== false) { // R
                    $s .= sprintf('%F %F m ', $xR, $yR);
                    $s .= sprintf('%F %F l ', $xB, $yB);
                    $s .= 'S ';
                } elseif (strpos($border,'B') !== false) { // B
                    $s .= sprintf('%F %F m ', $xB, $yB);
                    $s .= sprintf('%F %F l ', $xL, $yL);
                    $s .= 'S ';
                }
            }
            if (is_array($style) AND !empty($style)) {
                // reset border style to previous value
                $s .= "\n".$this->linestyleWidth.' '.$this->linestyleCap.' '.$this->linestyleJoin.' '.$this->linestyleDash.' '.$this->DrawColor."\n";
            }
        }
        return $s;
    }

    /**
     * This method allows printing text with line breaks.
     * They can be automatic (as soon as the text reaches the right border of the cell) or explicit (via the \n character). As many cells as necessary are output, one below the other.<br />
     * Text can be aligned, centered or justified. The cell block can be framed and the background painted.
     * @param $w (float) Width of cells. If 0, they extend up to the right margin of the page.
     * @param $h (float) Cell minimum height. The cell extends automatically if needed.
     * @param $txt (string) String to print
     * @param $border (mixed) Indicates if borders must be drawn around the cell. The value can be a number:<ul><li>0: no border (default)</li><li>1: frame</li></ul> or a string containing some or all of the following characters (in any order):<ul><li>L: left</li><li>T: top</li><li>R: right</li><li>B: bottom</li></ul> or an array of line styles for each border group - for example: array('LTRB' => array('width' => 2, 'cap' => 'butt', 'join' => 'miter', 'dash' => 0, 'color' => array(0, 0, 0)))
     * @param $align (string) Allows to center or align the text. Possible values are:<ul><li>L or empty string: left align</li><li>C: center</li><li>R: right align</li><li>J: justification (default value when $ishtml=false)</li></ul>
     * @param $fill (boolean) Indicates if the cell background must be painted (true) or transparent (false).
     * @param $ln (int) Indicates where the current position should go after the call. Possible values are:<ul><li>0: to the right</li><li>1: to the beginning of the next line [DEFAULT]</li><li>2: below</li></ul>
     * @param $x (float) x position in user units
     * @param $y (float) y position in user units
     * @param $reseth (boolean) if true reset the last cell height (default true).
     * @param $stretch (int) font stretch mode: <ul><li>0 = disabled</li><li>1 = horizontal scaling only if text is larger than cell width</li><li>2 = forced horizontal scaling to fit cell width</li><li>3 = character spacing only if text is larger than cell width</li><li>4 = forced character spacing to fit cell width</li></ul> General font stretching and scaling values will be preserved when possible.
     * @param $ishtml (boolean) INTERNAL USE ONLY -- set to true if $txt is HTML content (default = false). Never set this parameter to true, use instead writeHTMLCell() or writeHTML() methods.
     * @param $autopadding (boolean) if true, uses internal padding and automatically adjust it to account for line width.
     * @param $maxh (float) maximum height. It should be >= $h and less then remaining space to the bottom of the page, or 0 for disable this feature. This feature works only when $ishtml=false.
     * @param $valign (string) Vertical alignment of text (requires $maxh = $h > 0). Possible values are:<ul><li>T: TOP</li><li>M: middle</li><li>B: bottom</li></ul>. This feature works only when $ishtml=false and the cell must fit in a single page.
     * @param $fitcell (boolean) if true attempt to fit all the text within the cell by reducing the font size (do not work in HTML mode). $maxh must be greater than 0 and equal to $h.
     * @return int Return the number of cells or 1 for html mode.
     * @public
     * @since 1.3
     * @see SetFont(), SetDrawColor(), SetFillColor(), SetTextColor(), SetLineWidth(), Cell(), Write(), SetAutoPageBreak()
     */
    public function MultiCell($w, $h, $txt, $border=0, $align='J', $fill=false, $ln=1, $x='', $y='', $reseth=true, $stretch=0, $ishtml=false, $autopadding=true, $maxh=0, $valign='T', $fitcell=false) {
        $prev_cell_margin = $this->cell_margin;
        $prev_cell_padding = $this->cell_padding;
        // adjust internal padding
        $this->adjustCellPadding($border);
        $mc_padding = $this->cell_padding;
        $mc_margin = $this->cell_margin;
        $this->cell_padding['T'] = 0;
        $this->cell_padding['B'] = 0;
        $this->setCellMargins(0, 0, 0, 0);
        if (TCPDF_STATIC::empty_string($this->lasth) OR $reseth) {
            // reset row height
            $this->resetLastH();
        }
        if (!TCPDF_STATIC::empty_string($y)) {
            $this->SetY($y);
        } else {
            $y = $this->GetY();
        }
        $resth = 0;
        if (($h > 0) AND $this->inPageBody() AND (($y + $h + $mc_margin['T'] + $mc_margin['B']) > $this->PageBreakTrigger)) {
            // spit cell in more pages/columns
            $newh = ($this->PageBreakTrigger - $y);
            $resth = ($h - $newh); // cell to be printed on the next page/column
            $h = $newh;
        }
        // get current page number
        $startpage = $this->page;
        // get current column
        $startcolumn = $this->current_column;
        if (!TCPDF_STATIC::empty_string($x)) {
            $this->SetX($x);
        } else {
            $x = $this->GetX();
        }
        // check page for no-write regions and adapt page margins if necessary
        list($x, $y) = $this->checkPageRegions(0, $x, $y);
        // apply margins
        $oy = $y + $mc_margin['T'];
        if ($this->rtl) {
            $ox = ($this->w - $x - $mc_margin['R']);
        } else {
            $ox = ($x + $mc_margin['L']);
        }
        $this->x = $ox;
        $this->y = $oy;
        // set width
        if (TCPDF_STATIC::empty_string($w) OR ($w <= 0)) {
            if ($this->rtl) {
                $w = ($this->x - $this->lMargin - $mc_margin['L']);
            } else {
                $w = ($this->w - $this->x - $this->rMargin - $mc_margin['R']);
            }
        }
        // store original margin values
        $lMargin = $this->lMargin;
        $rMargin = $this->rMargin;
        if ($this->rtl) {
            $this->rMargin = ($this->w - $this->x);
            $this->lMargin = ($this->x - $w);
        } else {
            $this->lMargin = ($this->x);
            $this->rMargin = ($this->w - $this->x - $w);
        }
        $this->clMargin = $this->lMargin;
        $this->crMargin = $this->rMargin;
        if ($autopadding) {
            // add top padding
            $this->y += $mc_padding['T'];
        }
        if ($ishtml) { // ******* Write HTML text
            $this->writeHTML($txt, true, false, $reseth, true, $align);
            $nl = 1;
        } else { // ******* Write simple text
            $prev_FontSizePt = $this->FontSizePt;
            if ($fitcell) {
                // ajust height values
                $tobottom = ($this->h - $this->y - $this->bMargin - $this->cell_padding['T'] - $this->cell_padding['B']);
                $h = $maxh = max(min($h, $tobottom), min($maxh, $tobottom));
            }
            // vertical alignment
            if ($maxh > 0) {
                // get text height
                $text_height = $this->getStringHeight($w, $txt, $reseth, $autopadding, $mc_padding, $border);
                if ($fitcell AND ($text_height > $maxh) AND ($this->FontSizePt > 1)) {
                    // try to reduce font size to fit text on cell (use a quick search algorithm)
                    $fmin = 1;
                    $fmax = $this->FontSizePt;
                    $diff_epsilon = (1 / $this->k); // one point (min resolution)
                    $maxit = (2 * min(100, max(10, intval($fmax)))); // max number of iterations
                    while ($maxit >= 0) {
                        $fmid = (($fmax + $fmin) / 2);
                        $this->SetFontSize($fmid, false);
                        $this->resetLastH();
                        $text_height = $this->getStringHeight($w, $txt, $reseth, $autopadding, $mc_padding, $border);
                        $diff = ($maxh - $text_height);
                        if ($diff >= 0) {
                            if ($diff <= $diff_epsilon) {
                                break;
                            }
                            $fmin = $fmid;
                        } else {
                            $fmax = $fmid;
                        }
                        --$maxit;
                    }
                    if ($maxit < 0) {
                        // premature exit, we get the minimum font value to fit the cell
                        $this->SetFontSize($fmin);
                        $this->resetLastH();
                        $text_height = $this->getStringHeight($w, $txt, $reseth, $autopadding, $mc_padding, $border);
                    } else {
                        $this->SetFontSize($fmid);
                        $this->resetLastH();
                    }
                }
                if ($text_height < $maxh) {
                    if ($valign == 'M') {
                        // text vertically centered
                        $this->y += (($maxh - $text_height) / 2);
                    } elseif ($valign == 'B') {
                        // text vertically aligned on bottom
                        $this->y += ($maxh - $text_height);
                    }
                }
            }
            $nl = $this->Write($this->lasth, $txt, '', 0, $align, true, $stretch, false, true, $maxh, 0, $mc_margin);
            if ($fitcell) {
                // restore font size
                $this->SetFontSize($prev_FontSizePt);
            }
        }
        if ($autopadding) {
            // add bottom padding
            $this->y += $mc_padding['B'];
        }
        // Get end-of-text Y position
        $currentY = $this->y;
        // get latest page number
        $endpage = $this->page;
        if ($resth > 0) {
            $skip = ($endpage - $startpage);
            $tmpresth = $resth;
            while ($tmpresth > 0) {
                if ($skip <= 0) {
                    // add a page (or trig AcceptPageBreak() for multicolumn mode)
                    $this->checkPageBreak($this->PageBreakTrigger + 1);
                }
                if ($this->num_columns > 1) {
                    $tmpresth -= ($this->h - $this->y - $this->bMargin);
                } else {
                    $tmpresth -= ($this->h - $this->tMargin - $this->bMargin);
                }
                --$skip;
            }
            $currentY = $this->y;
            $endpage = $this->page;
        }
        // get latest column
        $endcolumn = $this->current_column;
        if ($this->num_columns == 0) {
            $this->num_columns = 1;
        }
        // disable page regions check
        $check_page_regions = $this->check_page_regions;
        $this->check_page_regions = false;
        // get border modes
        $border_start = TCPDF_STATIC::getBorderMode($border, $position='start', $this->opencell);
        $border_end = TCPDF_STATIC::getBorderMode($border, $position='end', $this->opencell);
        $border_middle = TCPDF_STATIC::getBorderMode($border, $position='middle', $this->opencell);
        // design borders around HTML cells.
        for ($page = $startpage; $page <= $endpage; ++$page) { // for each page
            $ccode = '';
            $this->setPage($page);
            if ($this->num_columns < 2) {
                // single-column mode
                $this->SetX($x);
                $this->y = $this->tMargin;
            }
            // account for margin changes
            if ($page > $startpage) {
                if (($this->rtl) AND ($this->pagedim[$page]['orm'] != $this->pagedim[$startpage]['orm'])) {
                    $this->x -= ($this->pagedim[$page]['orm'] - $this->pagedim[$startpage]['orm']);
                } elseif ((!$this->rtl) AND ($this->pagedim[$page]['olm'] != $this->pagedim[$startpage]['olm'])) {
                    $this->x += ($this->pagedim[$page]['olm'] - $this->pagedim[$startpage]['olm']);
                }
            }
            if ($startpage == $endpage) {
                // single page
                for ($column = $startcolumn; $column <= $endcolumn; ++$column) { // for each column
                    $this->selectColumn($column);
                    if ($this->rtl) {
                        $this->x -= $mc_margin['R'];
                    } else {
                        $this->x += $mc_margin['L'];
                    }
                    if ($startcolumn == $endcolumn) { // single column
                        $cborder = $border;
                        $h = max($h, ($currentY - $oy));
                        $this->y = $oy;
                    } elseif ($column == $startcolumn) { // first column
                        $cborder = $border_start;
                        $this->y = $oy;
                        $h = $this->h - $this->y - $this->bMargin;
                    } elseif ($column == $endcolumn) { // end column
                        $cborder = $border_end;
                        $h = $currentY - $this->y;
                        if ($resth > $h) {
                            $h = $resth;
                        }
                    } else { // middle column
                        $cborder = $border_middle;
                        $h = $this->h - $this->y - $this->bMargin;
                        $resth -= $h;
                    }
                    $ccode .= $this->getCellCode($w, $h, '', $cborder, 1, '', $fill, '', 0, true)."\n";
                } // end for each column
            } elseif ($page == $startpage) { // first page
                for ($column = $startcolumn; $column < $this->num_columns; ++$column) { // for each column
                    $this->selectColumn($column);
                    if ($this->rtl) {
                        $this->x -= $mc_margin['R'];
                    } else {
                        $this->x += $mc_margin['L'];
                    }
                    if ($column == $startcolumn) { // first column
                        $cborder = $border_start;
                        $this->y = $oy;
                        $h = $this->h - $this->y - $this->bMargin;
                    } else { // middle column
                        $cborder = $border_middle;
                        $h = $this->h - $this->y - $this->bMargin;
                        $resth -= $h;
                    }
                    $ccode .= $this->getCellCode($w, $h, '', $cborder, 1, '', $fill, '', 0, true)."\n";
                } // end for each column
            } elseif ($page == $endpage) { // last page
                for ($column = 0; $column <= $endcolumn; ++$column) { // for each column
                    $this->selectColumn($column);
                    if ($this->rtl) {
                        $this->x -= $mc_margin['R'];
                    } else {
                        $this->x += $mc_margin['L'];
                    }
                    if ($column == $endcolumn) {
                        // end column
                        $cborder = $border_end;
                        $h = $currentY - $this->y;
                        if ($resth > $h) {
                            $h = $resth;
                        }
                    } else {
                        // middle column
                        $cborder = $border_middle;
                        $h = $this->h - $this->y - $this->bMargin;
                        $resth -= $h;
                    }
                    $ccode .= $this->getCellCode($w, $h, '', $cborder, 1, '', $fill, '', 0, true)."\n";
                } // end for each column
            } else { // middle page
                for ($column = 0; $column < $this->num_columns; ++$column) { // for each column
                    $this->selectColumn($column);
                    if ($this->rtl) {
                        $this->x -= $mc_margin['R'];
                    } else {
                        $this->x += $mc_margin['L'];
                    }
                    $cborder = $border_middle;
                    $h = $this->h - $this->y - $this->bMargin;
                    $resth -= $h;
                    $ccode .= $this->getCellCode($w, $h, '', $cborder, 1, '', $fill, '', 0, true)."\n";
                } // end for each column
            }
            if ($cborder OR $fill) {
                $offsetlen = strlen($ccode);
                // draw border and fill
                if ($this->inxobj) {
                    // we are inside an XObject template
                    if (end($this->xobjects[$this->xobjid]['transfmrk']) !== false) {
                        $pagemarkkey = key($this->xobjects[$this->xobjid]['transfmrk']);
                        $pagemark = $this->xobjects[$this->xobjid]['transfmrk'][$pagemarkkey];
                        $this->xobjects[$this->xobjid]['transfmrk'][$pagemarkkey] += $offsetlen;
                    } else {
                        $pagemark = $this->xobjects[$this->xobjid]['intmrk'];
                        $this->xobjects[$this->xobjid]['intmrk'] += $offsetlen;
                    }
                    $pagebuff = $this->xobjects[$this->xobjid]['outdata'];
                    $pstart = substr($pagebuff, 0, $pagemark);
                    $pend = substr($pagebuff, $pagemark);
                    $this->xobjects[$this->xobjid]['outdata'] = $pstart.$ccode.$pend;
                } else {
                    if (end($this->transfmrk[$this->page]) !== false) {
                        $pagemarkkey = key($this->transfmrk[$this->page]);
                        $pagemark = $this->transfmrk[$this->page][$pagemarkkey];
                        $this->transfmrk[$this->page][$pagemarkkey] += $offsetlen;
                    } elseif ($this->InFooter) {
                        $pagemark = $this->footerpos[$this->page];
                        $this->footerpos[$this->page] += $offsetlen;
                    } else {
                        $pagemark = $this->intmrk[$this->page];
                        $this->intmrk[$this->page] += $offsetlen;
                    }
                    $pagebuff = $this->getPageBuffer($this->page);
                    $pstart = substr($pagebuff, 0, $pagemark);
                    $pend = substr($pagebuff, $pagemark);
                    $this->setPageBuffer($this->page, $pstart.$ccode.$pend);
                }
            }
        } // end for each page
        // restore page regions check
        $this->check_page_regions = $check_page_regions;
        // Get end-of-cell Y position
        $currentY = $this->GetY();
        // restore previous values
        if ($this->num_columns > 1) {
            $this->selectColumn();
        } else {
            // restore original margins
            $this->lMargin = $lMargin;
            $this->rMargin = $rMargin;
            if ($this->page > $startpage) {
                // check for margin variations between pages (i.e. booklet mode)
                $dl = ($this->pagedim[$this->page]['olm'] - $this->pagedim[$startpage]['olm']);
                $dr = ($this->pagedim[$this->page]['orm'] - $this->pagedim[$startpage]['orm']);
                if (($dl != 0) OR ($dr != 0)) {
                    $this->lMargin += $dl;
                    $this->rMargin += $dr;
                }
            }
        }
        if ($ln > 0) {
            //Go to the beginning of the next line
            $this->SetY($currentY + $mc_margin['B']);
            if ($ln == 2) {
                $this->SetX($x + $w + $mc_margin['L'] + $mc_margin['R']);
            }
        } else {
            // go left or right by case
            $this->setPage($startpage);
            $this->y = $y;
            $this->SetX($x + $w + $mc_margin['L'] + $mc_margin['R']);
        }
        $this->setContentMark();
        $this->cell_padding = $prev_cell_padding;
        $this->cell_margin = $prev_cell_margin;
        $this->clMargin = $this->lMargin;
        $this->crMargin = $this->rMargin;
        return $nl;
    }

    /**
     * This method return the estimated number of lines for print a simple text string using Multicell() method.
     * @param $txt (string) String for calculating his height
     * @param $w (float) Width of cells. If 0, they extend up to the right margin of the page.
     * @param $reseth (boolean) if true reset the last cell height (default false).
     * @param $autopadding (boolean) if true, uses internal padding and automatically adjust it to account for line width (default true).
     * @param $cellpadding (float) Internal cell padding, if empty uses default cell padding.
     * @param $border (mixed) Indicates if borders must be drawn around the cell. The value can be a number:<ul><li>0: no border (default)</li><li>1: frame</li></ul> or a string containing some or all of the following characters (in any order):<ul><li>L: left</li><li>T: top</li><li>R: right</li><li>B: bottom</li></ul> or an array of line styles for each border group - for example: array('LTRB' => array('width' => 2, 'cap' => 'butt', 'join' => 'miter', 'dash' => 0, 'color' => array(0, 0, 0)))
     * @return float Return the minimal height needed for multicell method for printing the $txt param.
     * @author Alexander Escalona Fern\E1ndez, Nicola Asuni
     * @public
     * @since 4.5.011
     */
    public function getNumLines($txt, $w=0, $reseth=false, $autopadding=true, $cellpadding='', $border=0) {
        if ($txt === NULL) {
            return 0;
        }
        if ($txt === '') {
            // empty string
            return 1;
        }
        // adjust internal padding
        $prev_cell_padding = $this->cell_padding;
        $prev_lasth = $this->lasth;
        if (is_array($cellpadding)) {
            $this->cell_padding = $cellpadding;
        }
        $this->adjustCellPadding($border);
        if (TCPDF_STATIC::empty_string($w) OR ($w <= 0)) {
            if ($this->rtl) {
                $w = $this->x - $this->lMargin;
            } else {
                $w = $this->w - $this->rMargin - $this->x;
            }
        }
        $wmax = $w - $this->cell_padding['L'] - $this->cell_padding['R'];
        if ($reseth) {
            // reset row height
            $this->resetLastH();
        }
        $lines = 1;
        $sum = 0;
        $chars = TCPDF_FONTS::utf8Bidi(TCPDF_FONTS::UTF8StringToArray($txt, $this->isunicode, $this->CurrentFont), $txt, $this->tmprtl, $this->isunicode, $this->CurrentFont);
        $charsWidth = $this->GetArrStringWidth($chars, '', '', 0, true);
        $length = count($chars);
        $lastSeparator = -1;
        for ($i = 0; $i < $length; ++$i) {
            $c = $chars[$i];
            $charWidth = $charsWidth[$i];
            if (($c != 160)
                    AND (($c == 173)
                        OR preg_match($this->re_spaces, TCPDF_FONTS::unichr($c, $this->isunicode))
                        OR (($c == 45)
                            AND ($i > 0) AND ($i < ($length - 1))
                            AND @preg_match('/[\p{L}]/'.$this->re_space['m'], TCPDF_FONTS::unichr($chars[($i - 1)], $this->isunicode))
                            AND @preg_match('/[\p{L}]/'.$this->re_space['m'], TCPDF_FONTS::unichr($chars[($i + 1)], $this->isunicode))
                        )
                    )
                ) {
                $lastSeparator = $i;
            }
            if ((($sum + $charWidth) > $wmax) OR ($c == 10)) {
                ++$lines;
                if ($c == 10) {
                    $lastSeparator = -1;
                    $sum = 0;
                } elseif ($lastSeparator != -1) {
                    $i = $lastSeparator;
                    $lastSeparator = -1;
                    $sum = 0;
                } else {
                    $sum = $charWidth;
                }
            } else {
                $sum += $charWidth;
            }
        }
        if ($chars[($length - 1)] == 10) {
            --$lines;
        }
        $this->cell_padding = $prev_cell_padding;
        $this->lasth = $prev_lasth;
        return $lines;
    }

    /**
     * This method return the estimated height needed for printing a simple text string using the Multicell() method.
     * Generally, if you want to know the exact height for a block of content you can use the following alternative technique:
     * @pre
     *  // store current object
     *  $pdf->startTransaction();
     *  // store starting values
     *  $start_y = $pdf->GetY();
     *  $start_page = $pdf->getPage();
     *  // call your printing functions with your parameters
     *  // - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
     *  $pdf->MultiCell($w=0, $h=0, $txt, $border=1, $align='L', $fill=false, $ln=1, $x='', $y='', $reseth=true, $stretch=0, $ishtml=false, $autopadding=true, $maxh=0);
     *  // - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
     *  // get the new Y
     *  $end_y = $pdf->GetY();
     *  $end_page = $pdf->getPage();
     *  // calculate height
     *  $height = 0;
     *  if ($end_page == $start_page) {
     *      $height = $end_y - $start_y;
     *  } else {
     *      for ($page=$start_page; $page <= $end_page; ++$page) {
     *          $this->setPage($page);
     *          if ($page == $start_page) {
     *              // first page
     *              $height = $this->h - $start_y - $this->bMargin;
     *          } elseif ($page == $end_page) {
     *              // last page
     *              $height = $end_y - $this->tMargin;
     *          } else {
     *              $height = $this->h - $this->tMargin - $this->bMargin;
     *          }
     *      }
     *  }
     *  // restore previous object
     *  $pdf = $pdf->rollbackTransaction();
     *
     * @param $w (float) Width of cells. If 0, they extend up to the right margin of the page.
     * @param $txt (string) String for calculating his height
     * @param $reseth (boolean) if true reset the last cell height (default false).
     * @param $autopadding (boolean) if true, uses internal padding and automatically adjust it to account for line width (default true).
     * @param $cellpadding (float) Internal cell padding, if empty uses default cell padding.
     * @param $border (mixed) Indicates if borders must be drawn around the cell. The value can be a number:<ul><li>0: no border (default)</li><li>1: frame</li></ul> or a string containing some or all of the following characters (in any order):<ul><li>L: left</li><li>T: top</li><li>R: right</li><li>B: bottom</li></ul> or an array of line styles for each border group - for example: array('LTRB' => array('width' => 2, 'cap' => 'butt', 'join' => 'miter', 'dash' => 0, 'color' => array(0, 0, 0)))
     * @return float Return the minimal height needed for multicell method for printing the $txt param.
     * @author Nicola Asuni, Alexander Escalona Fern\E1ndez
     * @public
     */
    public function getStringHeight($w, $txt, $reseth=false, $autopadding=true, $cellpadding='', $border=0) {
        // adjust internal padding
        $prev_cell_padding = $this->cell_padding;
        $prev_lasth = $this->lasth;
        if (is_array($cellpadding)) {
            $this->cell_padding = $cellpadding;
        }
        $this->adjustCellPadding($border);
        $lines = $this->getNumLines($txt, $w, $reseth, $autopadding, $cellpadding, $border);
        $height = $this->getCellHeight(($lines * $this->FontSize), $autopadding);
        $this->cell_padding = $prev_cell_padding;
        $this->lasth = $prev_lasth;
        return $height;
    }

    /**
     * This method prints text from the current position.<br />
     * @param $h (float) Line height
     * @param $txt (string) String to print
     * @param $link (mixed) URL or identifier returned by AddLink()
     * @param $fill (boolean) Indicates if the cell background must be painted (true) or transparent (false).
     * @param $align (string) Allows to center or align the text. Possible values are:<ul><li>L or empty string: left align (default value)</li><li>C: center</li><li>R: right align</li><li>J: justify</li></ul>
     * @param $ln (boolean) if true set cursor at the bottom of the line, otherwise set cursor at the top of the line.
     * @param $stretch (int) font stretch mode: <ul><li>0 = disabled</li><li>1 = horizontal scaling only if text is larger than cell width</li><li>2 = forced horizontal scaling to fit cell width</li><li>3 = character spacing only if text is larger than cell width</li><li>4 = forced character spacing to fit cell width</li></ul> General font stretching and scaling values will be preserved when possible.
     * @param $firstline (boolean) if true prints only the first line and return the remaining string.
     * @param $firstblock (boolean) if true the string is the starting of a line.
     * @param $maxh (float) maximum height. It should be >= $h and less then remaining space to the bottom of the page, or 0 for disable this feature.
     * @param $wadj (float) first line width will be reduced by this amount (used in HTML mode).
     * @param $margin (array) margin array of the parent container
     * @return mixed Return the number of cells or the remaining string if $firstline = true.
     * @public
     * @since 1.5
     */
    public function Write($h, $txt, $link='', $fill=false, $align='', $ln=false, $stretch=0, $firstline=false, $firstblock=false, $maxh=0, $wadj=0, $margin='') {
        // check page for no-write regions and adapt page margins if necessary
        list($this->x, $this->y) = $this->checkPageRegions($h, $this->x, $this->y);
        if (strlen($txt) == 0) {
            // fix empty text
            $txt = ' ';
        }
        if ($margin === '') {
            // set default margins
            $margin = $this->cell_margin;
        }
        // remove carriage returns
        $s = str_replace("\r", '', $txt);
        // check if string contains arabic text
        if (preg_match(TCPDF_FONT_DATA::$uni_RE_PATTERN_ARABIC, $s)) {
            $arabic = true;
        } else {
            $arabic = false;
        }
        // check if string contains RTL text
        if ($arabic OR ($this->tmprtl == 'R') OR preg_match(TCPDF_FONT_DATA::$uni_RE_PATTERN_RTL, $s)) {
            $rtlmode = true;
        } else {
            $rtlmode = false;
        }
        // get a char width
        $chrwidth = $this->GetCharWidth(46); // dot character
        // get array of unicode values
        $chars = TCPDF_FONTS::UTF8StringToArray($s, $this->isunicode, $this->CurrentFont);
        // calculate maximum width for a single character on string
        $chrw = $this->GetArrStringWidth($chars, '', '', 0, true);
        array_walk($chrw, array($this, 'getRawCharWidth'));
        $maxchwidth = max($chrw);
        // get array of chars
        $uchars = TCPDF_FONTS::UTF8ArrayToUniArray($chars, $this->isunicode);
        // get the number of characters
        $nb = count($chars);
        // replacement for SHY character (minus symbol)
        $shy_replacement = 45;
        $shy_replacement_char = TCPDF_FONTS::unichr($shy_replacement, $this->isunicode);
        // widht for SHY replacement
        $shy_replacement_width = $this->GetCharWidth($shy_replacement);
        // page width
        $pw = $w = $this->w - $this->lMargin - $this->rMargin;
        // calculate remaining line width ($w)
        if ($this->rtl) {
            $w = $this->x - $this->lMargin;
        } else {
            $w = $this->w - $this->rMargin - $this->x;
        }
        // max column width
        $wmax = ($w - $wadj);
        if (!$firstline) {
            $wmax -= ($this->cell_padding['L'] + $this->cell_padding['R']);
        }
        if ((!$firstline) AND (($chrwidth > $wmax) OR ($maxchwidth > $wmax))) {
            // the maximum width character do not fit on column
            return '';
        }
        // minimum row height
        $row_height = max($h, $this->getCellHeight($this->FontSize));
        // max Y
        $maxy = $this->y + $maxh - max($row_height, $h);
        $start_page = $this->page;
        $i = 0; // character position
        $j = 0; // current starting position
        $sep = -1; // position of the last blank space
        $prevsep = $sep; // previous separator
        $shy = false; // true if the last blank is a soft hypen (SHY)
        $prevshy = $shy; // previous shy mode
        $l = 0; // current string length
        $nl = 0; //number of lines
        $linebreak = false;
        $pc = 0; // previous character
        // for each character
        while ($i < $nb) {
            if (($maxh > 0) AND ($this->y > $maxy) ) {
                break;
            }
            //Get the current character
            $c = $chars[$i];
            if ($c == 10) { // 10 = "\n" = new line
                //Explicit line break
                if ($align == 'J') {
                    if ($this->rtl) {
                        $talign = 'R';
                    } else {
                        $talign = 'L';
                    }
                } else {
                    $talign = $align;
                }
                $tmpstr = TCPDF_FONTS::UniArrSubString($uchars, $j, $i);
                if ($firstline) {
                    $startx = $this->x;
                    $tmparr = array_slice($chars, $j, ($i - $j));
                    if ($rtlmode) {
                        $tmparr = TCPDF_FONTS::utf8Bidi($tmparr, $tmpstr, $this->tmprtl, $this->isunicode, $this->CurrentFont);
                    }
                    $linew = $this->GetArrStringWidth($tmparr);
                    unset($tmparr);
                    if ($this->rtl) {
                        $this->endlinex = $startx - $linew;
                    } else {
                        $this->endlinex = $startx + $linew;
                    }
                    $w = $linew;
                    $tmpcellpadding = $this->cell_padding;
                    if ($maxh == 0) {
                        $this->SetCellPadding(0);
                    }
                }
                if ($firstblock AND $this->isRTLTextDir()) {
                    $tmpstr = $this->stringRightTrim($tmpstr);
                }
                // Skip newlines at the beginning of a page or column
                if (!empty($tmpstr) OR ($this->y < ($this->PageBreakTrigger - $row_height))) {
                    $this->Cell($w, $h, $tmpstr, 0, 1, $talign, $fill, $link, $stretch);
                }
                unset($tmpstr);
                if ($firstline) {
                    $this->cell_padding = $tmpcellpadding;
                    return (TCPDF_FONTS::UniArrSubString($uchars, $i));
                }
                ++$nl;
                $j = $i + 1;
                $l = 0;
                $sep = -1;
                $prevsep = $sep;
                $shy = false;
                // account for margin changes
                if ((($this->y + $this->lasth) > $this->PageBreakTrigger) AND ($this->inPageBody())) {
                    $this->AcceptPageBreak();
                    if ($this->rtl) {
                        $this->x -= $margin['R'];
                    } else {
                        $this->x += $margin['L'];
                    }
                    $this->lMargin += $margin['L'];
                    $this->rMargin += $margin['R'];
                }
                $w = $this->getRemainingWidth();
                $wmax = ($w - $this->cell_padding['L'] - $this->cell_padding['R']);
            } else {
                // 160 is the non-breaking space.
                // 173 is SHY (Soft Hypen).
                // \p{Z} or \p{Separator}: any kind of Unicode whitespace or invisible separator.
                // \p{Lo} or \p{Other_Letter}: a Unicode letter or ideograph that does not have lowercase and uppercase variants.
                // \p{Lo} is needed because Chinese characters are packed next to each other without spaces in between.
                if (($c != 160)
                    AND (($c == 173)
                        OR preg_match($this->re_spaces, TCPDF_FONTS::unichr($c, $this->isunicode))
                        OR (($c == 45)
                            AND ($i < ($nb - 1))
                            AND @preg_match('/[\p{L}]/'.$this->re_space['m'], TCPDF_FONTS::unichr($pc, $this->isunicode))
                            AND @preg_match('/[\p{L}]/'.$this->re_space['m'], TCPDF_FONTS::unichr($chars[($i + 1)], $this->isunicode))
                        )
                    )
                ) {
                    // update last blank space position
                    $prevsep = $sep;
                    $sep = $i;
                    // check if is a SHY
                    if (($c == 173) OR ($c == 45)) {
                        $prevshy = $shy;
                        $shy = true;
                        if ($pc == 45) {
                            $tmp_shy_replacement_width = 0;
                            $tmp_shy_replacement_char = '';
                        } else {
                            $tmp_shy_replacement_width = $shy_replacement_width;
                            $tmp_shy_replacement_char = $shy_replacement_char;
                        }
                    } else {
                        $shy = false;
                    }
                }
                // update string length
                if ($this->isUnicodeFont() AND ($arabic)) {
                    // with bidirectional algorithm some chars may be changed affecting the line length
                    // *** very slow ***
                    $l = $this->GetArrStringWidth(TCPDF_FONTS::utf8Bidi(array_slice($chars, $j, ($i - $j)), '', $this->tmprtl, $this->isunicode, $this->CurrentFont));
                } else {
                    $l += $this->GetCharWidth($c);
                }
                if (($l > $wmax) OR (($c == 173) AND (($l + $tmp_shy_replacement_width) >= $wmax))) {
                    if (($c == 173) AND (($l + $tmp_shy_replacement_width) > $wmax)) {
                        $sep = $prevsep;
                        $shy = $prevshy;
                    }
                    // we have reached the end of column
                    if ($sep == -1) {
                        // check if the line was already started
                        if (($this->rtl AND ($this->x <= ($this->w - $this->rMargin - $this->cell_padding['R'] - $margin['R'] - $chrwidth)))
                            OR ((!$this->rtl) AND ($this->x >= ($this->lMargin + $this->cell_padding['L'] + $margin['L'] + $chrwidth)))) {
                            // print a void cell and go to next line
                            $this->Cell($w, $h, '', 0, 1);
                            $linebreak = true;
                            if ($firstline) {
                                return (TCPDF_FONTS::UniArrSubString($uchars, $j));
                            }
                        } else {
                            // truncate the word because do not fit on column
                            $tmpstr = TCPDF_FONTS::UniArrSubString($uchars, $j, $i);
                            if ($firstline) {
                                $startx = $this->x;
                                $tmparr = array_slice($chars, $j, ($i - $j));
                                if ($rtlmode) {
                                    $tmparr = TCPDF_FONTS::utf8Bidi($tmparr, $tmpstr, $this->tmprtl, $this->isunicode, $this->CurrentFont);
                                }
                                $linew = $this->GetArrStringWidth($tmparr);
                                unset($tmparr);
                                if ($this->rtl) {
                                    $this->endlinex = $startx - $linew;
                                } else {
                                    $this->endlinex = $startx + $linew;
                                }
                                $w = $linew;
                                $tmpcellpadding = $this->cell_padding;
                                if ($maxh == 0) {
                                    $this->SetCellPadding(0);
                                }
                            }
                            if ($firstblock AND $this->isRTLTextDir()) {
                                $tmpstr = $this->stringRightTrim($tmpstr);
                            }
                            $this->Cell($w, $h, $tmpstr, 0, 1, $align, $fill, $link, $stretch);
                            unset($tmpstr);
                            if ($firstline) {
                                $this->cell_padding = $tmpcellpadding;
                                return (TCPDF_FONTS::UniArrSubString($uchars, $i));
                            }
                            $j = $i;
                            --$i;
                        }
                    } else {
                        // word wrapping
                        if ($this->rtl AND (!$firstblock) AND ($sep < $i)) {
                            $endspace = 1;
                        } else {
                            $endspace = 0;
                        }
                        // check the length of the next string
                        $strrest = TCPDF_FONTS::UniArrSubString($uchars, ($sep + $endspace));
                        $nextstr = TCPDF_STATIC::pregSplit('/'.$this->re_space['p'].'/', $this->re_space['m'], $this->stringTrim($strrest));
                        if (isset($nextstr[0]) AND ($this->GetStringWidth($nextstr[0]) > $pw)) {
                            // truncate the word because do not fit on a full page width
                            $tmpstr = TCPDF_FONTS::UniArrSubString($uchars, $j, $i);
                            if ($firstline) {
                                $startx = $this->x;
                                $tmparr = array_slice($chars, $j, ($i - $j));
                                if ($rtlmode) {
                                    $tmparr = TCPDF_FONTS::utf8Bidi($tmparr, $tmpstr, $this->tmprtl, $this->isunicode, $this->CurrentFont);
                                }
                                $linew = $this->GetArrStringWidth($tmparr);
                                unset($tmparr);
                                if ($this->rtl) {
                                    $this->endlinex = ($startx - $linew);
                                } else {
                                    $this->endlinex = ($startx + $linew);
                                }
                                $w = $linew;
                                $tmpcellpadding = $this->cell_padding;
                                if ($maxh == 0) {
                                    $this->SetCellPadding(0);
                                }
                            }
                            if ($firstblock AND $this->isRTLTextDir()) {
                                $tmpstr = $this->stringRightTrim($tmpstr);
                            }
                            $this->Cell($w, $h, $tmpstr, 0, 1, $align, $fill, $link, $stretch);
                            unset($tmpstr);
                            if ($firstline) {
                                $this->cell_padding = $tmpcellpadding;
                                return (TCPDF_FONTS::UniArrSubString($uchars, $i));
                            }
                            $j = $i;
                            --$i;
                        } else {
                            // word wrapping
                            if ($shy) {
                                // add hypen (minus symbol) at the end of the line
                                $shy_width = $tmp_shy_replacement_width;
                                if ($this->rtl) {
                                    $shy_char_left = $tmp_shy_replacement_char;
                                    $shy_char_right = '';
                                } else {
                                    $shy_char_left = '';
                                    $shy_char_right = $tmp_shy_replacement_char;
                                }
                            } else {
                                $shy_width = 0;
                                $shy_char_left = '';
                                $shy_char_right = '';
                            }
                            $tmpstr = TCPDF_FONTS::UniArrSubString($uchars, $j, ($sep + $endspace));
                            if ($firstline) {
                                $startx = $this->x;
                                $tmparr = array_slice($chars, $j, (($sep + $endspace) - $j));
                                if ($rtlmode) {
                                    $tmparr = TCPDF_FONTS::utf8Bidi($tmparr, $tmpstr, $this->tmprtl, $this->isunicode, $this->CurrentFont);
                                }
                                $linew = $this->GetArrStringWidth($tmparr);
                                unset($tmparr);
                                if ($this->rtl) {
                                    $this->endlinex = $startx - $linew - $shy_width;
                                } else {
                                    $this->endlinex = $startx + $linew + $shy_width;
                                }
                                $w = $linew;
                                $tmpcellpadding = $this->cell_padding;
                                if ($maxh == 0) {
                                    $this->SetCellPadding(0);
                                }
                            }
                            // print the line
                            if ($firstblock AND $this->isRTLTextDir()) {
                                $tmpstr = $this->stringRightTrim($tmpstr);
                            }
                            $this->Cell($w, $h, $shy_char_left.$tmpstr.$shy_char_right, 0, 1, $align, $fill, $link, $stretch);
                            unset($tmpstr);
                            if ($firstline) {
                                if ($chars[$sep] == 45) {
                                    $endspace += 1;
                                }
                                // return the remaining text
                                $this->cell_padding = $tmpcellpadding;
                                return (TCPDF_FONTS::UniArrSubString($uchars, ($sep + $endspace)));
                            }
                            $i = $sep;
                            $sep = -1;
                            $shy = false;
                            $j = ($i + 1);
                        }
                    }
                    // account for margin changes
                    if ((($this->y + $this->lasth) > $this->PageBreakTrigger) AND ($this->inPageBody())) {
                        $this->AcceptPageBreak();
                        if ($this->rtl) {
                            $this->x -= $margin['R'];
                        } else {
                            $this->x += $margin['L'];
                        }
                        $this->lMargin += $margin['L'];
                        $this->rMargin += $margin['R'];
                    }
                    $w = $this->getRemainingWidth();
                    $wmax = $w - $this->cell_padding['L'] - $this->cell_padding['R'];
                    if ($linebreak) {
                        $linebreak = false;
                    } else {
                        ++$nl;
                        $l = 0;
                    }
                }
            }
            // save last character
            $pc = $c;
            ++$i;
        } // end while i < nb
        // print last substring (if any)
        if ($l > 0) {
            switch ($align) {
                case 'J':
                case 'C': {
                    $w = $w;
                    break;
                }
                case 'L': {
                    if ($this->rtl) {
                        $w = $w;
                    } else {
                        $w = $l;
                    }
                    break;
                }
                case 'R': {
                    if ($this->rtl) {
                        $w = $l;
                    } else {
                        $w = $w;
                    }
                    break;
                }
                default: {
                    $w = $l;
                    break;
                }
            }
            $tmpstr = TCPDF_FONTS::UniArrSubString($uchars, $j, $nb);
            if ($firstline) {
                $startx = $this->x;
                $tmparr = array_slice($chars, $j, ($nb - $j));
                if ($rtlmode) {
                    $tmparr = TCPDF_FONTS::utf8Bidi($tmparr, $tmpstr, $this->tmprtl, $this->isunicode, $this->CurrentFont);
                }
                $linew = $this->GetArrStringWidth($tmparr);
                unset($tmparr);
                if ($this->rtl) {
                    $this->endlinex = $startx - $linew;
                } else {
                    $this->endlinex = $startx + $linew;
                }
                $w = $linew;
                $tmpcellpadding = $this->cell_padding;
                if ($maxh == 0) {
                    $this->SetCellPadding(0);
                }
            }
            if ($firstblock AND $this->isRTLTextDir()) {
                $tmpstr = $this->stringRightTrim($tmpstr);
            }
            $this->Cell($w, $h, $tmpstr, 0, $ln, $align, $fill, $link, $stretch);
            unset($tmpstr);
            if ($firstline) {
                $this->cell_padding = $tmpcellpadding;
                return (TCPDF_FONTS::UniArrSubString($uchars, $nb));
            }
            ++$nl;
        }
        if ($firstline) {
            return '';
        }
        return $nl;
    }

    /**
     * Returns the remaining width between the current position and margins.
     * @return int Return the remaining width
     * @protected
     */
    protected function getRemainingWidth() {
        list($this->x, $this->y) = $this->checkPageRegions(0, $this->x, $this->y);
        if ($this->rtl) {
            return ($this->x - $this->lMargin);
        } else {
            return ($this->w - $this->rMargin - $this->x);
        }
    }

    /**
     * Set the block dimensions accounting for page breaks and page/column fitting
     * @param $w (float) width
     * @param $h (float) height
     * @param $x (float) X coordinate
     * @param $y (float) Y coodiante
     * @param $fitonpage (boolean) if true the block is resized to not exceed page dimensions.
     * @return array($w, $h, $x, $y)
     * @protected
     * @since 5.5.009 (2010-07-05)
     */
    protected function fitBlock($w, $h, $x, $y, $fitonpage=false) {
        if ($w <= 0) {
            // set maximum width
            $w = ($this->w - $this->lMargin - $this->rMargin);
            if ($w <= 0) {
                $w = 1;
            }
        }
        if ($h <= 0) {
            // set maximum height
            $h = ($this->PageBreakTrigger - $this->tMargin);
            if ($h <= 0) {
                $h = 1;
            }
        }
        // resize the block to be vertically contained on a single page or single column
        if ($fitonpage OR $this->AutoPageBreak) {
            $ratio_wh = ($w / $h);
            if ($h > ($this->PageBreakTrigger - $this->tMargin)) {
                $h = $this->PageBreakTrigger - $this->tMargin;
                $w = ($h * $ratio_wh);
            }
            // resize the block to be horizontally contained on a single page or single column
            if ($fitonpage) {
                $maxw = ($this->w - $this->lMargin - $this->rMargin);
                if ($w > $maxw) {
                    $w = $maxw;
                    $h = ($w / $ratio_wh);
                }
            }
        }
        // Check whether we need a new page or new column first as this does not fit
        $prev_x = $this->x;
        $prev_y = $this->y;
        if ($this->checkPageBreak($h, $y) OR ($this->y < $prev_y)) {
            $y = $this->y;
            if ($this->rtl) {
                $x += ($prev_x - $this->x);
            } else {
                $x += ($this->x - $prev_x);
            }
            $this->newline = true;
        }
        // resize the block to be contained on the remaining available page or column space
        if ($fitonpage) {
            $ratio_wh = ($w / $h);
            if (($y + $h) > $this->PageBreakTrigger) {
                $h = $this->PageBreakTrigger - $y;
                $w = ($h * $ratio_wh);
            }
            if ((!$this->rtl) AND (($x + $w) > ($this->w - $this->rMargin))) {
                $w = $this->w - $this->rMargin - $x;
                $h = ($w / $ratio_wh);
            } elseif (($this->rtl) AND (($x - $w) < ($this->lMargin))) {
                $w = $x - $this->lMargin;
                $h = ($w / $ratio_wh);
            }
        }
        return array($w, $h, $x, $y);
    }

    /**
     * Puts an image in the page.
     * The upper-left corner must be given.
     * The dimensions can be specified in different ways:<ul>
     * <li>explicit width and height (expressed in user unit)</li>
     * <li>one explicit dimension, the other being calculated automatically in order to keep the original proportions</li>
     * <li>no explicit dimension, in which case the image is put at 72 dpi</li></ul>
     * Supported formats are JPEG and PNG images whitout GD library and all images supported by GD: GD, GD2, GD2PART, GIF, JPEG, PNG, BMP, XBM, XPM;
     * The format can be specified explicitly or inferred from the file extension.<br />
     * It is possible to put a link on the image.<br />
     * Remark: if an image is used several times, only one copy will be embedded in the file.<br />
     * @param $file (string) Name of the file containing the image or a '@' character followed by the image data string. To link an image without embedding it on the document, set an asterisk character before the URL (i.e.: '*http://www.example.com/image.jpg').
     * @param $x (float) Abscissa of the upper-left corner (LTR) or upper-right corner (RTL).
     * @param $y (float) Ordinate of the upper-left corner (LTR) or upper-right corner (RTL).
     * @param $w (float) Width of the image in the page. If not specified or equal to zero, it is automatically calculated.
     * @param $h (float) Height of the image in the page. If not specified or equal to zero, it is automatically calculated.
     * @param $type (string) Image format. Possible values are (case insensitive): JPEG and PNG (whitout GD library) and all images supported by GD: GD, GD2, GD2PART, GIF, JPEG, PNG, BMP, XBM, XPM;. If not specified, the type is inferred from the file extension.
     * @param $link (mixed) URL or identifier returned by AddLink().
     * @param $align (string) Indicates the alignment of the pointer next to image insertion relative to image height. The value can be:<ul><li>T: top-right for LTR or top-left for RTL</li><li>M: middle-right for LTR or middle-left for RTL</li><li>B: bottom-right for LTR or bottom-left for RTL</li><li>N: next line</li></ul>
     * @param $resize (mixed) If true resize (reduce) the image to fit $w and $h (requires GD or ImageMagick library); if false do not resize; if 2 force resize in all cases (upscaling and downscaling).
     * @param $dpi (int) dot-per-inch resolution used on resize
     * @param $palign (string) Allows to center or align the image on the current line. Possible values are:<ul><li>L : left align</li><li>C : center</li><li>R : right align</li><li>'' : empty string : left for LTR or right for RTL</li></ul>
     * @param $ismask (boolean) true if this image is a mask, false otherwise
     * @param $imgmask (mixed) image object returned by this function or false
     * @param $border (mixed) Indicates if borders must be drawn around the cell. The value can be a number:<ul><li>0: no border (default)</li><li>1: frame</li></ul> or a string containing some or all of the following characters (in any order):<ul><li>L: left</li><li>T: top</li><li>R: right</li><li>B: bottom</li></ul> or an array of line styles for each border group - for example: array('LTRB' => array('width' => 2, 'cap' => 'butt', 'join' => 'miter', 'dash' => 0, 'color' => array(0, 0, 0)))
     * @param $fitbox (mixed) If not false scale image dimensions proportionally to fit within the ($w, $h) box. $fitbox can be true or a 2 characters string indicating the image alignment inside the box. The first character indicate the horizontal alignment (L = left, C = center, R = right) the second character indicate the vertical algnment (T = top, M = middle, B = bottom).
     * @param $hidden (boolean) If true do not display the image.
     * @param $fitonpage (boolean) If true the image is resized to not exceed page dimensions.
     * @param $alt (boolean) If true the image will be added as alternative and not directly printed (the ID of the image will be returned).
     * @param $altimgs (array) Array of alternate images IDs. Each alternative image must be an array with two values: an integer representing the image ID (the value returned by the Image method) and a boolean value to indicate if the image is the default for printing.
     * @return image information
     * @public
     * @since 1.1
     */
    public function Image($file, $x='', $y='', $w=0, $h=0, $type='', $link='', $align='', $resize=false, $dpi=300, $palign='', $ismask=false, $imgmask=false, $border=0, $fitbox=false, $hidden=false, $fitonpage=false, $alt=false, $altimgs=array()) {
        if ($this->state != 2) {
            return;
        }
        if (strcmp($x, '') === 0) {
            $x = $this->x;
        }
        if (strcmp($y, '') === 0) {
            $y = $this->y;
        }
        // check page for no-write regions and adapt page margins if necessary
        list($x, $y) = $this->checkPageRegions($h, $x, $y);
        $exurl = ''; // external streams
        $imsize = FALSE;
        // check if we are passing an image as file or string
        if ($file[0] === '@') {
            // image from string
            $imgdata = substr($file, 1);
        } else { // image file
            if ($file[0] === '*') {
                // image as external stream
                $file = substr($file, 1);
                $exurl = $file;
            }
            // check if is a local file
            if (!@file_exists($file)) {
                // try to encode spaces on filename
                $tfile = str_replace(' ', '%20', $file);
                if (@file_exists($tfile)) {
                    $file = $tfile;
                }
            }
            if (($imsize = @getimagesize($file)) === FALSE) {
                if (in_array($file, $this->imagekeys)) {
                    // get existing image data
                    $info = $this->getImageBuffer($file);
                    $imsize = array($info['w'], $info['h']);
                } elseif (strpos($file, '__tcpdf_'.$this->file_id.'_img') === FALSE) {
                    $imgdata = TCPDF_STATIC::fileGetContents($file);
                }
            }
        }
        if (!empty($imgdata)) {
            // copy image to cache
            $original_file = $file;
            $file = TCPDF_STATIC::getObjFilename('img', $this->file_id);
            $fp = TCPDF_STATIC::fopenLocal($file, 'w');
            if (!$fp) {
                $this->Error('Unable to write file: '.$file);
            }
            fwrite($fp, $imgdata);
            fclose($fp);
            unset($imgdata);
            $imsize = @getimagesize($file);
            if ($imsize === FALSE) {
                unlink($file);
                $file = $original_file;
            }
        }
        if ($imsize === FALSE) {
            if (($w > 0) AND ($h > 0)) {
                // get measures from specified data
                $pw = $this->getHTMLUnitToUnits($w, 0, $this->pdfunit, true) * $this->imgscale * $this->k;
                $ph = $this->getHTMLUnitToUnits($h, 0, $this->pdfunit, true) * $this->imgscale * $this->k;
                $imsize = array($pw, $ph);
            } else {
                $this->Error('[Image] Unable to get the size of the image: '.$file);
            }
        }
        // file hash
        $filehash = md5($file);
        // get original image width and height in pixels
        list($pixw, $pixh) = $imsize;
        // calculate image width and height on document
        if (($w <= 0) AND ($h <= 0)) {
            // convert image size to document unit
            $w = $this->pixelsToUnits($pixw);
            $h = $this->pixelsToUnits($pixh);
        } elseif ($w <= 0) {
            $w = $h * $pixw / $pixh;
        } elseif ($h <= 0) {
            $h = $w * $pixh / $pixw;
        } elseif (($fitbox !== false) AND ($w > 0) AND ($h > 0)) {
            if (strlen($fitbox) !== 2) {
                // set default alignment
                $fitbox = '--';
            }
            // scale image dimensions proportionally to fit within the ($w, $h) box
            if ((($w * $pixh) / ($h * $pixw)) < 1) {
                // store current height
                $oldh = $h;
                // calculate new height
                $h = $w * $pixh / $pixw;
                // height difference
                $hdiff = ($oldh - $h);
                // vertical alignment
                switch (strtoupper($fitbox[1])) {
                    case 'T': {
                        break;
                    }
                    case 'M': {
                        $y += ($hdiff / 2);
                        break;
                    }
                    case 'B': {
                        $y += $hdiff;
                        break;
                    }
                }
            } else {
                // store current width
                $oldw = $w;
                // calculate new width
                $w = $h * $pixw / $pixh;
                // width difference
                $wdiff = ($oldw - $w);
                // horizontal alignment
                switch (strtoupper($fitbox[0])) {
                    case 'L': {
                        if ($this->rtl) {
                            $x -= $wdiff;
                        }
                        break;
                    }
                    case 'C': {
                        if ($this->rtl) {
                            $x -= ($wdiff / 2);
                        } else {
                            $x += ($wdiff / 2);
                        }
                        break;
                    }
                    case 'R': {
                        if (!$this->rtl) {
                            $x += $wdiff;
                        }
                        break;
                    }
                }
            }
        }
        // fit the image on available space
        list($w, $h, $x, $y) = $this->fitBlock($w, $h, $x, $y, $fitonpage);
        // calculate new minimum dimensions in pixels
        $neww = round($w * $this->k * $dpi / $this->dpi);
        $newh = round($h * $this->k * $dpi / $this->dpi);
        // check if resize is necessary (resize is used only to reduce the image)
        $newsize = ($neww * $newh);
        $pixsize = ($pixw * $pixh);
        if (intval($resize) == 2) {
            $resize = true;
        } elseif ($newsize >= $pixsize) {
            $resize = false;
        }
        // check if image has been already added on document
        $newimage = true;
        if (in_array($file, $this->imagekeys)) {
            $newimage = false;
            // get existing image data
            $info = $this->getImageBuffer($file);
            if (strpos($file, '__tcpdf_'.$this->file_id.'_imgmask_') === FALSE) {
                // check if the newer image is larger
                $oldsize = ($info['w'] * $info['h']);
                if ((($oldsize < $newsize) AND ($resize)) OR (($oldsize < $pixsize) AND (!$resize))) {
                    $newimage = true;
                }
            }
        } elseif (($ismask === false) AND ($imgmask === false) AND (strpos($file, '__tcpdf_'.$this->file_id.'_imgmask_') === FALSE)) {
            // create temp image file (without alpha channel)
            $tempfile_plain = K_PATH_CACHE.'__tcpdf_'.$this->file_id.'_imgmask_plain_'.$filehash;
            // create temp alpha file
            $tempfile_alpha = K_PATH_CACHE.'__tcpdf_'.$this->file_id.'_imgmask_alpha_'.$filehash;
            // check for cached images
            if (in_array($tempfile_plain, $this->imagekeys)) {
                // get existing image data
                $info = $this->getImageBuffer($tempfile_plain);
                // check if the newer image is larger
                $oldsize = ($info['w'] * $info['h']);
                if ((($oldsize < $newsize) AND ($resize)) OR (($oldsize < $pixsize) AND (!$resize))) {
                    $newimage = true;
                } else {
                    $newimage = false;
                    // embed mask image
                    $imgmask = $this->Image($tempfile_alpha, $x, $y, $w, $h, 'PNG', '', '', $resize, $dpi, '', true, false);
                    // embed image, masked with previously embedded mask
                    return $this->Image($tempfile_plain, $x, $y, $w, $h, $type, $link, $align, $resize, $dpi, $palign, false, $imgmask);
                }
            }
        }
        if ($newimage) {
            //First use of image, get info
            $type = strtolower($type);
            if ($type == '') {
                $type = TCPDF_IMAGES::getImageFileType($file, $imsize);
            } elseif ($type == 'jpg') {
                $type = 'jpeg';
            }
            $mqr = TCPDF_STATIC::get_mqr();
            TCPDF_STATIC::set_mqr(false);
            // Specific image handlers (defined on TCPDF_IMAGES CLASS)
            $mtd = '_parse'.$type;
            // GD image handler function
            $gdfunction = 'imagecreatefrom'.$type;
            $info = false;
            if ((method_exists('TCPDF_IMAGES', $mtd)) AND (!($resize AND (function_exists($gdfunction) OR extension_loaded('imagick'))))) {
                // TCPDF image functions
                $info = TCPDF_IMAGES::$mtd($file);
                if (($ismask === false) AND ($imgmask === false) AND (strpos($file, '__tcpdf_'.$this->file_id.'_imgmask_') === FALSE)
                    AND (($info === 'pngalpha') OR (isset($info['trns']) AND !empty($info['trns'])))) {
                    return $this->ImagePngAlpha($file, $x, $y, $pixw, $pixh, $w, $h, 'PNG', $link, $align, $resize, $dpi, $palign, $filehash);
                }
            }
            if (($info === false) AND function_exists($gdfunction)) {
                try {
                    // GD library
                    $img = $gdfunction($file);
                    if ($img !== false) {
                        if ($resize) {
                            $imgr = imagecreatetruecolor($neww, $newh);
                            if (($type == 'gif') OR ($type == 'png')) {
                                $imgr = TCPDF_IMAGES::setGDImageTransparency($imgr, $img);
                            }
                            imagecopyresampled($imgr, $img, 0, 0, 0, 0, $neww, $newh, $pixw, $pixh);
                            $img = $imgr;
                        }
                        if (($type == 'gif') OR ($type == 'png')) {
                            $info = TCPDF_IMAGES::_toPNG($img, TCPDF_STATIC::getObjFilename('img', $this->file_id));
                        } else {
                            $info = TCPDF_IMAGES::_toJPEG($img, $this->jpeg_quality, TCPDF_STATIC::getObjFilename('img', $this->file_id));
                        }
                    }
                } catch(Exception $e) {
                    $info = false;
                }
            }
            if (($info === false) AND extension_loaded('imagick')) {
                try {
                    // ImageMagick library
                    $img = new Imagick();
                    if ($type == 'svg') {
                        if ($file[0] === '@') {
                            // image from string
                            $svgimg = substr($file, 1);
                        } else {
                            // get SVG file content
                            $svgimg = TCPDF_STATIC::fileGetContents($file);
                        }
                        if ($svgimg !== FALSE) {
                            // get width and height
                            $regs = array();
                            if (preg_match('/<svg([^\>]*)>/si', $svgimg, $regs)) {
                                $svgtag = $regs[1];
                                $tmp = array();
                                if (preg_match('/[\s]+width[\s]*=[\s]*"([^"]*)"/si', $svgtag, $tmp)) {
                                    $ow = $this->getHTMLUnitToUnits($tmp[1], 1, $this->svgunit, false);
                                    $owu = sprintf('%F', ($ow * $dpi / 72)).$this->pdfunit;
                                    $svgtag = preg_replace('/[\s]+width[\s]*=[\s]*"[^"]*"/si', ' width="'.$owu.'"', $svgtag, 1);
                                } else {
                                    $ow = $w;
                                }
                                $tmp = array();
                                if (preg_match('/[\s]+height[\s]*=[\s]*"([^"]*)"/si', $svgtag, $tmp)) {
                                    $oh = $this->getHTMLUnitToUnits($tmp[1], 1, $this->svgunit, false);
                                    $ohu = sprintf('%F', ($oh * $dpi / 72)).$this->pdfunit;
                                    $svgtag = preg_replace('/[\s]+height[\s]*=[\s]*"[^"]*"/si', ' height="'.$ohu.'"', $svgtag, 1);
                                } else {
                                    $oh = $h;
                                }
                                $tmp = array();
                                if (!preg_match('/[\s]+viewBox[\s]*=[\s]*"[\s]*([0-9\.]+)[\s]+([0-9\.]+)[\s]+([0-9\.]+)[\s]+([0-9\.]+)[\s]*"/si', $svgtag, $tmp)) {
                                    $vbw = ($ow * $this->imgscale * $this->k);
                                    $vbh = ($oh * $this->imgscale * $this->k);
                                    $vbox = sprintf(' viewBox="0 0 %F %F" ', $vbw, $vbh);
                                    $svgtag = $vbox.$svgtag;
                                }
                                $svgimg = preg_replace('/<svg([^\>]*)>/si', '<svg'.$svgtag.'>', $svgimg, 1);
                            }
                            $img->readImageBlob($svgimg);
                        }
                    } else {
                        $img->readImage($file);
                    }
                    if ($resize) {
                        $img->resizeImage($neww, $newh, 10, 1, false);
                    }
                    $img->setCompressionQuality($this->jpeg_quality);
                    $img->setImageFormat('jpeg');
                    $tempname = TCPDF_STATIC::getObjFilename('img', $this->file_id);
                    $img->writeImage($tempname);
                    $info = TCPDF_IMAGES::_parsejpeg($tempname);
                    unlink($tempname);
                    $img->destroy();
                } catch(Exception $e) {
                    $info = false;
                }
            }
            if ($info === false) {
                // unable to process image
                return;
            }
            TCPDF_STATIC::set_mqr($mqr);
            if ($ismask) {
                // force grayscale
                $info['cs'] = 'DeviceGray';
            }
            if ($imgmask !== false) {
                $info['masked'] = $imgmask;
            }
            if (!empty($exurl)) {
                $info['exurl'] = $exurl;
            }
            // array of alternative images
            $info['altimgs'] = $altimgs;
            // add image to document
            $info['i'] = $this->setImageBuffer($file, $info);
        }
        // set alignment
        $this->img_rb_y = $y + $h;
        // set alignment
        if ($this->rtl) {
            if ($palign == 'L') {
                $ximg = $this->lMargin;
            } elseif ($palign == 'C') {
                $ximg = ($this->w + $this->lMargin - $this->rMargin - $w) / 2;
            } elseif ($palign == 'R') {
                $ximg = $this->w - $this->rMargin - $w;
            } else {
                $ximg = $x - $w;
            }
            $this->img_rb_x = $ximg;
        } else {
            if ($palign == 'L') {
                $ximg = $this->lMargin;
            } elseif ($palign == 'C') {
                $ximg = ($this->w + $this->lMargin - $this->rMargin - $w) / 2;
            } elseif ($palign == 'R') {
                $ximg = $this->w - $this->rMargin - $w;
            } else {
                $ximg = $x;
            }
            $this->img_rb_x = $ximg + $w;
        }
        if ($ismask OR $hidden) {
            // image is not displayed
            return $info['i'];
        }
        $xkimg = $ximg * $this->k;
        if (!$alt) {
            // only non-alternative immages will be set
            $this->_out(sprintf('q %F 0 0 %F %F %F cm /I%u Do Q', ($w * $this->k), ($h * $this->k), $xkimg, (($this->h - ($y + $h)) * $this->k), $info['i']));
        }
        if (!empty($border)) {
            $bx = $this->x;
            $by = $this->y;
            $this->x = $ximg;
            if ($this->rtl) {
                $this->x += $w;
            }
            $this->y = $y;
            $this->Cell($w, $h, '', $border, 0, '', 0, '', 0, true);
            $this->x = $bx;
            $this->y = $by;
        }
        if ($link) {
            $this->Link($ximg, $y, $w, $h, $link, 0);
        }
        // set pointer to align the next text/objects
        switch($align) {
            case 'T': {
                $this->y = $y;
                $this->x = $this->img_rb_x;
                break;
            }
            case 'M': {
                $this->y = $y + round($h/2);
                $this->x = $this->img_rb_x;
                break;
            }
            case 'B': {
                $this->y = $this->img_rb_y;
                $this->x = $this->img_rb_x;
                break;
            }
            case 'N': {
                $this->SetY($this->img_rb_y);
                break;
            }
            default:{
                break;
            }
        }
        $this->endlinex = $this->img_rb_x;
        if ($this->inxobj) {
            // we are inside an XObject template
            $this->xobjects[$this->xobjid]['images'][] = $info['i'];
        }
        return $info['i'];
    }

    /**
     * Extract info from a PNG image with alpha channel using the Imagick or GD library.
     * @param $file (string) Name of the file containing the image.
     * @param $x (float) Abscissa of the upper-left corner.
     * @param $y (float) Ordinate of the upper-left corner.
     * @param $wpx (float) Original width of the image in pixels.
     * @param $hpx (float) original height of the image in pixels.
     * @param $w (float) Width of the image in the page. If not specified or equal to zero, it is automatically calculated.
     * @param $h (float) Height of the image in the page. If not specified or equal to zero, it is automatically calculated.
     * @param $type (string) Image format. Possible values are (case insensitive): JPEG and PNG (whitout GD library) and all images supported by GD: GD, GD2, GD2PART, GIF, JPEG, PNG, BMP, XBM, XPM;. If not specified, the type is inferred from the file extension.
     * @param $link (mixed) URL or identifier returned by AddLink().
     * @param $align (string) Indicates the alignment of the pointer next to image insertion relative to image height. The value can be:<ul><li>T: top-right for LTR or top-left for RTL</li><li>M: middle-right for LTR or middle-left for RTL</li><li>B: bottom-right for LTR or bottom-left for RTL</li><li>N: next line</li></ul>
     * @param $resize (boolean) If true resize (reduce) the image to fit $w and $h (requires GD library).
     * @param $dpi (int) dot-per-inch resolution used on resize
     * @param $palign (string) Allows to center or align the image on the current line. Possible values are:<ul><li>L : left align</li><li>C : center</li><li>R : right align</li><li>'' : empty string : left for LTR or right for RTL</li></ul>
     * @param $filehash (string) File hash used to build unique file names.
     * @author Nicola Asuni
     * @protected
     * @since 4.3.007 (2008-12-04)
     * @see Image()
     */
    protected function ImagePngAlpha($file, $x, $y, $wpx, $hpx, $w, $h, $type, $link, $align, $resize, $dpi, $palign, $filehash='') {
        // create temp images
        if (empty($filehash)) {
            $filehash = md5($file);
        }
        // create temp image file (without alpha channel)
        $tempfile_plain = K_PATH_CACHE.'__tcpdf_'.$this->file_id.'_imgmask_plain_'.$filehash;
        // create temp alpha file
        $tempfile_alpha = K_PATH_CACHE.'__tcpdf_'.$this->file_id.'_imgmask_alpha_'.$filehash;
        $parsed = false;
        $parse_error = '';
        // ImageMagick extension
        if (($parsed === false) AND extension_loaded('imagick')) {
            try {
                // ImageMagick library
                $img = new Imagick();
                $img->readImage($file);
                // clone image object
                $imga = TCPDF_STATIC::objclone($img);
                // extract alpha channel
                if (method_exists($img, 'setImageAlphaChannel') AND defined('Imagick::ALPHACHANNEL_EXTRACT')) {
                    $img->setImageAlphaChannel(Imagick::ALPHACHANNEL_EXTRACT);
                } else {
                    $img->separateImageChannel(8); // 8 = (imagick::CHANNEL_ALPHA | imagick::CHANNEL_OPACITY | imagick::CHANNEL_MATTE);
                    $img->negateImage(true);
                }
                $img->setImageFormat('png');
                $img->writeImage($tempfile_alpha);
                // remove alpha channel
                if (method_exists($imga, 'setImageMatte')) {
                    $imga->setImageMatte(false);
                } else {
                    $imga->separateImageChannel(39); // 39 = (imagick::CHANNEL_ALL & ~(imagick::CHANNEL_ALPHA | imagick::CHANNEL_OPACITY | imagick::CHANNEL_MATTE));
                }
                $imga->setImageFormat('png');
                $imga->writeImage($tempfile_plain);
                $parsed = true;
            } catch (Exception $e) {
                // Imagemagick fails, try with GD
                $parse_error = 'Imagick library error: '.$e->getMessage();
            }
        }
        // GD extension
        if (($parsed === false) AND function_exists('imagecreatefrompng')) {
            try {
                // generate images
                $img = imagecreatefrompng($file);
                $imgalpha = imagecreate($wpx, $hpx);
                // generate gray scale palette (0 -> 255)
                for ($c = 0; $c < 256; ++$c) {
                    ImageColorAllocate($imgalpha, $c, $c, $c);
                }
                // extract alpha channel
                for ($xpx = 0; $xpx < $wpx; ++$xpx) {
                    for ($ypx = 0; $ypx < $hpx; ++$ypx) {
                        $color = imagecolorat($img, $xpx, $ypx);
                        // get and correct gamma color
                        $alpha = $this->getGDgamma($img, $color);
                        imagesetpixel($imgalpha, $xpx, $ypx, $alpha);
                    }
                }
                imagepng($imgalpha, $tempfile_alpha);
                imagedestroy($imgalpha);
                // extract image without alpha channel
                $imgplain = imagecreatetruecolor($wpx, $hpx);
                imagecopy($imgplain, $img, 0, 0, 0, 0, $wpx, $hpx);
                imagepng($imgplain, $tempfile_plain);
                imagedestroy($imgplain);
                $parsed = true;
            } catch (Exception $e) {
                // GD fails
                $parse_error = 'GD library error: '.$e->getMessage();
            }
        }
        if ($parsed === false) {
            if (empty($parse_error)) {
                $this->Error('TCPDF requires the Imagick or GD extension to handle PNG images with alpha channel.');
            } else {
                $this->Error($parse_error);
            }
        }
        // embed mask image
        $imgmask = $this->Image($tempfile_alpha, $x, $y, $w, $h, 'PNG', '', '', $resize, $dpi, '', true, false);
        // embed image, masked with previously embedded mask
        $this->Image($tempfile_plain, $x, $y, $w, $h, $type, $link, $align, $resize, $dpi, $palign, false, $imgmask);
    }

    /**
     * Get the GD-corrected PNG gamma value from alpha color
     * @param $img (int) GD image Resource ID.
     * @param $c (int) alpha color
     * @protected
     * @since 4.3.007 (2008-12-04)
     */
    protected function getGDgamma($img, $c) {
        if (!isset($this->gdgammacache['#'.$c])) {
            $colors = imagecolorsforindex($img, $c);
            // GD alpha is only 7 bit (0 -> 127)
            $this->gdgammacache['#'.$c] = (((127 - $colors['alpha']) / 127) * 255);
            // correct gamma
            $this->gdgammacache['#'.$c] = (pow(($this->gdgammacache['#'.$c] / 255), 2.2) * 255);
            // store the latest values on cache to improve performances
            if (count($this->gdgammacache) > 8) {
                // remove one element from the cache array
                array_shift($this->gdgammacache);
            }
        }
        return $this->gdgammacache['#'.$c];
    }

    /**
     * Performs a line break.
     * The current abscissa goes back to the left margin and the ordinate increases by the amount passed in parameter.
     * @param $h (float) The height of the break. By default, the value equals the height of the last printed cell.
     * @param $cell (boolean) if true add the current left (or right o for RTL) padding to the X coordinate
     * @public
     * @since 1.0
     * @see Cell()
     */
    public function Ln($h='', $cell=false) {
        if (($this->num_columns > 1) AND ($this->y == $this->columns[$this->current_column]['y']) AND isset($this->columns[$this->current_column]['x']) AND ($this->x == $this->columns[$this->current_column]['x'])) {
            // revove vertical space from the top of the column
            return;
        }
        if ($cell) {
            if ($this->rtl) {
                $cellpadding = $this->cell_padding['R'];
            } else {
                $cellpadding = $this->cell_padding['L'];
            }
        } else {
            $cellpadding = 0;
        }
        if ($this->rtl) {
            $this->x = $this->w - $this->rMargin - $cellpadding;
        } else {
            $this->x = $this->lMargin + $cellpadding;
        }
        if (is_string($h)) {
            $h = $this->lasth;
        }
        $this->y += $h;
        $this->newline = true;
    }

    /**
     * Returns the relative X value of current position.
     * The value is relative to the left border for LTR languages and to the right border for RTL languages.
     * @return float
     * @public
     * @since 1.2
     * @see SetX(), GetY(), SetY()
     */
    public function GetX() {
        //Get x position
        if ($this->rtl) {
            return ($this->w - $this->x);
        } else {
            return $this->x;
        }
    }

    /**
     * Returns the absolute X value of current position.
     * @return float
     * @public
     * @since 1.2
     * @see SetX(), GetY(), SetY()
     */
    public function GetAbsX() {
        return $this->x;
    }

    /**
     * Returns the ordinate of the current position.
     * @return float
     * @public
     * @since 1.0
     * @see SetY(), GetX(), SetX()
     */
    public function GetY() {
        return $this->y;
    }

    /**
     * Defines the abscissa of the current position.
     * If the passed value is negative, it is relative to the right of the page (or left if language is RTL).
     * @param $x (float) The value of the abscissa in user units.
     * @param $rtloff (boolean) if true always uses the page top-left corner as origin of axis.
     * @public
     * @since 1.2
     * @see GetX(), GetY(), SetY(), SetXY()
     */
    public function SetX($x, $rtloff=false) {
        $x = floatval($x);
        if (!$rtloff AND $this->rtl) {
            if ($x >= 0) {
                $this->x = $this->w - $x;
            } else {
                $this->x = abs($x);
            }
        } else {
            if ($x >= 0) {
                $this->x = $x;
            } else {
                $this->x = $this->w + $x;
            }
        }
        if ($this->x < 0) {
            $this->x = 0;
        }
        if ($this->x > $this->w) {
            $this->x = $this->w;
        }
    }

    /**
     * Moves the current abscissa back to the left margin and sets the ordinate.
     * If the passed value is negative, it is relative to the bottom of the page.
     * @param $y (float) The value of the ordinate in user units.
     * @param $resetx (bool) if true (default) reset the X position.
     * @param $rtloff (boolean) if true always uses the page top-left corner as origin of axis.
     * @public
     * @since 1.0
     * @see GetX(), GetY(), SetY(), SetXY()
     */
    public function SetY($y, $resetx=true, $rtloff=false) {
        $y = floatval($y);
        if ($resetx) {
            //reset x
            if (!$rtloff AND $this->rtl) {
                $this->x = $this->w - $this->rMargin;
            } else {
                $this->x = $this->lMargin;
            }
        }
        if ($y >= 0) {
            $this->y = $y;
        } else {
            $this->y = $this->h + $y;
        }
        if ($this->y < 0) {
            $this->y = 0;
        }
        if ($this->y > $this->h) {
            $this->y = $this->h;
        }
    }

    /**
     * Defines the abscissa and ordinate of the current position.
     * If the passed values are negative, they are relative respectively to the right and bottom of the page.
     * @param $x (float) The value of the abscissa.
     * @param $y (float) The value of the ordinate.
     * @param $rtloff (boolean) if true always uses the page top-left corner as origin of axis.
     * @public
     * @since 1.2
     * @see SetX(), SetY()
     */
    public function SetXY($x, $y, $rtloff=false) {
        $this->SetY($y, false, $rtloff);
        $this->SetX($x, $rtloff);
    }

    /**
     * Set the absolute X coordinate of the current pointer.
     * @param $x (float) The value of the abscissa in user units.
     * @public
     * @since 5.9.186 (2012-09-13)
     * @see setAbsX(), setAbsY(), SetAbsXY()
     */
    public function SetAbsX($x) {
        $this->x = floatval($x);
    }

    /**
     * Set the absolute Y coordinate of the current pointer.
     * @param $y (float) (float) The value of the ordinate in user units.
     * @public
     * @since 5.9.186 (2012-09-13)
     * @see setAbsX(), setAbsY(), SetAbsXY()
     */
    public function SetAbsY($y) {
        $this->y = floatval($y);
    }

    /**
     * Set the absolute X and Y coordinates of the current pointer.
     * @param $x (float) The value of the abscissa in user units.
     * @param $y (float) (float) The value of the ordinate in user units.
     * @public
     * @since 5.9.186 (2012-09-13)
     * @see setAbsX(), setAbsY(), SetAbsXY()
     */
    public function SetAbsXY($x, $y) {
        $this->SetAbsX($x);
        $this->SetAbsY($y);
    }

    /**
     * Send the document to a given destination: string, local file or browser.
     * In the last case, the plug-in may be used (if present) or a download ("Save as" dialog box) may be forced.<br />
     * The method first calls Close() if necessary to terminate the document.
     * @param $name (string) The name of the file when saved. Note that special characters are removed and blanks characters are replaced with the underscore character.
     * @param $dest (string) Destination where to send the document. It can take one of the following values:<ul><li>I: send the file inline to the browser (default). The plug-in is used if available. The name given by name is used when one selects the "Save as" option on the link generating the PDF.</li><li>D: send to the browser and force a file download with the name given by name.</li><li>F: save to a local server file with the name given by name.</li><li>S: return the document as a string (name is ignored).</li><li>FI: equivalent to F + I option</li><li>FD: equivalent to F + D option</li><li>E: return the document as base64 mime multi-part email attachment (RFC 2045)</li></ul>
     * @return string
     * @public
     * @since 1.0
     * @see Close()
     */
    public function Output($name='doc.pdf', $dest='I') {
        //Output PDF to some destination
        //Finish document if necessary
        if ($this->state < 3) {
            $this->Close();
        }
        //Normalize parameters
        if (is_bool($dest)) {
            $dest = $dest ? 'D' : 'F';
        }
        $dest = strtoupper($dest);
        if ($dest[0] != 'F') {
            $name = preg_replace('/[\s]+/', '_', $name);
            $name = preg_replace('/[^a-zA-Z0-9_\.-]/', '', $name);
        }
        if ($this->sign) {
            // *** apply digital signature to the document ***
            // get the document content
            $pdfdoc = $this->getBuffer();
            // remove last newline
            $pdfdoc = substr($pdfdoc, 0, -1);
            // remove filler space
            $byterange_string_len = strlen(TCPDF_STATIC::$byterange_string);
            // define the ByteRange
            $byte_range = array();
            $byte_range[0] = 0;
            $byte_range[1] = strpos($pdfdoc, TCPDF_STATIC::$byterange_string) + $byterange_string_len + 10;
            $byte_range[2] = $byte_range[1] + $this->signature_max_length + 2;
            $byte_range[3] = strlen($pdfdoc) - $byte_range[2];
            $pdfdoc = substr($pdfdoc, 0, $byte_range[1]).substr($pdfdoc, $byte_range[2]);
            // replace the ByteRange
            $byterange = sprintf('/ByteRange[0 %u %u %u]', $byte_range[1], $byte_range[2], $byte_range[3]);
            $byterange .= str_repeat(' ', ($byterange_string_len - strlen($byterange)));
            $pdfdoc = str_replace(TCPDF_STATIC::$byterange_string, $byterange, $pdfdoc);
            // write the document to a temporary folder
            $tempdoc = TCPDF_STATIC::getObjFilename('doc', $this->file_id);
            $f = TCPDF_STATIC::fopenLocal($tempdoc, 'wb');
            if (!$f) {
                $this->Error('Unable to create temporary file: '.$tempdoc);
            }
            $pdfdoc_length = strlen($pdfdoc);
            fwrite($f, $pdfdoc, $pdfdoc_length);
            fclose($f);
            // get digital signature via openssl library
            $tempsign = TCPDF_STATIC::getObjFilename('sig', $this->file_id);
            if (empty($this->signature_data['extracerts'])) {
                openssl_pkcs7_sign($tempdoc, $tempsign, $this->signature_data['signcert'], array($this->signature_data['privkey'], $this->signature_data['password']), array(), PKCS7_BINARY | PKCS7_DETACHED);
            } else {
                openssl_pkcs7_sign($tempdoc, $tempsign, $this->signature_data['signcert'], array($this->signature_data['privkey'], $this->signature_data['password']), array(), PKCS7_BINARY | PKCS7_DETACHED, $this->signature_data['extracerts']);
            }
            // read signature
            $signature = file_get_contents($tempsign);
            // extract signature
            $signature = substr($signature, $pdfdoc_length);
            $signature = substr($signature, (strpos($signature, "%%EOF\n\n------") + 13));
            $tmparr = explode("\n\n", $signature);
            $signature = $tmparr[1];
            // decode signature
            $signature = base64_decode(trim($signature));
            // add TSA timestamp to signature
            $signature = $this->applyTSA($signature);
            // convert signature to hex
            $signature = current(unpack('H*', $signature));
            $signature = str_pad($signature, $this->signature_max_length, '0');
            // Add signature to the document
            $this->buffer = substr($pdfdoc, 0, $byte_range[1]).'<'.$signature.'>'.substr($pdfdoc, $byte_range[1]);
            $this->bufferlen = strlen($this->buffer);
        }
        switch($dest) {
            case 'I': {
                // Send PDF to the standard output
                if (ob_get_contents()) {
                    $this->Error('Some data has already been output, can\'t send PDF file');
                }
                if (php_sapi_name() != 'cli') {
                    // send output to a browser
                    header('Content-Type: application/pdf');
                    if (headers_sent()) {
                        $this->Error('Some data has already been output to browser, can\'t send PDF file');
                    }
                    header('Cache-Control: private, must-revalidate, post-check=0, pre-check=0, max-age=1');
                    //header('Cache-Control: public, must-revalidate, max-age=0'); // HTTP/1.1
                    header('Pragma: public');
                    header('Expires: Sat, 26 Jul 1997 05:00:00 GMT'); // Date in the past
                    header('Last-Modified: '.gmdate('D, d M Y H:i:s').' GMT');
                    header('Content-Disposition: inline; filename="'.basename($name).'"');
                    TCPDF_STATIC::sendOutputData($this->getBuffer(), $this->bufferlen);
                } else {
                    echo $this->getBuffer();
                }
                break;
            }
            case 'D': {
                // download PDF as file
                if (ob_get_contents()) {
                    $this->Error('Some data has already been output, can\'t send PDF file');
                }
                header('Content-Description: File Transfer');
                if (headers_sent()) {
                    $this->Error('Some data has already been output to browser, can\'t send PDF file');
                }
                header('Cache-Control: private, must-revalidate, post-check=0, pre-check=0, max-age=1');
                //header('Cache-Control: public, must-revalidate, max-age=0'); // HTTP/1.1
                header('Pragma: public');
                header('Expires: Sat, 26 Jul 1997 05:00:00 GMT'); // Date in the past
                header('Last-Modified: '.gmdate('D, d M Y H:i:s').' GMT');
                // force download dialog
                if (strpos(php_sapi_name(), 'cgi') === false) {
                    header('Content-Type: application/force-download');
                    header('Content-Type: application/octet-stream', false);
                    header('Content-Type: application/download', false);
                    header('Content-Type: application/pdf', false);
                } else {
                    header('Content-Type: application/pdf');
                }
                // use the Content-Disposition header to supply a recommended filename
                header('Content-Disposition: attachment; filename="'.basename($name).'"');
                header('Content-Transfer-Encoding: binary');
                TCPDF_STATIC::sendOutputData($this->getBuffer(), $this->bufferlen);
                break;
            }
            case 'F':
            case 'FI':
            case 'FD': {
                // save PDF to a local file
                $f = TCPDF_STATIC::fopenLocal($name, 'wb');
                if (!$f) {
                    $this->Error('Unable to create output file: '.$name);
                }
                fwrite($f, $this->getBuffer(), $this->bufferlen);
                fclose($f);
                if ($dest == 'FI') {
                    // send headers to browser
                    header('Content-Type: application/pdf');
                    header('Cache-Control: private, must-revalidate, post-check=0, pre-check=0, max-age=1');
                    //header('Cache-Control: public, must-revalidate, max-age=0'); // HTTP/1.1
                    header('Pragma: public');
                    header('Expires: Sat, 26 Jul 1997 05:00:00 GMT'); // Date in the past
                    header('Last-Modified: '.gmdate('D, d M Y H:i:s').' GMT');
                    header('Content-Disposition: inline; filename="'.basename($name).'"');
                    TCPDF_STATIC::sendOutputData(file_get_contents($name), filesize($name));
                } elseif ($dest == 'FD') {
                    // send headers to browser
                    if (ob_get_contents()) {
                        $this->Error('Some data has already been output, can\'t send PDF file');
                    }
                    header('Content-Description: File Transfer');
                    if (headers_sent()) {
                        $this->Error('Some data has already been output to browser, can\'t send PDF file');
                    }
                    header('Cache-Control: private, must-revalidate, post-check=0, pre-check=0, max-age=1');
                    header('Pragma: public');
                    header('Expires: Sat, 26 Jul 1997 05:00:00 GMT'); // Date in the past
                    header('Last-Modified: '.gmdate('D, d M Y H:i:s').' GMT');
                    // force download dialog
                    if (strpos(php_sapi_name(), 'cgi') === false) {
                        header('Content-Type: application/force-download');
                        header('Content-Type: application/octet-stream', false);
                        header('Content-Type: application/download', false);
                        header('Content-Type: application/pdf', false);
                    } else {
                        header('Content-Type: application/pdf');
                    }
                    // use the Content-Disposition header to supply a recommended filename
                    header('Content-Disposition: attachment; filename="'.basename($name).'"');
                    header('Content-Transfer-Encoding: binary');
                    TCPDF_STATIC::sendOutputData(file_get_contents($name), filesize($name));
                }
                break;
            }
            case 'E': {
                // return PDF as base64 mime multi-part email attachment (RFC 2045)
                $retval = 'Content-Type: application/pdf;'."\r\n";
                $retval .= ' name="'.$name.'"'."\r\n";
                $retval .= 'Content-Transfer-Encoding: base64'."\r\n";
                $retval .= 'Content-Disposition: attachment;'."\r\n";
                $retval .= ' filename="'.$name.'"'."\r\n\r\n";
                $retval .= chunk_split(base64_encode($this->getBuffer()), 76, "\r\n");
                return $retval;
            }
            case 'S': {
                // returns PDF as a string
                return $this->getBuffer();
            }
            default: {
                $this->Error('Incorrect output destination: '.$dest);
            }
        }
        return '';
    }

    /**
     * Unset all class variables except the following critical variables.
     * @param $destroyall (boolean) if true destroys all class variables, otherwise preserves critical variables.
     * @param $preserve_objcopy (boolean) if true preserves the objcopy variable
     * @public
     * @since 4.5.016 (2009-02-24)
     */
    public function _destroy($destroyall=false, $preserve_objcopy=false) {
        if ($destroyall AND !$preserve_objcopy) {
            // remove all temporary files
            $tmpfiles = glob(K_PATH_CACHE.'__tcpdf_'.$this->file_id.'_*');
            if (!empty($tmpfiles)) {
                array_map('unlink', $tmpfiles);
            }
        }
        $preserve = array(
            'file_id',
            'internal_encoding',
            'state',
            'bufferlen',
            'buffer',
            'cached_files',
            'sign',
            'signature_data',
            'signature_max_length',
            'byterange_string',
            'tsa_timestamp',
            'tsa_data'
        );
        foreach (array_keys(get_object_vars($this)) as $val) {
            if ($destroyall OR !in_array($val, $preserve)) {
                if ((!$preserve_objcopy OR ($val != 'objcopy')) AND ($val != 'file_id') AND isset($this->$val)) {
                    unset($this->$val);
                }
            }
        }
    }

    /**
     * Check for locale-related bug
     * @protected
     */
    protected function _dochecks() {
        //Check for locale-related bug
        if (1.1 == 1) {
            $this->Error('Don\'t alter the locale before including class file');
        }
        //Check for decimal separator
        if (sprintf('%.1F', 1.0) != '1.0') {
            setlocale(LC_NUMERIC, 'C');
        }
    }

    /**
     * Return an array containing variations for the basic page number alias.
     * @param $a (string) Base alias.
     * @return array of page number aliases
     * @protected
     */
    protected function getInternalPageNumberAliases($a= '') {
        $alias = array();
        // build array of Unicode + ASCII variants (the order is important)
        $alias = array('u' => array(), 'a' => array());
        $u = '{'.$a.'}';
        $alias['u'][] = TCPDF_STATIC::_escape($u);
        if ($this->isunicode) {
            $alias['u'][] = TCPDF_STATIC::_escape(TCPDF_FONTS::UTF8ToLatin1($u, $this->isunicode, $this->CurrentFont));
            $alias['u'][] = TCPDF_STATIC::_escape(TCPDF_FONTS::utf8StrRev($u, false, $this->tmprtl, $this->isunicode, $this->CurrentFont));
            $alias['a'][] = TCPDF_STATIC::_escape(TCPDF_FONTS::UTF8ToLatin1($a, $this->isunicode, $this->CurrentFont));
            $alias['a'][] = TCPDF_STATIC::_escape(TCPDF_FONTS::utf8StrRev($a, false, $this->tmprtl, $this->isunicode, $this->CurrentFont));
        }
        $alias['a'][] = TCPDF_STATIC::_escape($a);
        return $alias;
    }

    /**
     * Return an array containing all internal page aliases.
     * @return array of page number aliases
     * @protected
     */
    protected function getAllInternalPageNumberAliases() {
        $basic_alias = array(TCPDF_STATIC::$alias_tot_pages, TCPDF_STATIC::$alias_num_page, TCPDF_STATIC::$alias_group_tot_pages, TCPDF_STATIC::$alias_group_num_page, TCPDF_STATIC::$alias_right_shift);
        $pnalias = array();
        foreach($basic_alias as $k => $a) {
            $pnalias[$k] = $this->getInternalPageNumberAliases($a);
        }
        return $pnalias;
    }

    /**
     * Replace right shift page number aliases with spaces to correct right alignment.
     * This works perfectly only when using monospaced fonts.
     * @param $page (string) Page content.
     * @param $aliases (array) Array of page aliases.
     * @param $diff (int) initial difference to add.
     * @return replaced page content.
     * @protected
     */
    protected function replaceRightShiftPageNumAliases($page, $aliases, $diff) {
        foreach ($aliases as $type => $alias) {
            foreach ($alias as $a) {
                // find position of compensation factor
                $startnum = (strpos($a, ':') + 1);
                $a = substr($a, 0, $startnum);
                if (($pos = strpos($page, $a)) !== false) {
                    // end of alias
                    $endnum = strpos($page, '}', $pos);
                    // string to be replaced
                    $aa = substr($page, $pos, ($endnum - $pos + 1));
                    // get compensation factor
                    $ratio = substr($page, ($pos + $startnum), ($endnum - $pos - $startnum));
                    $ratio = preg_replace('/[^0-9\.]/', '', $ratio);
                    $ratio = floatval($ratio);
                    if ($type == 'u') {
                        $chrdiff = floor(($diff + 12) * $ratio);
                        $shift = str_repeat(' ', $chrdiff);
                        $shift = TCPDF_FONTS::UTF8ToUTF16BE($shift, false, $this->isunicode, $this->CurrentFont);
                    } else {
                        $chrdiff = floor(($diff + 11) * $ratio);
                        $shift = str_repeat(' ', $chrdiff);
                    }
                    $page = str_replace($aa, $shift, $page);
                }
            }
        }
        return $page;
    }

    /**
     * Set page boxes to be included on page descriptions.
     * @param $boxes (array) Array of page boxes to set on document: ('MediaBox', 'CropBox', 'BleedBox', 'TrimBox', 'ArtBox').
     * @protected
     */
    protected function setPageBoxTypes($boxes) {
        $this->page_boxes = array();
        foreach ($boxes as $box) {
            if (in_array($box, TCPDF_STATIC::$pageboxes)) {
                $this->page_boxes[] = $box;
            }
        }
    }

    /**
     * Output pages (and replace page number aliases).
     * @protected
     */
    protected function _putpages() {
        $filter = ($this->compress) ? '/Filter /FlateDecode ' : '';
        // get internal aliases for page numbers
        $pnalias = $this->getAllInternalPageNumberAliases();
        $num_pages = $this->numpages;
        $ptpa = TCPDF_STATIC::formatPageNumber(($this->starting_page_number + $num_pages - 1));
        $ptpu = TCPDF_FONTS::UTF8ToUTF16BE($ptpa, false, $this->isunicode, $this->CurrentFont);
        $ptp_num_chars = $this->GetNumChars($ptpa);
        $pagegroupnum = 0;
        $groupnum = 0;
        $ptgu = 1;
        $ptga = 1;
        $ptg_num_chars = 1;
        for ($n = 1; $n <= $num_pages; ++$n) {
            // get current page
            $temppage = $this->getPageBuffer($n);
            $pagelen = strlen($temppage);
            // set replacements for total pages number
            $pnpa = TCPDF_STATIC::formatPageNumber(($this->starting_page_number + $n - 1));
            $pnpu = TCPDF_FONTS::UTF8ToUTF16BE($pnpa, false, $this->isunicode, $this->CurrentFont);
            $pnp_num_chars = $this->GetNumChars($pnpa);
            $pdiff = 0; // difference used for right shift alignment of page numbers
            $gdiff = 0; // difference used for right shift alignment of page group numbers
            if (!empty($this->pagegroups)) {
                if (isset($this->newpagegroup[$n])) {
                    $pagegroupnum = 0;
                    ++$groupnum;
                    $ptga = TCPDF_STATIC::formatPageNumber($this->pagegroups[$groupnum]);
                    $ptgu = TCPDF_FONTS::UTF8ToUTF16BE($ptga, false, $this->isunicode, $this->CurrentFont);
                    $ptg_num_chars = $this->GetNumChars($ptga);
                }
                ++$pagegroupnum;
                $pnga = TCPDF_STATIC::formatPageNumber($pagegroupnum);
                $pngu = TCPDF_FONTS::UTF8ToUTF16BE($pnga, false, $this->isunicode, $this->CurrentFont);
                $png_num_chars = $this->GetNumChars($pnga);
                // replace page numbers
                $replace = array();
                $replace[] = array($ptgu, $ptg_num_chars, 9, $pnalias[2]['u']);
                $replace[] = array($ptga, $ptg_num_chars, 7, $pnalias[2]['a']);
                $replace[] = array($pngu, $png_num_chars, 9, $pnalias[3]['u']);
                $replace[] = array($pnga, $png_num_chars, 7, $pnalias[3]['a']);
                list($temppage, $gdiff) = TCPDF_STATIC::replacePageNumAliases($temppage, $replace, $gdiff);
            }
            // replace page numbers
            $replace = array();
            $replace[] = array($ptpu, $ptp_num_chars, 9, $pnalias[0]['u']);
            $replace[] = array($ptpa, $ptp_num_chars, 7, $pnalias[0]['a']);
            $replace[] = array($pnpu, $pnp_num_chars, 9, $pnalias[1]['u']);
            $replace[] = array($pnpa, $pnp_num_chars, 7, $pnalias[1]['a']);
            list($temppage, $pdiff) = TCPDF_STATIC::replacePageNumAliases($temppage, $replace, $pdiff);
            // replace right shift alias
            $temppage = $this->replaceRightShiftPageNumAliases($temppage, $pnalias[4], max($pdiff, $gdiff));
            // replace EPS marker
            $temppage = str_replace($this->epsmarker, '', $temppage);
            //Page
            $this->page_obj_id[$n] = $this->_newobj();
            $out = '<<';
            $out .= ' /Type /Page';
            $out .= ' /Parent 1 0 R';
            if (empty($this->signature_data['approval']) OR ($this->signature_data['approval'] != 'A')) {
                $out .= ' /LastModified '.$this->_datestring(0, $this->doc_modification_timestamp);
            }
            $out .= ' /Resources 2 0 R';
            foreach ($this->page_boxes as $box) {
                $out .= ' /'.$box;
                $out .= sprintf(' [%F %F %F %F]', $this->pagedim[$n][$box]['llx'], $this->pagedim[$n][$box]['lly'], $this->pagedim[$n][$box]['urx'], $this->pagedim[$n][$box]['ury']);
            }
            if (isset($this->pagedim[$n]['BoxColorInfo']) AND !empty($this->pagedim[$n]['BoxColorInfo'])) {
                $out .= ' /BoxColorInfo <<';
                foreach ($this->page_boxes as $box) {
                    if (isset($this->pagedim[$n]['BoxColorInfo'][$box])) {
                        $out .= ' /'.$box.' <<';
                        if (isset($this->pagedim[$n]['BoxColorInfo'][$box]['C'])) {
                            $color = $this->pagedim[$n]['BoxColorInfo'][$box]['C'];
                            $out .= ' /C [';
                            $out .= sprintf(' %F %F %F', ($color[0] / 255), ($color[1] / 255), ($color[2] / 255));
                            $out .= ' ]';
                        }
                        if (isset($this->pagedim[$n]['BoxColorInfo'][$box]['W'])) {
                            $out .= ' /W '.($this->pagedim[$n]['BoxColorInfo'][$box]['W'] * $this->k);
                        }
                        if (isset($this->pagedim[$n]['BoxColorInfo'][$box]['S'])) {
                            $out .= ' /S /'.$this->pagedim[$n]['BoxColorInfo'][$box]['S'];
                        }
                        if (isset($this->pagedim[$n]['BoxColorInfo'][$box]['D'])) {
                            $dashes = $this->pagedim[$n]['BoxColorInfo'][$box]['D'];
                            $out .= ' /D [';
                            foreach ($dashes as $dash) {
                                $out .= sprintf(' %F', ($dash * $this->k));
                            }
                            $out .= ' ]';
                        }
                        $out .= ' >>';
                    }
                }
                $out .= ' >>';
            }
            $out .= ' /Contents '.($this->n + 1).' 0 R';
            $out .= ' /Rotate '.$this->pagedim[$n]['Rotate'];
            if (!$this->pdfa_mode) {
                $out .= ' /Group << /Type /Group /S /Transparency /CS /DeviceRGB >>';
            }
            if (isset($this->pagedim[$n]['trans']) AND !empty($this->pagedim[$n]['trans'])) {
                // page transitions
                if (isset($this->pagedim[$n]['trans']['Dur'])) {
                    $out .= ' /Dur '.$this->pagedim[$n]['trans']['Dur'];
                }
                $out .= ' /Trans <<';
                $out .= ' /Type /Trans';
                if (isset($this->pagedim[$n]['trans']['S'])) {
                    $out .= ' /S /'.$this->pagedim[$n]['trans']['S'];
                }
                if (isset($this->pagedim[$n]['trans']['D'])) {
                    $out .= ' /D '.$this->pagedim[$n]['trans']['D'];
                }
                if (isset($this->pagedim[$n]['trans']['Dm'])) {
                    $out .= ' /Dm /'.$this->pagedim[$n]['trans']['Dm'];
                }
                if (isset($this->pagedim[$n]['trans']['M'])) {
                    $out .= ' /M /'.$this->pagedim[$n]['trans']['M'];
                }
                if (isset($this->pagedim[$n]['trans']['Di'])) {
                    $out .= ' /Di '.$this->pagedim[$n]['trans']['Di'];
                }
                if (isset($this->pagedim[$n]['trans']['SS'])) {
                    $out .= ' /SS '.$this->pagedim[$n]['trans']['SS'];
                }
                if (isset($this->pagedim[$n]['trans']['B'])) {
                    $out .= ' /B '.$this->pagedim[$n]['trans']['B'];
                }
                $out .= ' >>';
            }
            $out .= $this->_getannotsrefs($n);
            $out .= ' /PZ '.$this->pagedim[$n]['PZ'];
            $out .= ' >>';
            $out .= "\n".'endobj';
            $this->_out($out);
            //Page content
            $p = ($this->compress) ? gzcompress($temppage) : $temppage;
            $this->_newobj();
            $p = $this->_getrawstream($p);
            $this->_out('<<'.$filter.'/Length '.strlen($p).'>> stream'."\n".$p."\n".'endstream'."\n".'endobj');
        }
        //Pages root
        $out = $this->_getobj(1)."\n";
        $out .= '<< /Type /Pages /Kids [';
        foreach($this->page_obj_id as $page_obj) {
            $out .= ' '.$page_obj.' 0 R';
        }
        $out .= ' ] /Count '.$num_pages.' >>';
        $out .= "\n".'endobj';
        $this->_out($out);
    }

    /**
     * Get references to page annotations.
     * @param $n (int) page number
     * @return string
     * @protected
     * @author Nicola Asuni
     * @since 5.0.010 (2010-05-17)
     */
    protected function _getannotsrefs($n) {
        if (!(isset($this->PageAnnots[$n]) OR ($this->sign AND isset($this->signature_data['cert_type'])))) {
            return '';
        }
        $out = ' /Annots [';
        if (isset($this->PageAnnots[$n])) {
            foreach ($this->PageAnnots[$n] as $key => $val) {
                if (!in_array($val['n'], $this->radio_groups)) {
                    $out .= ' '.$val['n'].' 0 R';
                }
            }
            // add radiobutton groups
            if (isset($this->radiobutton_groups[$n])) {
                foreach ($this->radiobutton_groups[$n] as $key => $data) {
                    if (isset($data['n'])) {
                        $out .= ' '.$data['n'].' 0 R';
                    }
                }
            }
        }
        if ($this->sign AND ($n == $this->signature_appearance['page']) AND isset($this->signature_data['cert_type'])) {
            // set reference for signature object
            $out .= ' '.$this->sig_obj_id.' 0 R';
        }
        if (!empty($this->empty_signature_appearance)) {
            foreach ($this->empty_signature_appearance as $esa) {
                if ($esa['page'] == $n) {
                    // set reference for empty signature objects
                    $out .= ' '.$esa['objid'].' 0 R';
                }
            }
        }
        $out .= ' ]';
        return $out;
    }

    /**
     * Output annotations objects for all pages.
     * !!! THIS METHOD IS NOT YET COMPLETED !!!
     * See section 12.5 of PDF 32000_2008 reference.
     * @protected
     * @author Nicola Asuni
     * @since 4.0.018 (2008-08-06)
     */
    protected function _putannotsobjs() {
        // reset object counter
        for ($n=1; $n <= $this->numpages; ++$n) {
            if (isset($this->PageAnnots[$n])) {
                // set page annotations
                foreach ($this->PageAnnots[$n] as $key => $pl) {
                    $annot_obj_id = $this->PageAnnots[$n][$key]['n'];
                    // create annotation object for grouping radiobuttons
                    if (isset($this->radiobutton_groups[$n][$pl['txt']]) AND is_array($this->radiobutton_groups[$n][$pl['txt']])) {
                        $radio_button_obj_id = $this->radiobutton_groups[$n][$pl['txt']]['n'];
                        $annots = '<<';
                        $annots .= ' /Type /Annot';
                        $annots .= ' /Subtype /Widget';
                        $annots .= ' /Rect [0 0 0 0]';
                        if ($this->radiobutton_groups[$n][$pl['txt']]['#readonly#']) {
                            // read only
                            $annots .= ' /F 68';
                            $annots .= ' /Ff 49153';
                        } else {
                            $annots .= ' /F 4'; // default print for PDF/A
                            $annots .= ' /Ff 49152';
                        }
                        $annots .= ' /T '.$this->_datastring($pl['txt'], $radio_button_obj_id);
                        if (isset($pl['opt']['tu']) AND is_string($pl['opt']['tu'])) {
                            $annots .= ' /TU '.$this->_datastring($pl['opt']['tu'], $radio_button_obj_id);
                        }
                        $annots .= ' /FT /Btn';
                        $annots .= ' /Kids [';
                        $defval = '';
                        foreach ($this->radiobutton_groups[$n][$pl['txt']] as $key => $data) {
                            if (isset($data['kid'])) {
                                $annots .= ' '.$data['kid'].' 0 R';
                                if ($data['def'] !== 'Off') {
                                    $defval = $data['def'];
                                }
                            }
                        }
                        $annots .= ' ]';
                        if (!empty($defval)) {
                            $annots .= ' /V /'.$defval;
                        }
                        $annots .= ' >>';
                        $this->_out($this->_getobj($radio_button_obj_id)."\n".$annots."\n".'endobj');
                        $this->form_obj_id[] = $radio_button_obj_id;
                        // store object id to be used on Parent entry of Kids
                        $this->radiobutton_groups[$n][$pl['txt']] = $radio_button_obj_id;
                    }
                    $formfield = false;
                    $pl['opt'] = array_change_key_case($pl['opt'], CASE_LOWER);
                    $a = $pl['x'] * $this->k;
                    $b = $this->pagedim[$n]['h'] - (($pl['y'] + $pl['h']) * $this->k);
                    $c = $pl['w'] * $this->k;
                    $d = $pl['h'] * $this->k;
                    $rect = sprintf('%F %F %F %F', $a, $b, $a+$c, $b+$d);
                    // create new annotation object
                    $annots = '<</Type /Annot';
                    $annots .= ' /Subtype /'.$pl['opt']['subtype'];
                    $annots .= ' /Rect ['.$rect.']';
                    $ft = array('Btn', 'Tx', 'Ch', 'Sig');
                    if (isset($pl['opt']['ft']) AND in_array($pl['opt']['ft'], $ft)) {
                        $annots .= ' /FT /'.$pl['opt']['ft'];
                        $formfield = true;
                    }
                    $annots .= ' /Contents '.$this->_textstring($pl['txt'], $annot_obj_id);
                    $annots .= ' /P '.$this->page_obj_id[$n].' 0 R';
                    $annots .= ' /NM '.$this->_datastring(sprintf('%04u-%04u', $n, $key), $annot_obj_id);
                    $annots .= ' /M '.$this->_datestring($annot_obj_id, $this->doc_modification_timestamp);
                    if (isset($pl['opt']['f'])) {
                        $fval = 0;
                        if (is_array($pl['opt']['f'])) {
                            foreach ($pl['opt']['f'] as $f) {
                                switch (strtolower($f)) {
                                    case 'invisible': {
                                        $fval += 1 << 0;
                                        break;
                                    }
                                    case 'hidden': {
                                        $fval += 1 << 1;
                                        break;
                                    }
                                    case 'print': {
                                        $fval += 1 << 2;
                                        break;
                                    }
                                    case 'nozoom': {
                                        $fval += 1 << 3;
                                        break;
                                    }
                                    case 'norotate': {
                                        $fval += 1 << 4;
                                        break;
                                    }
                                    case 'noview': {
                                        $fval += 1 << 5;
                                        break;
                                    }
                                    case 'readonly': {
                                        $fval += 1 << 6;
                                        break;
                                    }
                                    case 'locked': {
                                        $fval += 1 << 8;
                                        break;
                                    }
                                    case 'togglenoview': {
                                        $fval += 1 << 9;
                                        break;
                                    }
                                    case 'lockedcontents': {
                                        $fval += 1 << 10;
                                        break;
                                    }
                                    default: {
                                        break;
                                    }
                                }
                            }
                        } else {
                            $fval = intval($pl['opt']['f']);
                        }
                    } else {
                        $fval = 4;
                    }
                    if ($this->pdfa_mode) {
                        // force print flag for PDF/A mode
                        $fval |= 4;
                    }
                    $annots .= ' /F '.intval($fval);
                    if (isset($pl['opt']['as']) AND is_string($pl['opt']['as'])) {
                        $annots .= ' /AS /'.$pl['opt']['as'];
                    }
                    if (isset($pl['opt']['ap'])) {
                        // appearance stream
                        $annots .= ' /AP <<';
                        if (is_array($pl['opt']['ap'])) {
                            foreach ($pl['opt']['ap'] as $apmode => $apdef) {
                                // $apmode can be: n = normal; r = rollover; d = down;
                                $annots .= ' /'.strtoupper($apmode);
                                if (is_array($apdef)) {
                                    $annots .= ' <<';
                                    foreach ($apdef as $apstate => $stream) {
                                        // reference to XObject that define the appearance for this mode-state
                                        $apsobjid = $this->_putAPXObject($c, $d, $stream);
                                        $annots .= ' /'.$apstate.' '.$apsobjid.' 0 R';
                                    }
                                    $annots .= ' >>';
                                } else {
                                    // reference to XObject that define the appearance for this mode
                                    $apsobjid = $this->_putAPXObject($c, $d, $apdef);
                                    $annots .= ' '.$apsobjid.' 0 R';
                                }
                            }
                        } else {
                            $annots .= $pl['opt']['ap'];
                        }
                        $annots .= ' >>';
                    }
                    if (isset($pl['opt']['bs']) AND (is_array($pl['opt']['bs']))) {
                        $annots .= ' /BS <<';
                        $annots .= ' /Type /Border';
                        if (isset($pl['opt']['bs']['w'])) {
                            $annots .= ' /W '.intval($pl['opt']['bs']['w']);
                        }
                        $bstyles = array('S', 'D', 'B', 'I', 'U');
                        if (isset($pl['opt']['bs']['s']) AND in_array($pl['opt']['bs']['s'], $bstyles)) {
                            $annots .= ' /S /'.$pl['opt']['bs']['s'];
                        }
                        if (isset($pl['opt']['bs']['d']) AND (is_array($pl['opt']['bs']['d']))) {
                            $annots .= ' /D [';
                            foreach ($pl['opt']['bs']['d'] as $cord) {
                                $annots .= ' '.intval($cord);
                            }
                            $annots .= ']';
                        }
                        $annots .= ' >>';
                    } else {
                        $annots .= ' /Border [';
                        if (isset($pl['opt']['border']) AND (count($pl['opt']['border']) >= 3)) {
                            $annots .= intval($pl['opt']['border'][0]).' ';
                            $annots .= intval($pl['opt']['border'][1]).' ';
                            $annots .= intval($pl['opt']['border'][2]);
                            if (isset($pl['opt']['border'][3]) AND is_array($pl['opt']['border'][3])) {
                                $annots .= ' [';
                                foreach ($pl['opt']['border'][3] as $dash) {
                                    $annots .= intval($dash).' ';
                                }
                                $annots .= ']';
                            }
                        } else {
                            $annots .= '0 0 0';
                        }
                        $annots .= ']';
                    }
                    if (isset($pl['opt']['be']) AND (is_array($pl['opt']['be']))) {
                        $annots .= ' /BE <<';
                        $bstyles = array('S', 'C');
                        if (isset($pl['opt']['be']['s']) AND in_array($pl['opt']['be']['s'], $bstyles)) {
                            $annots .= ' /S /'.$pl['opt']['bs']['s'];
                        } else {
                            $annots .= ' /S /S';
                        }
                        if (isset($pl['opt']['be']['i']) AND ($pl['opt']['be']['i'] >= 0) AND ($pl['opt']['be']['i'] <= 2)) {
                            $annots .= ' /I '.sprintf(' %F', $pl['opt']['be']['i']);
                        }
                        $annots .= '>>';
                    }
                    if (isset($pl['opt']['c']) AND (is_array($pl['opt']['c'])) AND !empty($pl['opt']['c'])) {
                        $annots .= ' /C '.TCPDF_COLORS::getColorStringFromArray($pl['opt']['c']);
                    }
                    //$annots .= ' /StructParent ';
                    //$annots .= ' /OC ';
                    $markups = array('text', 'freetext', 'line', 'square', 'circle', 'polygon', 'polyline', 'highlight', 'underline', 'squiggly', 'strikeout', 'stamp', 'caret', 'ink', 'fileattachment', 'sound');
                    if (in_array(strtolower($pl['opt']['subtype']), $markups)) {
                        // this is a markup type
                        if (isset($pl['opt']['t']) AND is_string($pl['opt']['t'])) {
                            $annots .= ' /T '.$this->_textstring($pl['opt']['t'], $annot_obj_id);
                        }
                        //$annots .= ' /Popup ';
                        if (isset($pl['opt']['ca'])) {
                            $annots .= ' /CA '.sprintf('%F', floatval($pl['opt']['ca']));
                        }
                        if (isset($pl['opt']['rc'])) {
                            $annots .= ' /RC '.$this->_textstring($pl['opt']['rc'], $annot_obj_id);
                        }
                        $annots .= ' /CreationDate '.$this->_datestring($annot_obj_id, $this->doc_creation_timestamp);
                        //$annots .= ' /IRT ';
                        if (isset($pl['opt']['subj'])) {
                            $annots .= ' /Subj '.$this->_textstring($pl['opt']['subj'], $annot_obj_id);
                        }
                        //$annots .= ' /RT ';
                        //$annots .= ' /IT ';
                        //$annots .= ' /ExData ';
                    }
                    $lineendings = array('Square', 'Circle', 'Diamond', 'OpenArrow', 'ClosedArrow', 'None', 'Butt', 'ROpenArrow', 'RClosedArrow', 'Slash');
                    // Annotation types
                    switch (strtolower($pl['opt']['subtype'])) {
                        case 'text': {
                            if (isset($pl['opt']['open'])) {
                                $annots .= ' /Open '. (strtolower($pl['opt']['open']) == 'true' ? 'true' : 'false');
                            }
                            $iconsapp = array('Comment', 'Help', 'Insert', 'Key', 'NewParagraph', 'Note', 'Paragraph');
                            if (isset($pl['opt']['name']) AND in_array($pl['opt']['name'], $iconsapp)) {
                                $annots .= ' /Name /'.$pl['opt']['name'];
                            } else {
                                $annots .= ' /Name /Note';
                            }
                            $statemodels = array('Marked', 'Review');
                            if (isset($pl['opt']['statemodel']) AND in_array($pl['opt']['statemodel'], $statemodels)) {
                                $annots .= ' /StateModel /'.$pl['opt']['statemodel'];
                            } else {
                                $pl['opt']['statemodel'] = 'Marked';
                                $annots .= ' /StateModel /'.$pl['opt']['statemodel'];
                            }
                            if ($pl['opt']['statemodel'] == 'Marked') {
                                $states = array('Accepted', 'Unmarked');
                            } else {
                                $states = array('Accepted', 'Rejected', 'Cancelled', 'Completed', 'None');
                            }
                            if (isset($pl['opt']['state']) AND in_array($pl['opt']['state'], $states)) {
                                $annots .= ' /State /'.$pl['opt']['state'];
                            } else {
                                if ($pl['opt']['statemodel'] == 'Marked') {
                                    $annots .= ' /State /Unmarked';
                                } else {
                                    $annots .= ' /State /None';
                                }
                            }
                            break;
                        }
                        case 'link': {
                            if (is_string($pl['txt'])) {
                                if ($pl['txt'][0] == '#') {
                                    // internal destination
                                    $annots .= ' /Dest /'.TCPDF_STATIC::encodeNameObject(substr($pl['txt'], 1));
                                } elseif ($pl['txt'][0] == '%') {
                                    // embedded PDF file
                                    $filename = basename(substr($pl['txt'], 1));
                                    $annots .= ' /A << /S /GoToE /D [0 /Fit] /NewWindow true /T << /R /C /P '.($n - 1).' /A '.$this->embeddedfiles[$filename]['a'].' >> >>';
                                } elseif ($pl['txt'][0] == '*') {
                                    // embedded generic file
                                    $filename = basename(substr($pl['txt'], 1));
                                    $jsa = 'var D=event.target.doc;var MyData=D.dataObjects;for (var i in MyData) if (MyData[i].path=="'.$filename.'") D.exportDataObject( { cName : MyData[i].name, nLaunch : 2});';
                                    $annots .= ' /A << /S /JavaScript /JS '.$this->_textstring($jsa, $annot_obj_id).'>>';
                                } else {
                                    $parsedUrl = parse_url($pl['txt']);
                                    if (empty($parsedUrl['scheme']) AND (strtolower(substr($parsedUrl['path'], -4)) == '.pdf')) {
                                        // relative link to a PDF file
                                        $dest = '[0 /Fit]'; // default page 0
                                        if (!empty($parsedUrl['fragment'])) {
                                            // check for named destination
                                            $tmp = explode('=', $parsedUrl['fragment']);
                                            $dest = '('.((count($tmp) == 2) ? $tmp[1] : $tmp[0]).')';
                                        }
                                        $annots .= ' /A <</S /GoToR /D '.$dest.' /F '.$this->_datastring($this->unhtmlentities($parsedUrl['path']), $annot_obj_id).' /NewWindow true>>';
                                    } else {
                                        // external URI link
                                        $annots .= ' /A <</S /URI /URI '.$this->_datastring($this->unhtmlentities($pl['txt']), $annot_obj_id).'>>';
                                    }
                                }
                            } elseif (isset($this->links[$pl['txt']])) {
                                // internal link ID
                                $l = $this->links[$pl['txt']];
                                if (isset($this->page_obj_id[($l['p'])])) {
                                    $annots .= sprintf(' /Dest [%u 0 R /XYZ 0 %F null]', $this->page_obj_id[($l['p'])], ($this->pagedim[$l['p']]['h'] - ($l['y'] * $this->k)));
                                }
                            }
                            $hmodes = array('N', 'I', 'O', 'P');
                            if (isset($pl['opt']['h']) AND in_array($pl['opt']['h'], $hmodes)) {
                                $annots .= ' /H /'.$pl['opt']['h'];
                            } else {
                                $annots .= ' /H /I';
                            }
                            //$annots .= ' /PA ';
                            //$annots .= ' /Quadpoints ';
                            break;
                        }
                        case 'freetext': {
                            if (isset($pl['opt']['da']) AND !empty($pl['opt']['da'])) {
                                $annots .= ' /DA ('.$pl['opt']['da'].')';
                            }
                            if (isset($pl['opt']['q']) AND ($pl['opt']['q'] >= 0) AND ($pl['opt']['q'] <= 2)) {
                                $annots .= ' /Q '.intval($pl['opt']['q']);
                            }
                            if (isset($pl['opt']['rc'])) {
                                $annots .= ' /RC '.$this->_textstring($pl['opt']['rc'], $annot_obj_id);
                            }
                            if (isset($pl['opt']['ds'])) {
                                $annots .= ' /DS '.$this->_textstring($pl['opt']['ds'], $annot_obj_id);
                            }
                            if (isset($pl['opt']['cl']) AND is_array($pl['opt']['cl'])) {
                                $annots .= ' /CL [';
                                foreach ($pl['opt']['cl'] as $cl) {
                                    $annots .= sprintf('%F ', $cl * $this->k);
                                }
                                $annots .= ']';
                            }
                            $tfit = array('FreeText', 'FreeTextCallout', 'FreeTextTypeWriter');
                            if (isset($pl['opt']['it']) AND in_array($pl['opt']['it'], $tfit)) {
                                $annots .= ' /IT /'.$pl['opt']['it'];
                            }
                            if (isset($pl['opt']['rd']) AND is_array($pl['opt']['rd'])) {
                                $l = $pl['opt']['rd'][0] * $this->k;
                                $r = $pl['opt']['rd'][1] * $this->k;
                                $t = $pl['opt']['rd'][2] * $this->k;
                                $b = $pl['opt']['rd'][3] * $this->k;
                                $annots .= ' /RD ['.sprintf('%F %F %F %F', $l, $r, $t, $b).']';
                            }
                            if (isset($pl['opt']['le']) AND in_array($pl['opt']['le'], $lineendings)) {
                                $annots .= ' /LE /'.$pl['opt']['le'];
                            }
                            break;
                        }
                        case 'line': {
                            break;
                        }
                        case 'square': {
                            break;
                        }
                        case 'circle': {
                            break;
                        }
                        case 'polygon': {
                            break;
                        }
                        case 'polyline': {
                            break;
                        }
                        case 'highlight': {
                            break;
                        }
                        case 'underline': {
                            break;
                        }
                        case 'squiggly': {
                            break;
                        }
                        case 'strikeout': {
                            break;
                        }
                        case 'stamp': {
                            break;
                        }
                        case 'caret': {
                            break;
                        }
                        case 'ink': {
                            break;
                        }
                        case 'popup': {
                            break;
                        }
                        case 'fileattachment': {
                            if ($this->pdfa_mode) {
                                // embedded files are not allowed in PDF/A mode
                                break;
                            }
                            if (!isset($pl['opt']['fs'])) {
                                break;
                            }
                            $filename = basename($pl['opt']['fs']);
                            if (isset($this->embeddedfiles[$filename]['f'])) {
                                $annots .= ' /FS '.$this->embeddedfiles[$filename]['f'].' 0 R';
                                $iconsapp = array('Graph', 'Paperclip', 'PushPin', 'Tag');
                                if (isset($pl['opt']['name']) AND in_array($pl['opt']['name'], $iconsapp)) {
                                    $annots .= ' /Name /'.$pl['opt']['name'];
                                } else {
                                    $annots .= ' /Name /PushPin';
                                }
                                // index (zero-based) of the annotation in the Annots array of this page
                                $this->embeddedfiles[$filename]['a'] = $key;
                            }
                            break;
                        }
                        case 'sound': {
                            if (!isset($pl['opt']['fs'])) {
                                break;
                            }
                            $filename = basename($pl['opt']['fs']);
                            if (isset($this->embeddedfiles[$filename]['f'])) {
                                // ... TO BE COMPLETED ...
                                // /R /C /B /E /CO /CP
                                $annots .= ' /Sound '.$this->embeddedfiles[$filename]['f'].' 0 R';
                                $iconsapp = array('Speaker', 'Mic');
                                if (isset($pl['opt']['name']) AND in_array($pl['opt']['name'], $iconsapp)) {
                                    $annots .= ' /Name /'.$pl['opt']['name'];
                                } else {
                                    $annots .= ' /Name /Speaker';
                                }
                            }
                            break;
                        }
                        case 'movie': {
                            break;
                        }
                        case 'widget': {
                            $hmode = array('N', 'I', 'O', 'P', 'T');
                            if (isset($pl['opt']['h']) AND in_array($pl['opt']['h'], $hmode)) {
                                $annots .= ' /H /'.$pl['opt']['h'];
                            }
                            if (isset($pl['opt']['mk']) AND (is_array($pl['opt']['mk'])) AND !empty($pl['opt']['mk'])) {
                                $annots .= ' /MK <<';
                                if (isset($pl['opt']['mk']['r'])) {
                                    $annots .= ' /R '.$pl['opt']['mk']['r'];
                                }
                                if (isset($pl['opt']['mk']['bc']) AND (is_array($pl['opt']['mk']['bc']))) {
                                    $annots .= ' /BC '.TCPDF_COLORS::getColorStringFromArray($pl['opt']['mk']['bc']);
                                }
                                if (isset($pl['opt']['mk']['bg']) AND (is_array($pl['opt']['mk']['bg']))) {
                                    $annots .= ' /BG '.TCPDF_COLORS::getColorStringFromArray($pl['opt']['mk']['bg']);
                                }
                                if (isset($pl['opt']['mk']['ca'])) {
                                    $annots .= ' /CA '.$pl['opt']['mk']['ca'];
                                }
                                if (isset($pl['opt']['mk']['rc'])) {
                                    $annots .= ' /RC '.$pl['opt']['mk']['rc'];
                                }
                                if (isset($pl['opt']['mk']['ac'])) {
                                    $annots .= ' /AC '.$pl['opt']['mk']['ac'];
                                }
                                if (isset($pl['opt']['mk']['i'])) {
                                    $info = $this->getImageBuffer($pl['opt']['mk']['i']);
                                    if ($info !== false) {
                                        $annots .= ' /I '.$info['n'].' 0 R';
                                    }
                                }
                                if (isset($pl['opt']['mk']['ri'])) {
                                    $info = $this->getImageBuffer($pl['opt']['mk']['ri']);
                                    if ($info !== false) {
                                        $annots .= ' /RI '.$info['n'].' 0 R';
                                    }
                                }
                                if (isset($pl['opt']['mk']['ix'])) {
                                    $info = $this->getImageBuffer($pl['opt']['mk']['ix']);
                                    if ($info !== false) {
                                        $annots .= ' /IX '.$info['n'].' 0 R';
                                    }
                                }
                                if (isset($pl['opt']['mk']['if']) AND (is_array($pl['opt']['mk']['if'])) AND !empty($pl['opt']['mk']['if'])) {
                                    $annots .= ' /IF <<';
                                    $if_sw = array('A', 'B', 'S', 'N');
                                    if (isset($pl['opt']['mk']['if']['sw']) AND in_array($pl['opt']['mk']['if']['sw'], $if_sw)) {
                                        $annots .= ' /SW /'.$pl['opt']['mk']['if']['sw'];
                                    }
                                    $if_s = array('A', 'P');
                                    if (isset($pl['opt']['mk']['if']['s']) AND in_array($pl['opt']['mk']['if']['s'], $if_s)) {
                                        $annots .= ' /S /'.$pl['opt']['mk']['if']['s'];
                                    }
                                    if (isset($pl['opt']['mk']['if']['a']) AND (is_array($pl['opt']['mk']['if']['a'])) AND !empty($pl['opt']['mk']['if']['a'])) {
                                        $annots .= sprintf(' /A [%F %F]', $pl['opt']['mk']['if']['a'][0], $pl['opt']['mk']['if']['a'][1]);
                                    }
                                    if (isset($pl['opt']['mk']['if']['fb']) AND ($pl['opt']['mk']['if']['fb'])) {
                                        $annots .= ' /FB true';
                                    }
                                    $annots .= '>>';
                                }
                                if (isset($pl['opt']['mk']['tp']) AND ($pl['opt']['mk']['tp'] >= 0) AND ($pl['opt']['mk']['tp'] <= 6)) {
                                    $annots .= ' /TP '.intval($pl['opt']['mk']['tp']);
                                }
                                $annots .= '>>';
                            } // end MK
                            // --- Entries for field dictionaries ---
                            if (isset($this->radiobutton_groups[$n][$pl['txt']])) {
                                // set parent
                                $annots .= ' /Parent '.$this->radiobutton_groups[$n][$pl['txt']].' 0 R';
                            }
                            if (isset($pl['opt']['t']) AND is_string($pl['opt']['t'])) {
                                $annots .= ' /T '.$this->_datastring($pl['opt']['t'], $annot_obj_id);
                            }
                            if (isset($pl['opt']['tu']) AND is_string($pl['opt']['tu'])) {
                                $annots .= ' /TU '.$this->_datastring($pl['opt']['tu'], $annot_obj_id);
                            }
                            if (isset($pl['opt']['tm']) AND is_string($pl['opt']['tm'])) {
                                $annots .= ' /TM '.$this->_datastring($pl['opt']['tm'], $annot_obj_id);
                            }
                            if (isset($pl['opt']['ff'])) {
                                if (is_array($pl['opt']['ff'])) {
                                    // array of bit settings
                                    $flag = 0;
                                    foreach($pl['opt']['ff'] as $val) {
                                        $flag += 1 << ($val - 1);
                                    }
                                } else {
                                    $flag = intval($pl['opt']['ff']);
                                }
                                $annots .= ' /Ff '.$flag;
                            }
                            if (isset($pl['opt']['maxlen'])) {
                                $annots .= ' /MaxLen '.intval($pl['opt']['maxlen']);
                            }
                            if (isset($pl['opt']['v'])) {
                                $annots .= ' /V';
                                if (is_array($pl['opt']['v'])) {
                                    foreach ($pl['opt']['v'] AS $optval) {
                                        if (is_float($optval)) {
                                            $optval = sprintf('%F', $optval);
                                        }
                                        $annots .= ' '.$optval;
                                    }
                                } else {
                                    $annots .= ' '.$this->_textstring($pl['opt']['v'], $annot_obj_id);
                                }
                            }
                            if (isset($pl['opt']['dv'])) {
                                $annots .= ' /DV';
                                if (is_array($pl['opt']['dv'])) {
                                    foreach ($pl['opt']['dv'] AS $optval) {
                                        if (is_float($optval)) {
                                            $optval = sprintf('%F', $optval);
                                        }
                                        $annots .= ' '.$optval;
                                    }
                                } else {
                                    $annots .= ' '.$this->_textstring($pl['opt']['dv'], $annot_obj_id);
                                }
                            }
                            if (isset($pl['opt']['rv'])) {
                                $annots .= ' /RV';
                                if (is_array($pl['opt']['rv'])) {
                                    foreach ($pl['opt']['rv'] AS $optval) {
                                        if (is_float($optval)) {
                                            $optval = sprintf('%F', $optval);
                                        }
                                        $annots .= ' '.$optval;
                                    }
                                } else {
                                    $annots .= ' '.$this->_textstring($pl['opt']['rv'], $annot_obj_id);
                                }
                            }
                            if (isset($pl['opt']['a']) AND !empty($pl['opt']['a'])) {
                                $annots .= ' /A << '.$pl['opt']['a'].' >>';
                            }
                            if (isset($pl['opt']['aa']) AND !empty($pl['opt']['aa'])) {
                                $annots .= ' /AA << '.$pl['opt']['aa'].' >>';
                            }
                            if (isset($pl['opt']['da']) AND !empty($pl['opt']['da'])) {
                                $annots .= ' /DA ('.$pl['opt']['da'].')';
                            }
                            if (isset($pl['opt']['q']) AND ($pl['opt']['q'] >= 0) AND ($pl['opt']['q'] <= 2)) {
                                $annots .= ' /Q '.intval($pl['opt']['q']);
                            }
                            if (isset($pl['opt']['opt']) AND (is_array($pl['opt']['opt'])) AND !empty($pl['opt']['opt'])) {
                                $annots .= ' /Opt [';
                                foreach($pl['opt']['opt'] AS $copt) {
                                    if (is_array($copt)) {
                                        $annots .= ' ['.$this->_textstring($copt[0], $annot_obj_id).' '.$this->_textstring($copt[1], $annot_obj_id).']';
                                    } else {
                                        $annots .= ' '.$this->_textstring($copt, $annot_obj_id);
                                    }
                                }
                                $annots .= ']';
                            }
                            if (isset($pl['opt']['ti'])) {
                                $annots .= ' /TI '.intval($pl['opt']['ti']);
                            }
                            if (isset($pl['opt']['i']) AND (is_array($pl['opt']['i'])) AND !empty($pl['opt']['i'])) {
                                $annots .= ' /I [';
                                foreach($pl['opt']['i'] AS $copt) {
                                    $annots .= intval($copt).' ';
                                }
                                $annots .= ']';
                            }
                            break;
                        }
                        case 'screen': {
                            break;
                        }
                        case 'printermark': {
                            break;
                        }
                        case 'trapnet': {
                            break;
                        }
                        case 'watermark': {
                            break;
                        }
                        case '3d': {
                            break;
                        }
                        default: {
                            break;
                        }
                    }
                    $annots .= '>>';
                    // create new annotation object
                    $this->_out($this->_getobj($annot_obj_id)."\n".$annots."\n".'endobj');
                    if ($formfield AND !isset($this->radiobutton_groups[$n][$pl['txt']])) {
                        // store reference of form object
                        $this->form_obj_id[] = $annot_obj_id;
                    }
                }
            }
        } // end for each page
    }

    /**
     * Put appearance streams XObject used to define annotation's appearance states.
     * @param $w (int) annotation width
     * @param $h (int) annotation height
     * @param $stream (string) appearance stream
     * @return int object ID
     * @protected
     * @since 4.8.001 (2009-09-09)
     */
    protected function _putAPXObject($w=0, $h=0, $stream='') {
        $stream = trim($stream);
        $out = $this->_getobj()."\n";
        $this->xobjects['AX'.$this->n] = array('n' => $this->n);
        $out .= '<<';
        $out .= ' /Type /XObject';
        $out .= ' /Subtype /Form';
        $out .= ' /FormType 1';
        if ($this->compress) {
            $stream = gzcompress($stream);
            $out .= ' /Filter /FlateDecode';
        }
        $rect = sprintf('%F %F', $w, $h);
        $out .= ' /BBox [0 0 '.$rect.']';
        $out .= ' /Matrix [1 0 0 1 0 0]';
        $out .= ' /Resources 2 0 R';
        $stream = $this->_getrawstream($stream);
        $out .= ' /Length '.strlen($stream);
        $out .= ' >>';
        $out .= ' stream'."\n".$stream."\n".'endstream';
        $out .= "\n".'endobj';
        $this->_out($out);
        return $this->n;
    }

    /**
     * Output fonts.
     * @author Nicola Asuni
     * @protected
     */
    protected function _putfonts() {
        $nf = $this->n;
        foreach ($this->diffs as $diff) {
            //Encodings
            $this->_newobj();
            $this->_out('<< /Type /Encoding /BaseEncoding /WinAnsiEncoding /Differences ['.$diff.'] >>'."\n".'endobj');
        }
        $mqr = TCPDF_STATIC::get_mqr();
        TCPDF_STATIC::set_mqr(false);
        foreach ($this->FontFiles as $file => $info) {
            // search and get font file to embedd
            $fontfile = TCPDF_FONTS::getFontFullPath($file, $info['fontdir']);
            if (!TCPDF_STATIC::empty_string($fontfile)) {
                $font = file_get_contents($fontfile);
                $compressed = (substr($file, -2) == '.z');
                if ((!$compressed) AND (isset($info['length2']))) {
                    $header = (ord($font[0]) == 128);
                    if ($header) {
                        // strip first binary header
                        $font = substr($font, 6);
                    }
                    if ($header AND (ord($font[$info['length1']]) == 128)) {
                        // strip second binary header
                        $font = substr($font, 0, $info['length1']).substr($font, ($info['length1'] + 6));
                    }
                } elseif ($info['subset'] AND ((!$compressed) OR ($compressed AND function_exists('gzcompress')))) {
                    if ($compressed) {
                        // uncompress font
                        $font = gzuncompress($font);
                    }
                    // merge subset characters
                    $subsetchars = array(); // used chars
                    foreach ($info['fontkeys'] as $fontkey) {
                        $fontinfo = $this->getFontBuffer($fontkey);
                        $subsetchars += $fontinfo['subsetchars'];
                    }
                    // rebuild a font subset
                    $font = TCPDF_FONTS::_getTrueTypeFontSubset($font, $subsetchars);
                    // calculate new font length
                    $info['length1'] = strlen($font);
                    if ($compressed) {
                        // recompress font
                        $font = gzcompress($font);
                    }
                }
                $this->_newobj();
                $this->FontFiles[$file]['n'] = $this->n;
                $stream = $this->_getrawstream($font);
                $out = '<< /Length '.strlen($stream);
                if ($compressed) {
                    $out .= ' /Filter /FlateDecode';
                }
                $out .= ' /Length1 '.$info['length1'];
                if (isset($info['length2'])) {
                    $out .= ' /Length2 '.$info['length2'].' /Length3 0';
                }
                $out .= ' >>';
                $out .= ' stream'."\n".$stream."\n".'endstream';
                $out .= "\n".'endobj';
                $this->_out($out);
            }
        }
        TCPDF_STATIC::set_mqr($mqr);
        foreach ($this->fontkeys as $k) {
            //Font objects
            $font = $this->getFontBuffer($k);
            $type = $font['type'];
            $name = $font['name'];
            if ($type == 'core') {
                // standard core font
                $out = $this->_getobj($this->font_obj_ids[$k])."\n";
                $out .= '<</Type /Font';
                $out .= ' /Subtype /Type1';
                $out .= ' /BaseFont /'.$name;
                $out .= ' /Name /F'.$font['i'];
                if ((strtolower($name) != 'symbol') AND (strtolower($name) != 'zapfdingbats')) {
                    $out .= ' /Encoding /WinAnsiEncoding';
                }
                if ($k == 'helvetica') {
                    // add default font for annotations
                    $this->annotation_fonts[$k] = $font['i'];
                }
                $out .= ' >>';
                $out .= "\n".'endobj';
                $this->_out($out);
            } elseif (($type == 'Type1') OR ($type == 'TrueType')) {
                // additional Type1 or TrueType font
                $out = $this->_getobj($this->font_obj_ids[$k])."\n";
                $out .= '<</Type /Font';
                $out .= ' /Subtype /'.$type;
                $out .= ' /BaseFont /'.$name;
                $out .= ' /Name /F'.$font['i'];
                $out .= ' /FirstChar 32 /LastChar 255';
                $out .= ' /Widths '.($this->n + 1).' 0 R';
                $out .= ' /FontDescriptor '.($this->n + 2).' 0 R';
                if ($font['enc']) {
                    if (isset($font['diff'])) {
                        $out .= ' /Encoding '.($nf + $font['diff']).' 0 R';
                    } else {
                        $out .= ' /Encoding /WinAnsiEncoding';
                    }
                }
                $out .= ' >>';
                $out .= "\n".'endobj';
                $this->_out($out);
                // Widths
                $this->_newobj();
                $s = '[';
                for ($i = 32; $i < 256; ++$i) {
                    if (isset($font['cw'][$i])) {
                        $s .= $font['cw'][$i].' ';
                    } else {
                        $s .= $font['dw'].' ';
                    }
                }
                $s .= ']';
                $s .= "\n".'endobj';
                $this->_out($s);
                //Descriptor
                $this->_newobj();
                $s = '<</Type /FontDescriptor /FontName /'.$name;
                foreach ($font['desc'] as $fdk => $fdv) {
                    if (is_float($fdv)) {
                        $fdv = sprintf('%F', $fdv);
                    }
                    $s .= ' /'.$fdk.' '.$fdv.'';
                }
                if (!TCPDF_STATIC::empty_string($font['file'])) {
                    $s .= ' /FontFile'.($type == 'Type1' ? '' : '2').' '.$this->FontFiles[$font['file']]['n'].' 0 R';
                }
                $s .= '>>';
                $s .= "\n".'endobj';
                $this->_out($s);
            } else {
                // additional types
                $mtd = '_put'.strtolower($type);
                if (!method_exists($this, $mtd)) {
                    $this->Error('Unsupported font type: '.$type);
                }
                $this->$mtd($font);
            }
        }
    }

    /**
     * Adds unicode fonts.<br>
     * Based on PDF Reference 1.3 (section 5)
     * @param $font (array) font data
     * @protected
     * @author Nicola Asuni
     * @since 1.52.0.TC005 (2005-01-05)
     */
    protected function _puttruetypeunicode($font) {
        $fontname = '';
        if ($font['subset']) {
            // change name for font subsetting
            $subtag = sprintf('%06u', $font['i']);
            $subtag = strtr($subtag, '0123456789', 'ABCDEFGHIJ');
            $fontname .= $subtag.'+';
        }
        $fontname .= $font['name'];
        // Type0 Font
        // A composite font composed of other fonts, organized hierarchically
        $out = $this->_getobj($this->font_obj_ids[$font['fontkey']])."\n";
        $out .= '<< /Type /Font';
        $out .= ' /Subtype /Type0';
        $out .= ' /BaseFont /'.$fontname;
        $out .= ' /Name /F'.$font['i'];
        $out .= ' /Encoding /'.$font['enc'];
        $out .= ' /ToUnicode '.($this->n + 1).' 0 R';
        $out .= ' /DescendantFonts ['.($this->n + 2).' 0 R]';
        $out .= ' >>';
        $out .= "\n".'endobj';
        $this->_out($out);
        // ToUnicode map for Identity-H
        $stream = TCPDF_FONT_DATA::$uni_identity_h;
        // ToUnicode Object
        $this->_newobj();
        $stream = ($this->compress) ? gzcompress($stream) : $stream;
        $filter = ($this->compress) ? '/Filter /FlateDecode ' : '';
        $stream = $this->_getrawstream($stream);
        $this->_out('<<'.$filter.'/Length '.strlen($stream).'>> stream'."\n".$stream."\n".'endstream'."\n".'endobj');
        // CIDFontType2
        // A CIDFont whose glyph descriptions are based on TrueType font technology
        $oid = $this->_newobj();
        $out = '<< /Type /Font';
        $out .= ' /Subtype /CIDFontType2';
        $out .= ' /BaseFont /'.$fontname;
        // A dictionary containing entries that define the character collection of the CIDFont.
        $cidinfo = '/Registry '.$this->_datastring($font['cidinfo']['Registry'], $oid);
        $cidinfo .= ' /Ordering '.$this->_datastring($font['cidinfo']['Ordering'], $oid);
        $cidinfo .= ' /Supplement '.$font['cidinfo']['Supplement'];
        $out .= ' /CIDSystemInfo << '.$cidinfo.' >>';
        $out .= ' /FontDescriptor '.($this->n + 1).' 0 R';
        $out .= ' /DW '.$font['dw']; // default width
        $out .= "\n".TCPDF_FONTS::_putfontwidths($font, 0);
        if (isset($font['ctg']) AND (!TCPDF_STATIC::empty_string($font['ctg']))) {
            $out .= "\n".'/CIDToGIDMap '.($this->n + 2).' 0 R';
        }
        $out .= ' >>';
        $out .= "\n".'endobj';
        $this->_out($out);
        // Font descriptor
        // A font descriptor describing the CIDFont default metrics other than its glyph widths
        $this->_newobj();
        $out = '<< /Type /FontDescriptor';
        $out .= ' /FontName /'.$fontname;
        foreach ($font['desc'] as $key => $value) {
            if (is_float($value)) {
                $value = sprintf('%F', $value);
            }
            $out .= ' /'.$key.' '.$value;
        }
        $fontdir = false;
        if (!TCPDF_STATIC::empty_string($font['file'])) {
            // A stream containing a TrueType font
            $out .= ' /FontFile2 '.$this->FontFiles[$font['file']]['n'].' 0 R';
            $fontdir = $this->FontFiles[$font['file']]['fontdir'];
        }
        $out .= ' >>';
        $out .= "\n".'endobj';
        $this->_out($out);
        if (isset($font['ctg']) AND (!TCPDF_STATIC::empty_string($font['ctg']))) {
            $this->_newobj();
            // Embed CIDToGIDMap
            // A specification of the mapping from CIDs to glyph indices
            // search and get CTG font file to embedd
            $ctgfile = strtolower($font['ctg']);
            // search and get ctg font file to embedd
            $fontfile = TCPDF_FONTS::getFontFullPath($ctgfile, $fontdir);
            if (TCPDF_STATIC::empty_string($fontfile)) {
                $this->Error('Font file not found: '.$ctgfile);
            }
            $stream = $this->_getrawstream(file_get_contents($fontfile));
            $out = '<< /Length '.strlen($stream).'';
            if (substr($fontfile, -2) == '.z') { // check file extension
                // Decompresses data encoded using the public-domain
                // zlib/deflate compression method, reproducing the
                // original text or binary data
                $out .= ' /Filter /FlateDecode';
            }
            $out .= ' >>';
            $out .= ' stream'."\n".$stream."\n".'endstream';
            $out .= "\n".'endobj';
            $this->_out($out);
        }
    }

    /**
     * Output CID-0 fonts.
     * A Type 0 CIDFont contains glyph descriptions based on the Adobe Type 1 font format
     * @param $font (array) font data
     * @protected
     * @author Andrew Whitehead, Nicola Asuni, Yukihiro Nakadaira
     * @since 3.2.000 (2008-06-23)
     */
    protected function _putcidfont0($font) {
        $cidoffset = 0;
        if (!isset($font['cw'][1])) {
            $cidoffset = 31;
        }
        if (isset($font['cidinfo']['uni2cid'])) {
            // convert unicode to cid.
            $uni2cid = $font['cidinfo']['uni2cid'];
            $cw = array();
            foreach ($font['cw'] as $uni => $width) {
                if (isset($uni2cid[$uni])) {
                    $cw[($uni2cid[$uni] + $cidoffset)] = $width;
                } elseif ($uni < 256) {
                    $cw[$uni] = $width;
                } // else unknown character
            }
            $font = array_merge($font, array('cw' => $cw));
        }
        $name = $font['name'];
        $enc = $font['enc'];
        if ($enc) {
            $longname = $name.'-'.$enc;
        } else {
            $longname = $name;
        }
        $out = $this->_getobj($this->font_obj_ids[$font['fontkey']])."\n";
        $out .= '<</Type /Font';
        $out .= ' /Subtype /Type0';
        $out .= ' /BaseFont /'.$longname;
        $out .= ' /Name /F'.$font['i'];
        if ($enc) {
            $out .= ' /Encoding /'.$enc;
        }
        $out .= ' /DescendantFonts ['.($this->n + 1).' 0 R]';
        $out .= ' >>';
        $out .= "\n".'endobj';
        $this->_out($out);
        $oid = $this->_newobj();
        $out = '<</Type /Font';
        $out .= ' /Subtype /CIDFontType0';
        $out .= ' /BaseFont /'.$name;
        $cidinfo = '/Registry '.$this->_datastring($font['cidinfo']['Registry'], $oid);
        $cidinfo .= ' /Ordering '.$this->_datastring($font['cidinfo']['Ordering'], $oid);
        $cidinfo .= ' /Supplement '.$font['cidinfo']['Supplement'];
        $out .= ' /CIDSystemInfo <<'.$cidinfo.'>>';
        $out .= ' /FontDescriptor '.($this->n + 1).' 0 R';
        $out .= ' /DW '.$font['dw'];
        $out .= "\n".TCPDF_FONTS::_putfontwidths($font, $cidoffset);
        $out .= ' >>';
        $out .= "\n".'endobj';
        $this->_out($out);
        $this->_newobj();
        $s = '<</Type /FontDescriptor /FontName /'.$name;
        foreach ($font['desc'] as $k => $v) {
            if ($k != 'Style') {
                if (is_float($v)) {
                    $v = sprintf('%F', $v);
                }
                $s .= ' /'.$k.' '.$v.'';
            }
        }
        $s .= '>>';
        $s .= "\n".'endobj';
        $this->_out($s);
    }

    /**
     * Output images.
     * @protected
     */
    protected function _putimages() {
        $filter = ($this->compress) ? '/Filter /FlateDecode ' : '';
        foreach ($this->imagekeys as $file) {
            $info = $this->getImageBuffer($file);
            // set object for alternate images array
            if ((!$this->pdfa_mode) AND isset($info['altimgs']) AND !empty($info['altimgs'])) {
                $altoid = $this->_newobj();
                $out = '[';
                foreach ($info['altimgs'] as $altimage) {
                    if (isset($this->xobjects['I'.$altimage[0]]['n'])) {
                        $out .= ' << /Image '.$this->xobjects['I'.$altimage[0]]['n'].' 0 R';
                        $out .= ' /DefaultForPrinting';
                        if ($altimage[1] === true) {
                            $out .= ' true';
                        } else {
                            $out .= ' false';
                        }
                        $out .= ' >>';
                    }
                }
                $out .= ' ]';
                $out .= "\n".'endobj';
                $this->_out($out);
            }
            // set image object
            $oid = $this->_newobj();
            $this->xobjects['I'.$info['i']] = array('n' => $oid);
            $this->setImageSubBuffer($file, 'n', $this->n);
            $out = '<</Type /XObject';
            $out .= ' /Subtype /Image';
            $out .= ' /Width '.$info['w'];
            $out .= ' /Height '.$info['h'];
            if (array_key_exists('masked', $info)) {
                $out .= ' /SMask '.($this->n - 1).' 0 R';
            }
            // set color space
            $icc = false;
            if (isset($info['icc']) AND ($info['icc'] !== false)) {
                // ICC Colour Space
                $icc = true;
                $out .= ' /ColorSpace [/ICCBased '.($this->n + 1).' 0 R]';
            } elseif ($info['cs'] == 'Indexed') {
                // Indexed Colour Space
                $out .= ' /ColorSpace [/Indexed /DeviceRGB '.((strlen($info['pal']) / 3) - 1).' '.($this->n + 1).' 0 R]';
            } else {
                // Device Colour Space
                $out .= ' /ColorSpace /'.$info['cs'];
            }
            if ($info['cs'] == 'DeviceCMYK') {
                $out .= ' /Decode [1 0 1 0 1 0 1 0]';
            }
            $out .= ' /BitsPerComponent '.$info['bpc'];
            if (isset($altoid) AND ($altoid > 0)) {
                // reference to alternate images dictionary
                $out .= ' /Alternates '.$altoid.' 0 R';
            }
            if (isset($info['exurl']) AND !empty($info['exurl'])) {
                // external stream
                $out .= ' /Length 0';
                $out .= ' /F << /FS /URL /F '.$this->_datastring($info['exurl'], $oid).' >>';
                if (isset($info['f'])) {
                    $out .= ' /FFilter /'.$info['f'];
                }
                $out .= ' >>';
                $out .= ' stream'."\n".'endstream';
            } else {
                if (isset($info['f'])) {
                    $out .= ' /Filter /'.$info['f'];
                }
                if (isset($info['parms'])) {
                    $out .= ' '.$info['parms'];
                }
                if (isset($info['trns']) AND is_array($info['trns'])) {
                    $trns = '';
                    $count_info = count($info['trns']);
                    if ($info['cs'] == 'Indexed') {
                        $maxval =(pow(2, $info['bpc']) - 1);
                        for ($i = 0; $i < $count_info; ++$i) {
                            if (($info['trns'][$i] != 0) AND ($info['trns'][$i] != $maxval)) {
                                // this is not a binary type mask @TODO: create a SMask
                                $trns = '';
                                break;
                            } elseif (empty($trns) AND ($info['trns'][$i] == 0)) {
                                // store the first fully transparent value
                                $trns .= $i.' '.$i.' ';
                            }
                        }
                    } else {
                        // grayscale or RGB
                        for ($i = 0; $i < $count_info; ++$i) {
                            if ($info['trns'][$i] == 0) {
                                $trns .= $info['trns'][$i].' '.$info['trns'][$i].' ';
                            }
                        }
                    }
                    // Colour Key Masking
                    if (!empty($trns)) {
                        $out .= ' /Mask ['.$trns.']';
                    }
                }
                $stream = $this->_getrawstream($info['data']);
                $out .= ' /Length '.strlen($stream).' >>';
                $out .= ' stream'."\n".$stream."\n".'endstream';
            }
            $out .= "\n".'endobj';
            $this->_out($out);
            if ($icc) {
                // ICC colour profile
                $this->_newobj();
                $icc = ($this->compress) ? gzcompress($info['icc']) : $info['icc'];
                $icc = $this->_getrawstream($icc);
                $this->_out('<</N '.$info['ch'].' /Alternate /'.$info['cs'].' '.$filter.'/Length '.strlen($icc).'>> stream'."\n".$icc."\n".'endstream'."\n".'endobj');
            } elseif ($info['cs'] == 'Indexed') {
                // colour palette
                $this->_newobj();
                $pal = ($this->compress) ? gzcompress($info['pal']) : $info['pal'];
                $pal = $this->_getrawstream($pal);
                $this->_out('<<'.$filter.'/Length '.strlen($pal).'>> stream'."\n".$pal."\n".'endstream'."\n".'endobj');
            }
        }
    }

    /**
     * Output Form XObjects Templates.
     * @author Nicola Asuni
     * @since 5.8.017 (2010-08-24)
     * @protected
     * @see startTemplate(), endTemplate(), printTemplate()
     */
    protected function _putxobjects() {
        foreach ($this->xobjects as $key => $data) {
            if (isset($data['outdata'])) {
                $stream = str_replace($this->epsmarker, '', trim($data['outdata']));
                $out = $this->_getobj($data['n'])."\n";
                $out .= '<<';
                $out .= ' /Type /XObject';
                $out .= ' /Subtype /Form';
                $out .= ' /FormType 1';
                if ($this->compress) {
                    $stream = gzcompress($stream);
                    $out .= ' /Filter /FlateDecode';
                }
                $out .= sprintf(' /BBox [%F %F %F %F]', ($data['x'] * $this->k), (-$data['y'] * $this->k), (($data['w'] + $data['x']) * $this->k), (($data['h'] - $data['y']) * $this->k));
                $out .= ' /Matrix [1 0 0 1 0 0]';
                $out .= ' /Resources <<';
                $out .= ' /ProcSet [/PDF /Text /ImageB /ImageC /ImageI]';
                if (!$this->pdfa_mode) {
                    // transparency
                    if (isset($data['extgstates']) AND !empty($data['extgstates'])) {
                        $out .= ' /ExtGState <<';
                        foreach ($data['extgstates'] as $k => $extgstate) {
                            if (isset($this->extgstates[$k]['name'])) {
                                $out .= ' /'.$this->extgstates[$k]['name'];
                            } else {
                                $out .= ' /GS'.$k;
                            }
                            $out .= ' '.$this->extgstates[$k]['n'].' 0 R';
                        }
                        $out .= ' >>';
                    }
                    if (isset($data['gradients']) AND !empty($data['gradients'])) {
                        $gp = '';
                        $gs = '';
                        foreach ($data['gradients'] as $id => $grad) {
                            // gradient patterns
                            $gp .= ' /p'.$id.' '.$this->gradients[$id]['pattern'].' 0 R';
                            // gradient shadings
                            $gs .= ' /Sh'.$id.' '.$this->gradients[$id]['id'].' 0 R';
                        }
                        $out .= ' /Pattern <<'.$gp.' >>';
                        $out .= ' /Shading <<'.$gs.' >>';
                    }
                }
                // spot colors
                if (isset($data['spot_colors']) AND !empty($data['spot_colors'])) {
                    $out .= ' /ColorSpace <<';
                    foreach ($data['spot_colors'] as $name => $color) {
                        $out .= ' /CS'.$color['i'].' '.$this->spot_colors[$name]['n'].' 0 R';
                    }
                    $out .= ' >>';
                }
                // fonts
                if (!empty($data['fonts'])) {
                    $out .= ' /Font <<';
                    foreach ($data['fonts'] as $fontkey => $fontid) {
                        $out .= ' /F'.$fontid.' '.$this->font_obj_ids[$fontkey].' 0 R';
                    }
                    $out .= ' >>';
                }
                // images or nested xobjects
                if (!empty($data['images']) OR !empty($data['xobjects'])) {
                    $out .= ' /XObject <<';
                    foreach ($data['images'] as $imgid) {
                        $out .= ' /I'.$imgid.' '.$this->xobjects['I'.$imgid]['n'].' 0 R';
                    }
                    foreach ($data['xobjects'] as $sub_id => $sub_objid) {
                        $out .= ' /'.$sub_id.' '.$sub_objid['n'].' 0 R';
                    }
                    $out .= ' >>';
                }
                $out .= ' >>'; //end resources
                if (isset($data['group']) AND ($data['group'] !== false)) {
                    // set transparency group
                    $out .= ' /Group << /Type /Group /S /Transparency';
                    if (is_array($data['group'])) {
                        if (isset($data['group']['CS']) AND !empty($data['group']['CS'])) {
                            $out .= ' /CS /'.$data['group']['CS'];
                        }
                        if (isset($data['group']['I'])) {
                            $out .= ' /I /'.($data['group']['I']===true?'true':'false');
                        }
                        if (isset($data['group']['K'])) {
                            $out .= ' /K /'.($data['group']['K']===true?'true':'false');
                        }
                    }
                    $out .= ' >>';
                }
                $stream = $this->_getrawstream($stream, $data['n']);
                $out .= ' /Length '.strlen($stream);
                $out .= ' >>';
                $out .= ' stream'."\n".$stream."\n".'endstream';
                $out .= "\n".'endobj';
                $this->_out($out);
            }
        }
    }

    /**
     * Output Spot Colors Resources.
     * @protected
     * @since 4.0.024 (2008-09-12)
     */
    protected function _putspotcolors() {
        foreach ($this->spot_colors as $name => $color) {
            $this->_newobj();
            $this->spot_colors[$name]['n'] = $this->n;
            $out = '[/Separation /'.str_replace(' ', '#20', $name);
            $out .= ' /DeviceCMYK <<';
            $out .= ' /Range [0 1 0 1 0 1 0 1] /C0 [0 0 0 0]';
            $out .= ' '.sprintf('/C1 [%F %F %F %F] ', ($color['C'] / 100), ($color['M'] / 100), ($color['Y'] / 100), ($color['K'] / 100));
            $out .= ' /FunctionType 2 /Domain [0 1] /N 1>>]';
            $out .= "\n".'endobj';
            $this->_out($out);
        }
    }

    /**
     * Return XObjects Dictionary.
     * @return string XObjects dictionary
     * @protected
     * @since 5.8.014 (2010-08-23)
     */
    protected function _getxobjectdict() {
        $out = '';
        foreach ($this->xobjects as $id => $objid) {
            $out .= ' /'.$id.' '.$objid['n'].' 0 R';
        }
        return $out;
    }

    /**
     * Output Resources Dictionary.
     * @protected
     */
    protected function _putresourcedict() {
        $out = $this->_getobj(2)."\n";
        $out .= '<< /ProcSet [/PDF /Text /ImageB /ImageC /ImageI]';
        $out .= ' /Font <<';
        foreach ($this->fontkeys as $fontkey) {
            $font = $this->getFontBuffer($fontkey);
            $out .= ' /F'.$font['i'].' '.$font['n'].' 0 R';
        }
        $out .= ' >>';
        $out .= ' /XObject <<';
        $out .= $this->_getxobjectdict();
        $out .= ' >>';
        // layers
        if (!empty($this->pdflayers)) {
            $out .= ' /Properties <<';
            foreach ($this->pdflayers as $layer) {
                $out .= ' /'.$layer['layer'].' '.$layer['objid'].' 0 R';
            }
            $out .= ' >>';
        }
        if (!$this->pdfa_mode) {
            // transparency
            if (isset($this->extgstates) AND !empty($this->extgstates)) {
                $out .= ' /ExtGState <<';
                foreach ($this->extgstates as $k => $extgstate) {
                    if (isset($extgstate['name'])) {
                        $out .= ' /'.$extgstate['name'];
                    } else {
                        $out .= ' /GS'.$k;
                    }
                    $out .= ' '.$extgstate['n'].' 0 R';
                }
                $out .= ' >>';
            }
            if (isset($this->gradients) AND !empty($this->gradients)) {
                $gp = '';
                $gs = '';
                foreach ($this->gradients as $id => $grad) {
                    // gradient patterns
                    $gp .= ' /p'.$id.' '.$grad['pattern'].' 0 R';
                    // gradient shadings
                    $gs .= ' /Sh'.$id.' '.$grad['id'].' 0 R';
                }
                $out .= ' /Pattern <<'.$gp.' >>';
                $out .= ' /Shading <<'.$gs.' >>';
            }
        }
        // spot colors
        if (isset($this->spot_colors) AND !empty($this->spot_colors)) {
            $out .= ' /ColorSpace <<';
            foreach ($this->spot_colors as $color) {
                $out .= ' /CS'.$color['i'].' '.$color['n'].' 0 R';
            }
            $out .= ' >>';
        }
        $out .= ' >>';
        $out .= "\n".'endobj';
        $this->_out($out);
    }

    /**
     * Output Resources.
     * @protected
     */
    protected function _putresources() {
        $this->_putextgstates();
        $this->_putocg();
        $this->_putfonts();
        $this->_putimages();
        $this->_putspotcolors();
        $this->_putshaders();
        $this->_putxobjects();
        $this->_putresourcedict();
        $this->_putdests();
        $this->_putEmbeddedFiles();
        $this->_putannotsobjs();
        $this->_putjavascript();
        $this->_putbookmarks();
        $this->_putencryption();
    }

    /**
     * Adds some Metadata information (Document Information Dictionary)
     * (see Chapter 14.3.3 Document Information Dictionary of PDF32000_2008.pdf Reference)
     * @return int object id
     * @protected
     */
    protected function _putinfo() {
        $oid = $this->_newobj();
        $out = '<<';
        // store current isunicode value
        $prev_isunicode = $this->isunicode;
        if ($this->docinfounicode) {
            $this->isunicode = true;
        }
        if (!TCPDF_STATIC::empty_string($this->title)) {
            // The document's title.
            $out .= ' /Title '.$this->_textstring($this->title, $oid);
        }
        if (!TCPDF_STATIC::empty_string($this->author)) {
            // The name of the person who created the document.
            $out .= ' /Author '.$this->_textstring($this->author, $oid);
        }
        if (!TCPDF_STATIC::empty_string($this->subject)) {
            // The subject of the document.
            $out .= ' /Subject '.$this->_textstring($this->subject, $oid);
        }
        if (!TCPDF_STATIC::empty_string($this->keywords)) {
            // Keywords associated with the document.
            $out .= ' /Keywords '.$this->_textstring($this->keywords, $oid);
        }
        if (!TCPDF_STATIC::empty_string($this->creator)) {
            // If the document was converted to PDF from another format, the name of the conforming product that created the original document from which it was converted.
            $out .= ' /Creator '.$this->_textstring($this->creator, $oid);
        }
        // restore previous isunicode value
        $this->isunicode = $prev_isunicode;
        // default producer
        $out .= ' /Producer '.$this->_textstring(TCPDF_STATIC::getTCPDFProducer(), $oid);
        // The date and time the document was created, in human-readable form
        $out .= ' /CreationDate '.$this->_datestring(0, $this->doc_creation_timestamp);
        // The date and time the document was most recently modified, in human-readable form
        $out .= ' /ModDate '.$this->_datestring(0, $this->doc_modification_timestamp);
        // A name object indicating whether the document has been modified to include trapping information
        $out .= ' /Trapped /False';
        $out .= ' >>';
        $out .= "\n".'endobj';
        $this->_out($out);
        return $oid;
    }

    /**
     * Set additional XMP data to be added on the default XMP data just before the end of "x:xmpmeta" tag.
     * IMPORTANT: This data is added as-is without controls, so you have to validate your data before using this method!
     * @param $xmp (string) Custom XMP data.
     * @since 5.9.128 (2011-10-06)
     * @public
     */
    public function setExtraXMP($xmp) {
        $this->custom_xmp = $xmp;
    }

    /**
     * Put XMP data object and return ID.
     * @return (int) The object ID.
     * @since 5.9.121 (2011-09-28)
     * @protected
     */
    protected function _putXMP() {
        $oid = $this->_newobj();
        // store current isunicode value
        $prev_isunicode = $this->isunicode;
        $this->isunicode = true;
        $prev_encrypted = $this->encrypted;
        $this->encrypted = false;
        // set XMP data
        $xmp = '<?xpacket begin="'.TCPDF_FONTS::unichr(0xfeff, $this->isunicode).'" id="W5M0MpCehiHzreSzNTczkc9d"?>'."\n";
        $xmp .= '<x:xmpmeta xmlns:x="adobe:ns:meta/" x:xmptk="Adobe XMP Core 4.2.1-c043 52.372728, 2009/01/18-15:08:04">'."\n";
        $xmp .= "\t".'<rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">'."\n";
        $xmp .= "\t\t".'<rdf:Description rdf:about="" xmlns:dc="http://purl.org/dc/elements/1.1/">'."\n";
        $xmp .= "\t\t\t".'<dc:format>application/pdf</dc:format>'."\n";
        $xmp .= "\t\t\t".'<dc:title>'."\n";
        $xmp .= "\t\t\t\t".'<rdf:Alt>'."\n";
        $xmp .= "\t\t\t\t\t".'<rdf:li xml:lang="x-default">'.TCPDF_STATIC::_escapeXML($this->title).'</rdf:li>'."\n";
        $xmp .= "\t\t\t\t".'</rdf:Alt>'."\n";
        $xmp .= "\t\t\t".'</dc:title>'."\n";
        $xmp .= "\t\t\t".'<dc:creator>'."\n";
        $xmp .= "\t\t\t\t".'<rdf:Seq>'."\n";
        $xmp .= "\t\t\t\t\t".'<rdf:li>'.TCPDF_STATIC::_escapeXML($this->author).'</rdf:li>'."\n";
        $xmp .= "\t\t\t\t".'</rdf:Seq>'."\n";
        $xmp .= "\t\t\t".'</dc:creator>'."\n";
        $xmp .= "\t\t\t".'<dc:description>'."\n";
        $xmp .= "\t\t\t\t".'<rdf:Alt>'."\n";
        $xmp .= "\t\t\t\t\t".'<rdf:li xml:lang="x-default">'.TCPDF_STATIC::_escapeXML($this->subject).'</rdf:li>'."\n";
        $xmp .= "\t\t\t\t".'</rdf:Alt>'."\n";
        $xmp .= "\t\t\t".'</dc:description>'."\n";
        $xmp .= "\t\t\t".'<dc:subject>'."\n";
        $xmp .= "\t\t\t\t".'<rdf:Bag>'."\n";
        $xmp .= "\t\t\t\t\t".'<rdf:li>'.TCPDF_STATIC::_escapeXML($this->keywords).'</rdf:li>'."\n";
        $xmp .= "\t\t\t\t".'</rdf:Bag>'."\n";
        $xmp .= "\t\t\t".'</dc:subject>'."\n";
        $xmp .= "\t\t".'</rdf:Description>'."\n";
        // convert doc creation date format
        $dcdate = TCPDF_STATIC::getFormattedDate($this->doc_creation_timestamp);
        $doccreationdate = substr($dcdate, 0, 4).'-'.substr($dcdate, 4, 2).'-'.substr($dcdate, 6, 2);
        $doccreationdate .= 'T'.substr($dcdate, 8, 2).':'.substr($dcdate, 10, 2).':'.substr($dcdate, 12, 2);
        $doccreationdate .= substr($dcdate, 14, 3).':'.substr($dcdate, 18, 2);
        $doccreationdate = TCPDF_STATIC::_escapeXML($doccreationdate);
        // convert doc modification date format
        $dmdate = TCPDF_STATIC::getFormattedDate($this->doc_modification_timestamp);
        $docmoddate = substr($dmdate, 0, 4).'-'.substr($dmdate, 4, 2).'-'.substr($dmdate, 6, 2);
        $docmoddate .= 'T'.substr($dmdate, 8, 2).':'.substr($dmdate, 10, 2).':'.substr($dmdate, 12, 2);
        $docmoddate .= substr($dmdate, 14, 3).':'.substr($dmdate, 18, 2);
        $docmoddate = TCPDF_STATIC::_escapeXML($docmoddate);
        $xmp .= "\t\t".'<rdf:Description rdf:about="" xmlns:xmp="http://ns.adobe.com/xap/1.0/">'."\n";
        $xmp .= "\t\t\t".'<xmp:CreateDate>'.$doccreationdate.'</xmp:CreateDate>'."\n";
        $xmp .= "\t\t\t".'<xmp:CreatorTool>'.$this->creator.'</xmp:CreatorTool>'."\n";
        $xmp .= "\t\t\t".'<xmp:ModifyDate>'.$docmoddate.'</xmp:ModifyDate>'."\n";
        $xmp .= "\t\t\t".'<xmp:MetadataDate>'.$doccreationdate.'</xmp:MetadataDate>'."\n";
        $xmp .= "\t\t".'</rdf:Description>'."\n";
        $xmp .= "\t\t".'<rdf:Description rdf:about="" xmlns:pdf="http://ns.adobe.com/pdf/1.3/">'."\n";
        $xmp .= "\t\t\t".'<pdf:Keywords>'.TCPDF_STATIC::_escapeXML($this->keywords).'</pdf:Keywords>'."\n";
        $xmp .= "\t\t\t".'<pdf:Producer>'.TCPDF_STATIC::_escapeXML(TCPDF_STATIC::getTCPDFProducer()).'</pdf:Producer>'."\n";
        $xmp .= "\t\t".'</rdf:Description>'."\n";
        $xmp .= "\t\t".'<rdf:Description rdf:about="" xmlns:xmpMM="http://ns.adobe.com/xap/1.0/mm/">'."\n";
        $uuid = 'uuid:'.substr($this->file_id, 0, 8).'-'.substr($this->file_id, 8, 4).'-'.substr($this->file_id, 12, 4).'-'.substr($this->file_id, 16, 4).'-'.substr($this->file_id, 20, 12);
        $xmp .= "\t\t\t".'<xmpMM:DocumentID>'.$uuid.'</xmpMM:DocumentID>'."\n";
        $xmp .= "\t\t\t".'<xmpMM:InstanceID>'.$uuid.'</xmpMM:InstanceID>'."\n";
        $xmp .= "\t\t".'</rdf:Description>'."\n";
        if ($this->pdfa_mode) {
            $xmp .= "\t\t".'<rdf:Description rdf:about="" xmlns:pdfaid="http://www.aiim.org/pdfa/ns/id/">'."\n";
            $xmp .= "\t\t\t".'<pdfaid:part>1</pdfaid:part>'."\n";
            $xmp .= "\t\t\t".'<pdfaid:conformance>B</pdfaid:conformance>'."\n";
            $xmp .= "\t\t".'</rdf:Description>'."\n";
        }
        // XMP extension schemas
        $xmp .= "\t\t".'<rdf:Description rdf:about="" xmlns:pdfaExtension="http://www.aiim.org/pdfa/ns/extension/" xmlns:pdfaSchema="http://www.aiim.org/pdfa/ns/schema#" xmlns:pdfaProperty="http://www.aiim.org/pdfa/ns/property#">'."\n";
        $xmp .= "\t\t\t".'<pdfaExtension:schemas>'."\n";
        $xmp .= "\t\t\t\t".'<rdf:Bag>'."\n";
        $xmp .= "\t\t\t\t\t".'<rdf:li rdf:parseType="Resource">'."\n";
        $xmp .= "\t\t\t\t\t\t".'<pdfaSchema:namespaceURI>http://ns.adobe.com/pdf/1.3/</pdfaSchema:namespaceURI>'."\n";
        $xmp .= "\t\t\t\t\t\t".'<pdfaSchema:prefix>pdf</pdfaSchema:prefix>'."\n";
        $xmp .= "\t\t\t\t\t\t".'<pdfaSchema:schema>Adobe PDF Schema</pdfaSchema:schema>'."\n";
        $xmp .= "\t\t\t\t\t".'</rdf:li>'."\n";
        $xmp .= "\t\t\t\t\t".'<rdf:li rdf:parseType="Resource">'."\n";
        $xmp .= "\t\t\t\t\t\t".'<pdfaSchema:namespaceURI>http://ns.adobe.com/xap/1.0/mm/</pdfaSchema:namespaceURI>'."\n";
        $xmp .= "\t\t\t\t\t\t".'<pdfaSchema:prefix>xmpMM</pdfaSchema:prefix>'."\n";
        $xmp .= "\t\t\t\t\t\t".'<pdfaSchema:schema>XMP Media Management Schema</pdfaSchema:schema>'."\n";
        $xmp .= "\t\t\t\t\t\t".'<pdfaSchema:property>'."\n";
        $xmp .= "\t\t\t\t\t\t\t".'<rdf:Seq>'."\n";
        $xmp .= "\t\t\t\t\t\t\t\t".'<rdf:li rdf:parseType="Resource">'."\n";
        $xmp .= "\t\t\t\t\t\t\t\t\t".'<pdfaProperty:category>internal</pdfaProperty:category>'."\n";
        $xmp .= "\t\t\t\t\t\t\t\t\t".'<pdfaProperty:description>UUID based identifier for specific incarnation of a document</pdfaProperty:description>'."\n";
        $xmp .= "\t\t\t\t\t\t\t\t\t".'<pdfaProperty:name>InstanceID</pdfaProperty:name>'."\n";
        $xmp .= "\t\t\t\t\t\t\t\t\t".'<pdfaProperty:valueType>URI</pdfaProperty:valueType>'."\n";
        $xmp .= "\t\t\t\t\t\t\t\t".'</rdf:li>'."\n";
        $xmp .= "\t\t\t\t\t\t\t".'</rdf:Seq>'."\n";
        $xmp .= "\t\t\t\t\t\t".'</pdfaSchema:property>'."\n";
        $xmp .= "\t\t\t\t\t".'</rdf:li>'."\n";
        $xmp .= "\t\t\t\t\t".'<rdf:li rdf:parseType="Resource">'."\n";
        $xmp .= "\t\t\t\t\t\t".'<pdfaSchema:namespaceURI>http://www.aiim.org/pdfa/ns/id/</pdfaSchema:namespaceURI>'."\n";
        $xmp .= "\t\t\t\t\t\t".'<pdfaSchema:prefix>pdfaid</pdfaSchema:prefix>'."\n";
        $xmp .= "\t\t\t\t\t\t".'<pdfaSchema:schema>PDF/A ID Schema</pdfaSchema:schema>'."\n";
        $xmp .= "\t\t\t\t\t\t".'<pdfaSchema:property>'."\n";
        $xmp .= "\t\t\t\t\t\t\t".'<rdf:Seq>'."\n";
        $xmp .= "\t\t\t\t\t\t\t\t".'<rdf:li rdf:parseType="Resource">'."\n";
        $xmp .= "\t\t\t\t\t\t\t\t\t".'<pdfaProperty:category>internal</pdfaProperty:category>'."\n";
        $xmp .= "\t\t\t\t\t\t\t\t\t".'<pdfaProperty:description>Part of PDF/A standard</pdfaProperty:description>'."\n";
        $xmp .= "\t\t\t\t\t\t\t\t\t".'<pdfaProperty:name>part</pdfaProperty:name>'."\n";
        $xmp .= "\t\t\t\t\t\t\t\t\t".'<pdfaProperty:valueType>Integer</pdfaProperty:valueType>'."\n";
        $xmp .= "\t\t\t\t\t\t\t\t".'</rdf:li>'."\n";
        $xmp .= "\t\t\t\t\t\t\t\t".'<rdf:li rdf:parseType="Resource">'."\n";
        $xmp .= "\t\t\t\t\t\t\t\t\t".'<pdfaProperty:category>internal</pdfaProperty:category>'."\n";
        $xmp .= "\t\t\t\t\t\t\t\t\t".'<pdfaProperty:description>Amendment of PDF/A standard</pdfaProperty:description>'."\n";
        $xmp .= "\t\t\t\t\t\t\t\t\t".'<pdfaProperty:name>amd</pdfaProperty:name>'."\n";
        $xmp .= "\t\t\t\t\t\t\t\t\t".'<pdfaProperty:valueType>Text</pdfaProperty:valueType>'."\n";
        $xmp .= "\t\t\t\t\t\t\t\t".'</rdf:li>'."\n";
        $xmp .= "\t\t\t\t\t\t\t\t".'<rdf:li rdf:parseType="Resource">'."\n";
        $xmp .= "\t\t\t\t\t\t\t\t\t".'<pdfaProperty:category>internal</pdfaProperty:category>'."\n";
        $xmp .= "\t\t\t\t\t\t\t\t\t".'<pdfaProperty:description>Conformance level of PDF/A standard</pdfaProperty:description>'."\n";
        $xmp .= "\t\t\t\t\t\t\t\t\t".'<pdfaProperty:name>conformance</pdfaProperty:name>'."\n";
        $xmp .= "\t\t\t\t\t\t\t\t\t".'<pdfaProperty:valueType>Text</pdfaProperty:valueType>'."\n";
        $xmp .= "\t\t\t\t\t\t\t\t".'</rdf:li>'."\n";
        $xmp .= "\t\t\t\t\t\t\t".'</rdf:Seq>'."\n";
        $xmp .= "\t\t\t\t\t\t".'</pdfaSchema:property>'."\n";
        $xmp .= "\t\t\t\t\t".'</rdf:li>'."\n";
        $xmp .= "\t\t\t\t".'</rdf:Bag>'."\n";
        $xmp .= "\t\t\t".'</pdfaExtension:schemas>'."\n";
        $xmp .= "\t\t".'</rdf:Description>'."\n";
        $xmp .= "\t".'</rdf:RDF>'."\n";
        $xmp .= $this->custom_xmp;
        $xmp .= '</x:xmpmeta>'."\n";
        $xmp .= '<?xpacket end="w"?>';
        $out = '<< /Type /Metadata /Subtype /XML /Length '.strlen($xmp).' >> stream'."\n".$xmp."\n".'endstream'."\n".'endobj';
        // restore previous isunicode value
        $this->isunicode = $prev_isunicode;
        $this->encrypted = $prev_encrypted;
        $this->_out($out);
        return $oid;
    }

    /**
     * Output Catalog.
     * @return int object id
     * @protected
     */
    protected function _putcatalog() {
        // put XMP
        $xmpobj = $this->_putXMP();
        // if required, add standard sRGB_IEC61966-2.1 blackscaled ICC colour profile
        if ($this->pdfa_mode OR $this->force_srgb) {
            $iccobj = $this->_newobj();
            $icc = file_get_contents(dirname(__FILE__).'/include/sRGB.icc');
            $filter = '';
            if ($this->compress) {
                $filter = ' /Filter /FlateDecode';
                $icc = gzcompress($icc);
            }
            $icc = $this->_getrawstream($icc);
            $this->_out('<</N 3 '.$filter.'/Length '.strlen($icc).'>> stream'."\n".$icc."\n".'endstream'."\n".'endobj');
        }
        // start catalog
        $oid = $this->_newobj();
        $out = '<< /Type /Catalog';
        $out .= ' /Version /'.$this->PDFVersion;
        //$out .= ' /Extensions <<>>';
        $out .= ' /Pages 1 0 R';
        //$out .= ' /PageLabels ' //...;
        $out .= ' /Names <<';
        if ((!$this->pdfa_mode) AND !empty($this->n_js)) {
            $out .= ' /JavaScript '.$this->n_js;
        }
        if (!empty($this->efnames)) {
            $out .= ' /EmbeddedFiles <</Names [';
            foreach ($this->efnames AS $fn => $fref) {
                $out .= ' '.$this->_datastring($fn).' '.$fref;
            }
            $out .= ' ]>>';
        }
        $out .= ' >>';
        if (!empty($this->dests)) {
            $out .= ' /Dests '.($this->n_dests).' 0 R';
        }
        $out .= $this->_putviewerpreferences();
        if (isset($this->LayoutMode) AND (!TCPDF_STATIC::empty_string($this->LayoutMode))) {
            $out .= ' /PageLayout /'.$this->LayoutMode;
        }
        if (isset($this->PageMode) AND (!TCPDF_STATIC::empty_string($this->PageMode))) {
            $out .= ' /PageMode /'.$this->PageMode;
        }
        if (count($this->outlines) > 0) {
            $out .= ' /Outlines '.$this->OutlineRoot.' 0 R';
            $out .= ' /PageMode /UseOutlines';
        }
        //$out .= ' /Threads []';
        if ($this->ZoomMode == 'fullpage') {
            $out .= ' /OpenAction ['.$this->page_obj_id[1].' 0 R /Fit]';
        } elseif ($this->ZoomMode == 'fullwidth') {
            $out .= ' /OpenAction ['.$this->page_obj_id[1].' 0 R /FitH null]';
        } elseif ($this->ZoomMode == 'real') {
            $out .= ' /OpenAction ['.$this->page_obj_id[1].' 0 R /XYZ null null 1]';
        } elseif (!is_string($this->ZoomMode)) {
            $out .= sprintf(' /OpenAction ['.$this->page_obj_id[1].' 0 R /XYZ null null %F]', ($this->ZoomMode / 100));
        }
        //$out .= ' /AA <<>>';
        //$out .= ' /URI <<>>';
        $out .= ' /Metadata '.$xmpobj.' 0 R';
        //$out .= ' /StructTreeRoot <<>>';
        //$out .= ' /MarkInfo <<>>';
        if (isset($this->l['a_meta_language'])) {
            $out .= ' /Lang '.$this->_textstring($this->l['a_meta_language'], $oid);
        }
        //$out .= ' /SpiderInfo <<>>';
        // set OutputIntent to sRGB IEC61966-2.1 if required
        if ($this->pdfa_mode OR $this->force_srgb) {
            $out .= ' /OutputIntents [<<';
            $out .= ' /Type /OutputIntent';
            $out .= ' /S /GTS_PDFA1';
            $out .= ' /OutputCondition '.$this->_textstring('sRGB IEC61966-2.1', $oid);
            $out .= ' /OutputConditionIdentifier '.$this->_textstring('sRGB IEC61966-2.1', $oid);
            $out .= ' /RegistryName '.$this->_textstring('http://www.color.org', $oid);
            $out .= ' /Info '.$this->_textstring('sRGB IEC61966-2.1', $oid);
            $out .= ' /DestOutputProfile '.$iccobj.' 0 R';
            $out .= ' >>]';
        }
        //$out .= ' /PieceInfo <<>>';
        if (!empty($this->pdflayers)) {
            $lyrobjs = '';
            $lyrobjs_off = '';
            $lyrobjs_lock = '';
            foreach ($this->pdflayers as $layer) {
                $layer_obj_ref = ' '.$layer['objid'].' 0 R';
                $lyrobjs .= $layer_obj_ref;
                if ($layer['view'] === false) {
                    $lyrobjs_off .= $layer_obj_ref;
                }
                if ($layer['lock']) {
                    $lyrobjs_lock .= $layer_obj_ref;
                }
            }
            $out .= ' /OCProperties << /OCGs ['.$lyrobjs.']';
            $out .= ' /D <<';
            $out .= ' /Name '.$this->_textstring('Layers', $oid);
            $out .= ' /Creator '.$this->_textstring('TCPDF', $oid);
            $out .= ' /BaseState /ON';
            $out .= ' /OFF ['.$lyrobjs_off.']';
            $out .= ' /Locked ['.$lyrobjs_lock.']';
            $out .= ' /Intent /View';
            $out .= ' /AS [';
            $out .= ' << /Event /Print /OCGs ['.$lyrobjs.'] /Category [/Print] >>';
            $out .= ' << /Event /View /OCGs ['.$lyrobjs.'] /Category [/View] >>';
            $out .= ' ]';
            $out .= ' /Order ['.$lyrobjs.']';
            $out .= ' /ListMode /AllPages';
            //$out .= ' /RBGroups ['..']';
            //$out .= ' /Locked ['..']';
            $out .= ' >>';
            $out .= ' >>';
        }
        // AcroForm
        if (!empty($this->form_obj_id)
            OR ($this->sign AND isset($this->signature_data['cert_type']))
            OR !empty($this->empty_signature_appearance)) {
            $out .= ' /AcroForm <<';
            $objrefs = '';
            if ($this->sign AND isset($this->signature_data['cert_type'])) {
                // set reference for signature object
                $objrefs .= $this->sig_obj_id.' 0 R';
            }
            if (!empty($this->empty_signature_appearance)) {
                foreach ($this->empty_signature_appearance as $esa) {
                    // set reference for empty signature objects
                    $objrefs .= ' '.$esa['objid'].' 0 R';
                }
            }
            if (!empty($this->form_obj_id)) {
                foreach($this->form_obj_id as $objid) {
                    $objrefs .= ' '.$objid.' 0 R';
                }
            }
            $out .= ' /Fields ['.$objrefs.']';
            // It's better to turn off this value and set the appearance stream for each annotation (/AP) to avoid conflicts with signature fields.
            if (empty($this->signature_data['approval']) OR ($this->signature_data['approval'] != 'A')) {
                $out .= ' /NeedAppearances false';
            }
            if ($this->sign AND isset($this->signature_data['cert_type'])) {
                if ($this->signature_data['cert_type'] > 0) {
                    $out .= ' /SigFlags 3';
                } else {
                    $out .= ' /SigFlags 1';
                }
            }
            //$out .= ' /CO ';
            if (isset($this->annotation_fonts) AND !empty($this->annotation_fonts)) {
                $out .= ' /DR <<';
                $out .= ' /Font <<';
                foreach ($this->annotation_fonts as $fontkey => $fontid) {
                    $out .= ' /F'.$fontid.' '.$this->font_obj_ids[$fontkey].' 0 R';
                }
                $out .= ' >> >>';
            }
            $font = $this->getFontBuffer('helvetica');
            $out .= ' /DA (/F'.$font['i'].' 0 Tf 0 g)';
            $out .= ' /Q '.(($this->rtl)?'2':'0');
            //$out .= ' /XFA ';
            $out .= ' >>';
            // signatures
            if ($this->sign AND isset($this->signature_data['cert_type']) 
                AND (empty($this->signature_data['approval']) OR ($this->signature_data['approval'] != 'A'))) {
                if ($this->signature_data['cert_type'] > 0) {
                    $out .= ' /Perms << /DocMDP '.($this->sig_obj_id + 1).' 0 R >>';
                } else {
                    $out .= ' /Perms << /UR3 '.($this->sig_obj_id + 1).' 0 R >>';
                }
            }
        }
        //$out .= ' /Legal <<>>';
        //$out .= ' /Requirements []';
        //$out .= ' /Collection <<>>';
        //$out .= ' /NeedsRendering true';
        $out .= ' >>';
        $out .= "\n".'endobj';
        $this->_out($out);
        return $oid;
    }

    /**
     * Output viewer preferences.
     * @return string for viewer preferences
     * @author Nicola asuni
     * @since 3.1.000 (2008-06-09)
     * @protected
     */
    protected function _putviewerpreferences() {
        $vp = $this->viewer_preferences;
        $out = ' /ViewerPreferences <<';
        if ($this->rtl) {
            $out .= ' /Direction /R2L';
        } else {
            $out .= ' /Direction /L2R';
        }
        if (isset($vp['HideToolbar']) AND ($vp['HideToolbar'])) {
            $out .= ' /HideToolbar true';
        }
        if (isset($vp['HideMenubar']) AND ($vp['HideMenubar'])) {
            $out .= ' /HideMenubar true';
        }
        if (isset($vp['HideWindowUI']) AND ($vp['HideWindowUI'])) {
            $out .= ' /HideWindowUI true';
        }
        if (isset($vp['FitWindow']) AND ($vp['FitWindow'])) {
            $out .= ' /FitWindow true';
        }
        if (isset($vp['CenterWindow']) AND ($vp['CenterWindow'])) {
            $out .= ' /CenterWindow true';
        }
        if (isset($vp['DisplayDocTitle']) AND ($vp['DisplayDocTitle'])) {
            $out .= ' /DisplayDocTitle true';
        }
        if (isset($vp['NonFullScreenPageMode'])) {
            $out .= ' /NonFullScreenPageMode /'.$vp['NonFullScreenPageMode'];
        }
        if (isset($vp['ViewArea'])) {
            $out .= ' /ViewArea /'.$vp['ViewArea'];
        }
        if (isset($vp['ViewClip'])) {
            $out .= ' /ViewClip /'.$vp['ViewClip'];
        }
        if (isset($vp['PrintArea'])) {
            $out .= ' /PrintArea /'.$vp['PrintArea'];
        }
        if (isset($vp['PrintClip'])) {
            $out .= ' /PrintClip /'.$vp['PrintClip'];
        }
        if (isset($vp['PrintScaling'])) {
            $out .= ' /PrintScaling /'.$vp['PrintScaling'];
        }
        if (isset($vp['Duplex']) AND (!TCPDF_STATIC::empty_string($vp['Duplex']))) {
            $out .= ' /Duplex /'.$vp['Duplex'];
        }
        if (isset($vp['PickTrayByPDFSize'])) {
            if ($vp['PickTrayByPDFSize']) {
                $out .= ' /PickTrayByPDFSize true';
            } else {
                $out .= ' /PickTrayByPDFSize false';
            }
        }
        if (isset($vp['PrintPageRange'])) {
            $PrintPageRangeNum = '';
            foreach ($vp['PrintPageRange'] as $k => $v) {
                $PrintPageRangeNum .= ' '.($v - 1).'';
            }
            $out .= ' /PrintPageRange ['.substr($PrintPageRangeNum,1).']';
        }
        if (isset($vp['NumCopies'])) {
            $out .= ' /NumCopies '.intval($vp['NumCopies']);
        }
        $out .= ' >>';
        return $out;
    }

    /**
     * Output PDF File Header (7.5.2).
     * @protected
     */
    protected function _putheader() {
        $this->_out('%PDF-'.$this->PDFVersion);
        $this->_out('%'.chr(0xe2).chr(0xe3).chr(0xcf).chr(0xd3));
    }

    /**
     * Output end of document (EOF).
     * @protected
     */
    protected function _enddoc() {
        if (isset($this->CurrentFont['fontkey']) AND isset($this->CurrentFont['subsetchars'])) {
            // save subset chars of the previous font
            $this->setFontSubBuffer($this->CurrentFont['fontkey'], 'subsetchars', $this->CurrentFont['subsetchars']);
        }
        $this->state = 1;
        $this->_putheader();
        $this->_putpages();
        $this->_putresources();
        // empty signature fields
        if (!empty($this->empty_signature_appearance)) {
            foreach ($this->empty_signature_appearance as $key => $esa) {
                // widget annotation for empty signature
                $out = $this->_getobj($esa['objid'])."\n";
                $out .= '<< /Type /Annot';
                $out .= ' /Subtype /Widget';
                $out .= ' /Rect ['.$esa['rect'].']';
                $out .= ' /P '.$this->page_obj_id[($esa['page'])].' 0 R'; // link to signature appearance page
                $out .= ' /F 4';
                $out .= ' /FT /Sig';
                $signame = $esa['name'].sprintf(' [%03d]', ($key + 1));
                $out .= ' /T '.$this->_textstring($signame, $esa['objid']);
                $out .= ' /Ff 0';
                $out .= ' >>';
                $out .= "\n".'endobj';
                $this->_out($out);
            }
        }
        // Signature
        if ($this->sign AND isset($this->signature_data['cert_type'])) {
            // widget annotation for signature
            $out = $this->_getobj($this->sig_obj_id)."\n";
            $out .= '<< /Type /Annot';
            $out .= ' /Subtype /Widget';
            $out .= ' /Rect ['.$this->signature_appearance['rect'].']';
            $out .= ' /P '.$this->page_obj_id[($this->signature_appearance['page'])].' 0 R'; // link to signature appearance page
            $out .= ' /F 4';
            $out .= ' /FT /Sig';
            $out .= ' /T '.$this->_textstring($this->signature_appearance['name'], $this->sig_obj_id);
            $out .= ' /Ff 0';
            $out .= ' /V '.($this->sig_obj_id + 1).' 0 R';
            $out .= ' >>';
            $out .= "\n".'endobj';
            $this->_out($out);
            // signature
            $this->_putsignature();
        }
        // Info
        $objid_info = $this->_putinfo();
        // Catalog
        $objid_catalog = $this->_putcatalog();
        // Cross-ref
        $o = $this->bufferlen;
        // XREF section
        $this->_out('xref');
        $this->_out('0 '.($this->n + 1));
        $this->_out('0000000000 65535 f ');
        $freegen = ($this->n + 2);
        for ($i=1; $i <= $this->n; ++$i) {
            if (!isset($this->offsets[$i]) AND ($i > 1)) {
                $this->_out(sprintf('0000000000 %05d f ', $freegen));
                ++$freegen;
            } else {
                $this->_out(sprintf('%010d 00000 n ', $this->offsets[$i]));
            }
        }
        // TRAILER
        $out = 'trailer'."\n";
        $out .= '<<';
        $out .= ' /Size '.($this->n + 1);
        $out .= ' /Root '.$objid_catalog.' 0 R';
        $out .= ' /Info '.$objid_info.' 0 R';
        if ($this->encrypted) {
            $out .= ' /Encrypt '.$this->encryptdata['objid'].' 0 R';
        }
        $out .= ' /ID [ <'.$this->file_id.'> <'.$this->file_id.'> ]';
        $out .= ' >>';
        $this->_out($out);
        $this->_out('startxref');
        $this->_out($o);
        $this->_out('%%EOF');
        $this->state = 3; // end-of-doc
    }

    /**
     * Initialize a new page.
     * @param $orientation (string) page orientation. Possible values are (case insensitive):<ul><li>P or PORTRAIT (default)</li><li>L or LANDSCAPE</li></ul>
     * @param $format (mixed) The format used for pages. It can be either: one of the string values specified at getPageSizeFromFormat() or an array of parameters specified at setPageFormat().
     * @protected
     * @see getPageSizeFromFormat(), setPageFormat()
     */
    protected function _beginpage($orientation='', $format='') {
        ++$this->page;
        $this->pageobjects[$this->page] = array();
        $this->setPageBuffer($this->page, '');
        // initialize array for graphics tranformation positions inside a page buffer
        $this->transfmrk[$this->page] = array();
        $this->state = 2;
        if (TCPDF_STATIC::empty_string($orientation)) {
            if (isset($this->CurOrientation)) {
                $orientation = $this->CurOrientation;
            } elseif ($this->fwPt > $this->fhPt) {
                // landscape
                $orientation = 'L';
            } else {
                // portrait
                $orientation = 'P';
            }
        }
        if (TCPDF_STATIC::empty_string($format)) {
            $this->pagedim[$this->page] = $this->pagedim[($this->page - 1)];
            $this->setPageOrientation($orientation);
        } else {
            $this->setPageFormat($format, $orientation);
        }
        if ($this->rtl) {
            $this->x = $this->w - $this->rMargin;
        } else {
            $this->x = $this->lMargin;
        }
        $this->y = $this->tMargin;
        if (isset($this->newpagegroup[$this->page])) {
            // start a new group
            $this->currpagegroup = $this->newpagegroup[$this->page];
            $this->pagegroups[$this->currpagegroup] = 1;
        } elseif (isset($this->currpagegroup) AND ($this->currpagegroup > 0)) {
            ++$this->pagegroups[$this->currpagegroup];
        }
    }

    /**
     * Mark end of page.
     * @protected
     */
    protected function _endpage() {
        $this->setVisibility('all');
        $this->state = 1;
    }

    /**
     * Begin a new object and return the object number.
     * @return int object number
     * @protected
     */
    protected function _newobj() {
        $this->_out($this->_getobj());
        return $this->n;
    }

    /**
     * Return the starting object string for the selected object ID.
     * @param $objid (int) Object ID (leave empty to get a new ID).
     * @return string the starting object string
     * @protected
     * @since 5.8.009 (2010-08-20)
     */
    protected function _getobj($objid='') {
        if ($objid === '') {
            ++$this->n;
            $objid = $this->n;
        }
        $this->offsets[$objid] = $this->bufferlen;
        $this->pageobjects[$this->page][] = $objid;
        return $objid.' 0 obj';
    }

    /**
     * Underline text.
     * @param $x (int) X coordinate
     * @param $y (int) Y coordinate
     * @param $txt (string) text to underline
     * @protected
     */
    protected function _dounderline($x, $y, $txt) {
        $w = $this->GetStringWidth($txt);
        return $this->_dounderlinew($x, $y, $w);
    }

    /**
     * Underline for rectangular text area.
     * @param $x (int) X coordinate
     * @param $y (int) Y coordinate
     * @param $w (int) width to underline
     * @protected
     * @since 4.8.008 (2009-09-29)
     */
    protected function _dounderlinew($x, $y, $w) {
        $linew = - $this->CurrentFont['ut'] / 1000 * $this->FontSizePt;
        return sprintf('%F %F %F %F re f', $x * $this->k, ((($this->h - $y) * $this->k) + $linew), $w * $this->k, $linew);
    }

    /**
     * Line through text.
     * @param $x (int) X coordinate
     * @param $y (int) Y coordinate
     * @param $txt (string) text to linethrough
     * @protected
     */
    protected function _dolinethrough($x, $y, $txt) {
        $w = $this->GetStringWidth($txt);
        return $this->_dolinethroughw($x, $y, $w);
    }

    /**
     * Line through for rectangular text area.
     * @param $x (int) X coordinate
     * @param $y (int) Y coordinate
     * @param $w (int) line length (width)
     * @protected
     * @since 4.9.008 (2009-09-29)
     */
    protected function _dolinethroughw($x, $y, $w) {
        $linew = - $this->CurrentFont['ut'] / 1000 * $this->FontSizePt;
        return sprintf('%F %F %F %F re f', $x * $this->k, ((($this->h - $y) * $this->k) + $linew + ($this->FontSizePt / 3)), $w * $this->k, $linew);
    }

    /**
     * Overline text.
     * @param $x (int) X coordinate
     * @param $y (int) Y coordinate
     * @param $txt (string) text to overline
     * @protected
     * @since 4.9.015 (2010-04-19)
     */
    protected function _dooverline($x, $y, $txt) {
        $w = $this->GetStringWidth($txt);
        return $this->_dooverlinew($x, $y, $w);
    }

    /**
     * Overline for rectangular text area.
     * @param $x (int) X coordinate
     * @param $y (int) Y coordinate
     * @param $w (int) width to overline
     * @protected
     * @since 4.9.015 (2010-04-19)
     */
    protected function _dooverlinew($x, $y, $w) {
        $linew = - $this->CurrentFont['ut'] / 1000 * $this->FontSizePt;
        return sprintf('%F %F %F %F re f', $x * $this->k, (($this->h - $y + $this->FontAscent) * $this->k) - $linew, $w * $this->k, $linew);

    }

    /**
     * Format a data string for meta information
     * @param $s (string) data string to escape.
     * @param $n (int) object ID
     * @return string escaped string.
     * @protected
     */
    protected function _datastring($s, $n=0) {
        if ($n == 0) {
            $n = $this->n;
        }
        $s = $this->_encrypt_data($n, $s);
        return '('. TCPDF_STATIC::_escape($s).')';
    }

    /**
     * Set the document creation timestamp
     * @param $time (mixed) Document creation timestamp in seconds or date-time string.
     * @public
     * @since 5.9.152 (2012-03-23)
     */
    public function setDocCreationTimestamp($time) {
        if (is_string($time)) {
            $time = TCPDF_STATIC::getTimestamp($time);
        }
        $this->doc_creation_timestamp = intval($time);
    }

    /**
     * Set the document modification timestamp
     * @param $time (mixed) Document modification timestamp in seconds or date-time string.
     * @public
     * @since 5.9.152 (2012-03-23)
     */
    public function setDocModificationTimestamp($time) {
        if (is_string($time)) {
            $time = TCPDF_STATIC::getTimestamp($time);
        }
        $this->doc_modification_timestamp = intval($time);
    }

    /**
     * Returns document creation timestamp in seconds.
     * @return (int) Creation timestamp in seconds.
     * @public
     * @since 5.9.152 (2012-03-23)
     */
    public function getDocCreationTimestamp() {
        return $this->doc_creation_timestamp;
    }

    /**
     * Returns document modification timestamp in seconds.
     * @return (int) Modfication timestamp in seconds.
     * @public
     * @since 5.9.152 (2012-03-23)
     */
    public function getDocModificationTimestamp() {
        return $this->doc_modification_timestamp;
    }

    /**
     * Returns a formatted date for meta information
     * @param $n (int) Object ID.
     * @param $timestamp (int) Timestamp to convert.
     * @return string escaped date string.
     * @protected
     * @since 4.6.028 (2009-08-25)
     */
    protected function _datestring($n=0, $timestamp=0) {
        if ((empty($timestamp)) OR ($timestamp < 0)) {
            $timestamp = $this->doc_creation_timestamp;
        }
        return $this->_datastring('D:'.TCPDF_STATIC::getFormattedDate($timestamp), $n);
    }

    /**
     * Format a text string for meta information
     * @param $s (string) string to escape.
     * @param $n (int) object ID
     * @return string escaped string.
     * @protected
     */
    protected function _textstring($s, $n=0) {
        if ($this->isunicode) {
            //Convert string to UTF-16BE
            $s = TCPDF_FONTS::UTF8ToUTF16BE($s, true, $this->isunicode, $this->CurrentFont);
        }
        return $this->_datastring($s, $n);
    }

    /**
     * get raw output stream.
     * @param $s (string) string to output.
     * @param $n (int) object reference for encryption mode
     * @protected
     * @author Nicola Asuni
     * @since 5.5.000 (2010-06-22)
     */
    protected function _getrawstream($s, $n=0) {
        if ($n <= 0) {
            // default to current object
            $n = $this->n;
        }
        return $this->_encrypt_data($n, $s);
    }

    /**
     * Output a string to the document.
     * @param $s (string) string to output.
     * @protected
     */
    protected function _out($s) {
        if ($this->state == 2) {
            if ($this->inxobj) {
                // we are inside an XObject template
                $this->xobjects[$this->xobjid]['outdata'] .= $s."\n";
            } elseif ((!$this->InFooter) AND isset($this->footerlen[$this->page]) AND ($this->footerlen[$this->page] > 0)) {
                // puts data before page footer
                $pagebuff = $this->getPageBuffer($this->page);
                $page = substr($pagebuff, 0, -$this->footerlen[$this->page]);
                $footer = substr($pagebuff, -$this->footerlen[$this->page]);
                $this->setPageBuffer($this->page, $page.$s."\n".$footer);
                // update footer position
                $this->footerpos[$this->page] += strlen($s."\n");
            } else {
                // set page data
                $this->setPageBuffer($this->page, $s."\n", true);
            }
        } elseif ($this->state > 0) {
            // set general data
            $this->setBuffer($s."\n");
        }
    }

    /**
     * Set header font.
     * @param $font (array) Array describing the basic font parameters: (family, style, size).
     * @public
     * @since 1.1
     */
    public function setHeaderFont($font) {
        $this->header_font = $font;
    }

    /**
     * Get header font.
     * @return array() Array describing the basic font parameters: (family, style, size).
     * @public
     * @since 4.0.012 (2008-07-24)
     */
    public function getHeaderFont() {
        return $this->header_font;
    }

    /**
     * Set footer font.
     * @param $font (array) Array describing the basic font parameters: (family, style, size).
     * @public
     * @since 1.1
     */
    public function setFooterFont($font) {
        $this->footer_font = $font;
    }

    /**
     * Get Footer font.
     * @return array() Array describing the basic font parameters: (family, style, size).
     * @public
     * @since 4.0.012 (2008-07-24)
     */
    public function getFooterFont() {
        return $this->footer_font;
    }

    /**
     * Set language array.
     * @param $language (array)
     * @public
     * @since 1.1
     */
    public function setLanguageArray($language) {
        $this->l = $language;
        if (isset($this->l['a_meta_dir'])) {
            $this->rtl = $this->l['a_meta_dir']=='rtl' ? true : false;
        } else {
            $this->rtl = false;
        }
    }

    /**
     * Returns the PDF data.
     * @public
     */
    public function getPDFData() {
        if ($this->state < 3) {
            $this->Close();
        }
        return $this->buffer;
    }

    /**
     * Output anchor link.
     * @param $url (string) link URL or internal link (i.e.: &lt;a href="#23,4.5"&gt;link to page 23 at 4.5 Y position&lt;/a&gt;)
     * @param $name (string) link name
     * @param $fill (boolean) Indicates if the cell background must be painted (true) or transparent (false).
     * @param $firstline (boolean) if true prints only the first line and return the remaining string.
     * @param $color (array) array of RGB text color
     * @param $style (string) font style (U, D, B, I)
     * @param $firstblock (boolean) if true the string is the starting of a line.
     * @return the number of cells used or the remaining text if $firstline = true;
     * @public
     */
    public function addHtmlLink($url, $name, $fill=false, $firstline=false, $color='', $style=-1, $firstblock=false) {
        if (isset($url[1]) AND ($url[0] == '#') AND is_numeric($url[1])) {
            // convert url to internal link
            $lnkdata = explode(',', $url);
            if (isset($lnkdata[0]) ) {
                $page = substr($lnkdata[0], 1);
                if (isset($lnkdata[1]) AND (strlen($lnkdata[1]) > 0)) {
                    $lnky = floatval($lnkdata[1]);
                } else {
                    $lnky = 0;
                }
                $url = $this->AddLink();
                $this->SetLink($url, $lnky, $page);
            }
        }
        // store current settings
        $prevcolor = $this->fgcolor;
        $prevstyle = $this->FontStyle;
        if (empty($color)) {
            $this->SetTextColorArray($this->htmlLinkColorArray);
        } else {
            $this->SetTextColorArray($color);
        }
        if ($style == -1) {
            $this->SetFont('', $this->FontStyle.$this->htmlLinkFontStyle);
        } else {
            $this->SetFont('', $this->FontStyle.$style);
        }
        $ret = $this->Write($this->lasth, $name, $url, $fill, '', false, 0, $firstline, $firstblock, 0);
        // restore settings
        $this->SetFont('', $prevstyle);
        $this->SetTextColorArray($prevcolor);
        return $ret;
    }

    /**
     * Converts pixels to User's Units.
     * @param $px (int) pixels
     * @return float value in user's unit
     * @public
     * @see setImageScale(), getImageScale()
     */
    public function pixelsToUnits($px) {
        return ($px / ($this->imgscale * $this->k));
    }

    /**
     * Reverse function for htmlentities.
     * Convert entities in UTF-8.
     * @param $text_to_convert (string) Text to convert.
     * @return string converted text string
     * @public
     */
    public function unhtmlentities($text_to_convert) {
        return @html_entity_decode($text_to_convert, ENT_QUOTES, $this->encoding);
    }

    // ENCRYPTION METHODS ----------------------------------

    /**
     * Compute encryption key depending on object number where the encrypted data is stored.
     * This is used for all strings and streams without crypt filter specifier.
     * @param $n (int) object number
     * @return int object key
     * @protected
     * @author Nicola Asuni
     * @since 2.0.000 (2008-01-02)
     */
    protected function _objectkey($n) {
        $objkey = $this->encryptdata['key'].pack('VXxx', $n);
        if ($this->encryptdata['mode'] == 2) { // AES-128
            // AES padding
            $objkey .= "\x73\x41\x6C\x54"; // sAlT
        }
        $objkey = substr(TCPDF_STATIC::_md5_16($objkey), 0, (($this->encryptdata['Length'] / 8) + 5));
        $objkey = substr($objkey, 0, 16);
        return $objkey;
    }

    /**
     * Encrypt the input string.
     * @param $n (int) object number
     * @param $s (string) data string to encrypt
     * @return encrypted string
     * @protected
     * @author Nicola Asuni
     * @since 5.0.005 (2010-05-11)
     */
    protected function _encrypt_data($n, $s) {
        if (!$this->encrypted) {
            return $s;
        }
        switch ($this->encryptdata['mode']) {
            case 0:   // RC4-40
            case 1: { // RC4-128
                $s = TCPDF_STATIC::_RC4($this->_objectkey($n), $s, $this->last_enc_key, $this->last_enc_key_c);
                break;
            }
            case 2: { // AES-128
                $s = TCPDF_STATIC::_AES($this->_objectkey($n), $s);
                break;
            }
            case 3: { // AES-256
                $s = TCPDF_STATIC::_AES($this->encryptdata['key'], $s);
                break;
            }
        }
        return $s;
    }

    /**
     * Put encryption on PDF document.
     * @protected
     * @author Nicola Asuni
     * @since 2.0.000 (2008-01-02)
     */
    protected function _putencryption() {
        if (!$this->encrypted) {
            return;
        }
        $this->encryptdata['objid'] = $this->_newobj();
        $out = '<<';
        if (!isset($this->encryptdata['Filter']) OR empty($this->encryptdata['Filter'])) {
            $this->encryptdata['Filter'] = 'Standard';
        }
        $out .= ' /Filter /'.$this->encryptdata['Filter'];
        if (isset($this->encryptdata['SubFilter']) AND !empty($this->encryptdata['SubFilter'])) {
            $out .= ' /SubFilter /'.$this->encryptdata['SubFilter'];
        }
        if (!isset($this->encryptdata['V']) OR empty($this->encryptdata['V'])) {
            $this->encryptdata['V'] = 1;
        }
        // V is a code specifying the algorithm to be used in encrypting and decrypting the document
        $out .= ' /V '.$this->encryptdata['V'];
        if (isset($this->encryptdata['Length']) AND !empty($this->encryptdata['Length'])) {
            // The length of the encryption key, in bits. The value shall be a multiple of 8, in the range 40 to 256
            $out .= ' /Length '.$this->encryptdata['Length'];
        } else {
            $out .= ' /Length 40';
        }
        if ($this->encryptdata['V'] >= 4) {
            if (!isset($this->encryptdata['StmF']) OR empty($this->encryptdata['StmF'])) {
                $this->encryptdata['StmF'] = 'Identity';
            }
            if (!isset($this->encryptdata['StrF']) OR empty($this->encryptdata['StrF'])) {
                // The name of the crypt filter that shall be used when decrypting all strings in the document.
                $this->encryptdata['StrF'] = 'Identity';
            }
            // A dictionary whose keys shall be crypt filter names and whose values shall be the corresponding crypt filter dictionaries.
            if (isset($this->encryptdata['CF']) AND !empty($this->encryptdata['CF'])) {
                $out .= ' /CF <<';
                $out .= ' /'.$this->encryptdata['StmF'].' <<';
                $out .= ' /Type /CryptFilter';
                if (isset($this->encryptdata['CF']['CFM']) AND !empty($this->encryptdata['CF']['CFM'])) {
                    // The method used
                    $out .= ' /CFM /'.$this->encryptdata['CF']['CFM'];
                    if ($this->encryptdata['pubkey']) {
                        $out .= ' /Recipients [';
                        foreach ($this->encryptdata['Recipients'] as $rec) {
                            $out .= ' <'.$rec.'>';
                        }
                        $out .= ' ]';
                        if (isset($this->encryptdata['CF']['EncryptMetadata']) AND (!$this->encryptdata['CF']['EncryptMetadata'])) {
                            $out .= ' /EncryptMetadata false';
                        } else {
                            $out .= ' /EncryptMetadata true';
                        }
                    }
                } else {
                    $out .= ' /CFM /None';
                }
                if (isset($this->encryptdata['CF']['AuthEvent']) AND !empty($this->encryptdata['CF']['AuthEvent'])) {
                    // The event to be used to trigger the authorization that is required to access encryption keys used by this filter.
                    $out .= ' /AuthEvent /'.$this->encryptdata['CF']['AuthEvent'];
                } else {
                    $out .= ' /AuthEvent /DocOpen';
                }
                if (isset($this->encryptdata['CF']['Length']) AND !empty($this->encryptdata['CF']['Length'])) {
                    // The bit length of the encryption key.
                    $out .= ' /Length '.$this->encryptdata['CF']['Length'];
                }
                $out .= ' >> >>';
            }
            // The name of the crypt filter that shall be used by default when decrypting streams.
            $out .= ' /StmF /'.$this->encryptdata['StmF'];
            // The name of the crypt filter that shall be used when decrypting all strings in the document.
            $out .= ' /StrF /'.$this->encryptdata['StrF'];
            if (isset($this->encryptdata['EFF']) AND !empty($this->encryptdata['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.
                $out .= ' /EFF /'.$this->encryptdata[''];
            }
        }
        // Additional encryption dictionary entries for the standard security handler
        if ($this->encryptdata['pubkey']) {
            if (($this->encryptdata['V'] < 4) AND isset($this->encryptdata['Recipients']) AND !empty($this->encryptdata['Recipients'])) {
                $out .= ' /Recipients [';
                foreach ($this->encryptdata['Recipients'] as $rec) {
                    $out .= ' <'.$rec.'>';
                }
                $out .= ' ]';
            }
        } else {
            $out .= ' /R';
            if ($this->encryptdata['V'] == 5) { // AES-256
                $out .= ' 5';
                $out .= ' /OE ('.TCPDF_STATIC::_escape($this->encryptdata['OE']).')';
                $out .= ' /UE ('.TCPDF_STATIC::_escape($this->encryptdata['UE']).')';
                $out .= ' /Perms ('.TCPDF_STATIC::_escape($this->encryptdata['perms']).')';
            } elseif ($this->encryptdata['V'] == 4) { // AES-128
                $out .= ' 4';
            } elseif ($this->encryptdata['V'] < 2) { // RC-40
                $out .= ' 2';
            } else { // RC-128
                $out .= ' 3';
            }
            $out .= ' /O ('.TCPDF_STATIC::_escape($this->encryptdata['O']).')';
            $out .= ' /U ('.TCPDF_STATIC::_escape($this->encryptdata['U']).')';
            $out .= ' /P '.$this->encryptdata['P'];
            if (isset($this->encryptdata['EncryptMetadata']) AND (!$this->encryptdata['EncryptMetadata'])) {
                $out .= ' /EncryptMetadata false';
            } else {
                $out .= ' /EncryptMetadata true';
            }
        }
        $out .= ' >>';
        $out .= "\n".'endobj';
        $this->_out($out);
    }

    /**
     * Compute U value (used for encryption)
     * @return string U value
     * @protected
     * @since 2.0.000 (2008-01-02)
     * @author Nicola Asuni
     */
    protected function _Uvalue() {
        if ($this->encryptdata['mode'] == 0) { // RC4-40
            return TCPDF_STATIC::_RC4($this->encryptdata['key'], TCPDF_STATIC::$enc_padding, $this->last_enc_key, $this->last_enc_key_c);
        } elseif ($this->encryptdata['mode'] < 3) { // RC4-128, AES-128
            $tmp = TCPDF_STATIC::_md5_16(TCPDF_STATIC::$enc_padding.$this->encryptdata['fileid']);
            $enc = TCPDF_STATIC::_RC4($this->encryptdata['key'], $tmp, $this->last_enc_key, $this->last_enc_key_c);
            $len = strlen($tmp);
            for ($i = 1; $i <= 19; ++$i) {
                $ek = '';
                for ($j = 0; $j < $len; ++$j) {
                    $ek .= chr(ord($this->encryptdata['key'][$j]) ^ $i);
                }
                $enc = TCPDF_STATIC::_RC4($ek, $enc, $this->last_enc_key, $this->last_enc_key_c);
            }
            $enc .= str_repeat("\x00", 16);
            return substr($enc, 0, 32);
        } elseif ($this->encryptdata['mode'] == 3) { // AES-256
            $seed = TCPDF_STATIC::_md5_16(TCPDF_STATIC::getRandomSeed());
            // User Validation Salt
            $this->encryptdata['UVS'] = substr($seed, 0, 8);
            // User Key Salt
            $this->encryptdata['UKS'] = substr($seed, 8, 16);
            return hash('sha256', $this->encryptdata['user_password'].$this->encryptdata['UVS'], true).$this->encryptdata['UVS'].$this->encryptdata['UKS'];
        }
    }

    /**
     * Compute UE value (used for encryption)
     * @return string UE value
     * @protected
     * @since 5.9.006 (2010-10-19)
     * @author Nicola Asuni
     */
    protected function _UEvalue() {
        $hashkey = hash('sha256', $this->encryptdata['user_password'].$this->encryptdata['UKS'], true);
        return TCPDF_STATIC::_AESnopad($hashkey, $this->encryptdata['key']);
    }

    /**
     * Compute O value (used for encryption)
     * @return string O value
     * @protected
     * @since 2.0.000 (2008-01-02)
     * @author Nicola Asuni
     */
    protected function _Ovalue() {
        if ($this->encryptdata['mode'] < 3) { // RC4-40, RC4-128, AES-128
            $tmp = TCPDF_STATIC::_md5_16($this->encryptdata['owner_password']);
            if ($this->encryptdata['mode'] > 0) {
                for ($i = 0; $i < 50; ++$i) {
                    $tmp = TCPDF_STATIC::_md5_16($tmp);
                }
            }
            $owner_key = substr($tmp, 0, ($this->encryptdata['Length'] / 8));
            $enc = TCPDF_STATIC::_RC4($owner_key, $this->encryptdata['user_password'], $this->last_enc_key, $this->last_enc_key_c);
            if ($this->encryptdata['mode'] > 0) {
                $len = strlen($owner_key);
                for ($i = 1; $i <= 19; ++$i) {
                    $ek = '';
                    for ($j = 0; $j < $len; ++$j) {
                        $ek .= chr(ord($owner_key[$j]) ^ $i);
                    }
                    $enc = TCPDF_STATIC::_RC4($ek, $enc, $this->last_enc_key, $this->last_enc_key_c);
                }
            }
            return $enc;
        } elseif ($this->encryptdata['mode'] == 3) { // AES-256
            $seed = TCPDF_STATIC::_md5_16(TCPDF_STATIC::getRandomSeed());
            // Owner Validation Salt
            $this->encryptdata['OVS'] = substr($seed, 0, 8);
            // Owner Key Salt
            $this->encryptdata['OKS'] = substr($seed, 8, 16);
            return hash('sha256', $this->encryptdata['owner_password'].$this->encryptdata['OVS'].$this->encryptdata['U'], true).$this->encryptdata['OVS'].$this->encryptdata['OKS'];
        }
    }

    /**
     * Compute OE value (used for encryption)
     * @return string OE value
     * @protected
     * @since 5.9.006 (2010-10-19)
     * @author Nicola Asuni
     */
    protected function _OEvalue() {
        $hashkey = hash('sha256', $this->encryptdata['owner_password'].$this->encryptdata['OKS'].$this->encryptdata['U'], true);
        return TCPDF_STATIC::_AESnopad($hashkey, $this->encryptdata['key']);
    }

    /**
     * Convert password for AES-256 encryption mode
     * @param $password (string) password
     * @return string password
     * @protected
     * @since 5.9.006 (2010-10-19)
     * @author Nicola Asuni
     */
    protected function _fixAES256Password($password) {
        $psw = ''; // password to be returned
        $psw_array = TCPDF_FONTS::utf8Bidi(TCPDF_FONTS::UTF8StringToArray($password, $this->isunicode, $this->CurrentFont), $password, $this->rtl, $this->isunicode, $this->CurrentFont);
        foreach ($psw_array as $c) {
            $psw .= TCPDF_FONTS::unichr($c, $this->isunicode);
        }
        return substr($psw, 0, 127);
    }

    /**
     * Compute encryption key
     * @protected
     * @since 2.0.000 (2008-01-02)
     * @author Nicola Asuni
     */
    protected function _generateencryptionkey() {
        $keybytelen = ($this->encryptdata['Length'] / 8);
        if (!$this->encryptdata['pubkey']) { // standard mode
            if ($this->encryptdata['mode'] == 3) { // AES-256
                // generate 256 bit random key
                $this->encryptdata['key'] = substr(hash('sha256', TCPDF_STATIC::getRandomSeed(), true), 0, $keybytelen);
                // truncate passwords
                $this->encryptdata['user_password'] = $this->_fixAES256Password($this->encryptdata['user_password']);
                $this->encryptdata['owner_password'] = $this->_fixAES256Password($this->encryptdata['owner_password']);
                // Compute U value
                $this->encryptdata['U'] = $this->_Uvalue();
                // Compute UE value
                $this->encryptdata['UE'] = $this->_UEvalue();
                // Compute O value
                $this->encryptdata['O'] = $this->_Ovalue();
                // Compute OE value
                $this->encryptdata['OE'] = $this->_OEvalue();
                // Compute P value
                $this->encryptdata['P'] = $this->encryptdata['protection'];
                // Computing the encryption dictionary's Perms (permissions) value
                $perms = TCPDF_STATIC::getEncPermissionsString($this->encryptdata['protection']); // bytes 0-3
                $perms .= chr(255).chr(255).chr(255).chr(255); // bytes 4-7
                if (isset($this->encryptdata['CF']['EncryptMetadata']) AND (!$this->encryptdata['CF']['EncryptMetadata'])) { // byte 8
                    $perms .= 'F';
                } else {
                    $perms .= 'T';
                }
                $perms .= 'adb'; // bytes 9-11
                $perms .= 'nick'; // bytes 12-15
                $this->encryptdata['perms'] = TCPDF_STATIC::_AESnopad($this->encryptdata['key'], $perms);
            } else { // RC4-40, RC4-128, AES-128
                // Pad passwords
                $this->encryptdata['user_password'] = substr($this->encryptdata['user_password'].TCPDF_STATIC::$enc_padding, 0, 32);
                $this->encryptdata['owner_password'] = substr($this->encryptdata['owner_password'].TCPDF_STATIC::$enc_padding, 0, 32);
                // Compute O value
                $this->encryptdata['O'] = $this->_Ovalue();
                // get default permissions (reverse byte order)
                $permissions = TCPDF_STATIC::getEncPermissionsString($this->encryptdata['protection']);
                // Compute encryption key
                $tmp = TCPDF_STATIC::_md5_16($this->encryptdata['user_password'].$this->encryptdata['O'].$permissions.$this->encryptdata['fileid']);
                if ($this->encryptdata['mode'] > 0) {
                    for ($i = 0; $i < 50; ++$i) {
                        $tmp = TCPDF_STATIC::_md5_16(substr($tmp, 0, $keybytelen));
                    }
                }
                $this->encryptdata['key'] = substr($tmp, 0, $keybytelen);
                // Compute U value
                $this->encryptdata['U'] = $this->_Uvalue();
                // Compute P value
                $this->encryptdata['P'] = $this->encryptdata['protection'];
            }
        } else { // Public-Key mode
            // random 20-byte seed
            $seed = sha1(TCPDF_STATIC::getRandomSeed(), true);
            $recipient_bytes = '';
            foreach ($this->encryptdata['pubkeys'] as $pubkey) {
                // for each public certificate
                if (isset($pubkey['p'])) {
                    $pkprotection = TCPDF_STATIC::getUserPermissionCode($pubkey['p'], $this->encryptdata['mode']);
                } else {
                    $pkprotection = $this->encryptdata['protection'];
                }
                // get default permissions (reverse byte order)
                $pkpermissions = TCPDF_STATIC::getEncPermissionsString($pkprotection);
                // envelope data
                $envelope = $seed.$pkpermissions;
                // write the envelope data to a temporary file
                $tempkeyfile = TCPDF_STATIC::getObjFilename('key', $this->file_id);
                $f = TCPDF_STATIC::fopenLocal($tempkeyfile, 'wb');
                if (!$f) {
                    $this->Error('Unable to create temporary key file: '.$tempkeyfile);
                }
                $envelope_length = strlen($envelope);
                fwrite($f, $envelope, $envelope_length);
                fclose($f);
                $tempencfile = TCPDF_STATIC::getObjFilename('enc', $this->file_id);
                if (!openssl_pkcs7_encrypt($tempkeyfile, $tempencfile, $pubkey['c'], array(), PKCS7_BINARY | PKCS7_DETACHED)) {
                    $this->Error('Unable to encrypt the file: '.$tempkeyfile);
                }
                // read encryption signature
                $signature = file_get_contents($tempencfile, false, null, $envelope_length);
                // extract signature
                $signature = substr($signature, strpos($signature, 'Content-Disposition'));
                $tmparr = explode("\n\n", $signature);
                $signature = trim($tmparr[1]);
                unset($tmparr);
                // decode signature
                $signature = base64_decode($signature);
                // convert signature to hex
                $hexsignature = current(unpack('H*', $signature));
                // store signature on recipients array
                $this->encryptdata['Recipients'][] = $hexsignature;
                // The bytes of each item in the Recipients array of PKCS#7 objects in the order in which they appear in the array
                $recipient_bytes .= $signature;
            }
            // calculate encryption key
            if ($this->encryptdata['mode'] == 3) { // AES-256
                $this->encryptdata['key'] = substr(hash('sha256', $seed.$recipient_bytes, true), 0, $keybytelen);
            } else { // RC4-40, RC4-128, AES-128
                $this->encryptdata['key'] = substr(sha1($seed.$recipient_bytes, true), 0, $keybytelen);
            }
        }
    }

    /**
     * Set document protection
     * Remark: the protection against modification is for people who have the full Acrobat product.
     * If you don't set any password, the document will open as usual. If you set a user password, the PDF viewer will ask for it before displaying the document. The master password, if different from the user one, can be used to get full access.
     * Note: protecting a document requires to encrypt it, which increases the processing time a lot. This can cause a PHP time-out in some cases, especially if the document contains images or fonts.
     * @param $permissions (Array) the set of permissions (specify the ones you want to block):<ul><li>print : Print the document;</li><li>modify : Modify the contents of the document by operations other than those controlled by 'fill-forms', 'extract' and 'assemble';</li><li>copy : Copy or otherwise extract text and graphics from the document;</li><li>annot-forms : Add or modify text annotations, fill in interactive form fields, and, if 'modify' is also set, create or modify interactive form fields (including signature fields);</li><li>fill-forms : Fill in existing interactive form fields (including signature fields), even if 'annot-forms' is not specified;</li><li>extract : Extract text and graphics (in support of accessibility to users with disabilities or for other purposes);</li><li>assemble : Assemble the document (insert, rotate, or delete pages and create bookmarks or thumbnail images), even if 'modify' is not set;</li><li>print-high : Print the document to a representation from which a faithful digital copy of the PDF content could be generated. When this is not set, printing is limited to a low-level representation of the appearance, possibly of degraded quality.</li><li>owner : (inverted logic - only for public-key) when set permits change of encryption and enables all other permissions.</li></ul>
     * @param $user_pass (String) user password. Empty by default.
     * @param $owner_pass (String) owner password. If not specified, a random value is used.
     * @param $mode (int) encryption strength: 0 = RC4 40 bit; 1 = RC4 128 bit; 2 = AES 128 bit; 3 = AES 256 bit.
     * @param $pubkeys (String) array of recipients containing public-key certificates ('c') and permissions ('p'). For example: array(array('c' => 'file://../examples/data/cert/tcpdf.crt', 'p' => array('print')))
     * @public
     * @since 2.0.000 (2008-01-02)
     * @author Nicola Asuni
     */
    public function SetProtection($permissions=array('print', 'modify', 'copy', 'annot-forms', 'fill-forms', 'extract', 'assemble', 'print-high'), $user_pass='', $owner_pass=null, $mode=0, $pubkeys=null) {
        if ($this->pdfa_mode) {
            // encryption is not allowed in PDF/A mode
            return;
        }
        $this->encryptdata['protection'] = TCPDF_STATIC::getUserPermissionCode($permissions, $mode);
        if (($pubkeys !== null) AND (is_array($pubkeys))) {
            // public-key mode
            $this->encryptdata['pubkeys'] = $pubkeys;
            if ($mode == 0) {
                // public-Key Security requires at least 128 bit
                $mode = 1;
            }
            if (!function_exists('openssl_pkcs7_encrypt')) {
                $this->Error('Public-Key Security requires openssl library.');
            }
            // Set Public-Key filter (available are: Entrust.PPKEF, Adobe.PPKLite, Adobe.PubSec)
            $this->encryptdata['pubkey'] = true;
            $this->encryptdata['Filter'] = 'Adobe.PubSec';
            $this->encryptdata['StmF'] = 'DefaultCryptFilter';
            $this->encryptdata['StrF'] = 'DefaultCryptFilter';
        } else {
            // standard mode (password mode)
            $this->encryptdata['pubkey'] = false;
            $this->encryptdata['Filter'] = 'Standard';
            $this->encryptdata['StmF'] = 'StdCF';
            $this->encryptdata['StrF'] = 'StdCF';
        }
        if ($mode > 1) { // AES
            if (!extension_loaded('openssl') && !extension_loaded('mcrypt')) {
                $this->Error('AES encryption requires openssl or mcrypt extension (http://www.php.net/manual/en/mcrypt.requirements.php).');
            }
            if (extension_loaded('openssl') && !in_array('aes-256-cbc', openssl_get_cipher_methods())) {
                $this->Error('AES encryption requires openssl/aes-256-cbc cypher.');
            }
            if (extension_loaded('mcrypt') && mcrypt_get_cipher_name(MCRYPT_RIJNDAEL_128) === false) {
                $this->Error('AES encryption requires MCRYPT_RIJNDAEL_128 cypher.');
            }
            if (($mode == 3) AND !function_exists('hash')) {
                // the Hash extension requires no external libraries and is enabled by default as of PHP 5.1.2.
                $this->Error('AES 256 encryption requires HASH Message Digest Framework (http://www.php.net/manual/en/book.hash.php).');
            }
        }
        if ($owner_pass === null) {
            $owner_pass = md5(TCPDF_STATIC::getRandomSeed());
        }
        $this->encryptdata['user_password'] = $user_pass;
        $this->encryptdata['owner_password'] = $owner_pass;
        $this->encryptdata['mode'] = $mode;
        switch ($mode) {
            case 0: { // RC4 40 bit
                $this->encryptdata['V'] = 1;
                $this->encryptdata['Length'] = 40;
                $this->encryptdata['CF']['CFM'] = 'V2';
                break;
            }
            case 1: { // RC4 128 bit
                $this->encryptdata['V'] = 2;
                $this->encryptdata['Length'] = 128;
                $this->encryptdata['CF']['CFM'] = 'V2';
                if ($this->encryptdata['pubkey']) {
                    $this->encryptdata['SubFilter'] = 'adbe.pkcs7.s4';
                    $this->encryptdata['Recipients'] = array();
                }
                break;
            }
            case 2: { // AES 128 bit
                $this->encryptdata['V'] = 4;
                $this->encryptdata['Length'] = 128;
                $this->encryptdata['CF']['CFM'] = 'AESV2';
                $this->encryptdata['CF']['Length'] = 128;
                if ($this->encryptdata['pubkey']) {
                    $this->encryptdata['SubFilter'] = 'adbe.pkcs7.s5';
                    $this->encryptdata['Recipients'] = array();
                }
                break;
            }
            case 3: { // AES 256 bit
                $this->encryptdata['V'] = 5;
                $this->encryptdata['Length'] = 256;
                $this->encryptdata['CF']['CFM'] = 'AESV3';
                $this->encryptdata['CF']['Length'] = 256;
                if ($this->encryptdata['pubkey']) {
                    $this->encryptdata['SubFilter'] = 'adbe.pkcs7.s5';
                    $this->encryptdata['Recipients'] = array();
                }
                break;
            }
        }
        $this->encrypted = true;
        $this->encryptdata['fileid'] = TCPDF_STATIC::convertHexStringToString($this->file_id);
        $this->_generateencryptionkey();
    }

    // END OF ENCRYPTION FUNCTIONS -------------------------

    // START TRANSFORMATIONS SECTION -----------------------

    /**
     * Starts a 2D tranformation saving current graphic state.
     * This function must be called before scaling, mirroring, translation, rotation and skewing.
     * Use StartTransform() before, and StopTransform() after the transformations to restore the normal behavior.
     * @public
     * @since 2.1.000 (2008-01-07)
     * @see StartTransform(), StopTransform()
     */
    public function StartTransform() {
        if ($this->state != 2) {
            return;
        }
        $this->_outSaveGraphicsState();
        if ($this->inxobj) {
            // we are inside an XObject template
            $this->xobjects[$this->xobjid]['transfmrk'][] = strlen($this->xobjects[$this->xobjid]['outdata']);
        } else {
            $this->transfmrk[$this->page][] = $this->pagelen[$this->page];
        }
        ++$this->transfmatrix_key;
        $this->transfmatrix[$this->transfmatrix_key] = array();
    }

    /**
     * Stops a 2D tranformation restoring previous graphic state.
     * This function must be called after scaling, mirroring, translation, rotation and skewing.
     * Use StartTransform() before, and StopTransform() after the transformations to restore the normal behavior.
     * @public
     * @since 2.1.000 (2008-01-07)
     * @see StartTransform(), StopTransform()
     */
    public function StopTransform() {
        if ($this->state != 2) {
            return;
        }
        $this->_outRestoreGraphicsState();
        if (isset($this->transfmatrix[$this->transfmatrix_key])) {
            array_pop($this->transfmatrix[$this->transfmatrix_key]);
            --$this->transfmatrix_key;
        }
        if ($this->inxobj) {
            // we are inside an XObject template
            array_pop($this->xobjects[$this->xobjid]['transfmrk']);
        } else {
            array_pop($this->transfmrk[$this->page]);
        }
    }
    /**
     * Horizontal Scaling.
     * @param $s_x (float) scaling factor for width as percent. 0 is not allowed.
     * @param $x (int) abscissa of the scaling center. Default is current x position
     * @param $y (int) ordinate of the scaling center. Default is current y position
     * @public
     * @since 2.1.000 (2008-01-07)
     * @see StartTransform(), StopTransform()
     */
    public function ScaleX($s_x, $x='', $y='') {
        $this->Scale($s_x, 100, $x, $y);
    }

    /**
     * Vertical Scaling.
     * @param $s_y (float) scaling factor for height as percent. 0 is not allowed.
     * @param $x (int) abscissa of the scaling center. Default is current x position
     * @param $y (int) ordinate of the scaling center. Default is current y position
     * @public
     * @since 2.1.000 (2008-01-07)
     * @see StartTransform(), StopTransform()
     */
    public function ScaleY($s_y, $x='', $y='') {
        $this->Scale(100, $s_y, $x, $y);
    }

    /**
     * Vertical and horizontal proportional Scaling.
     * @param $s (float) scaling factor for width and height as percent. 0 is not allowed.
     * @param $x (int) abscissa of the scaling center. Default is current x position
     * @param $y (int) ordinate of the scaling center. Default is current y position
     * @public
     * @since 2.1.000 (2008-01-07)
     * @see StartTransform(), StopTransform()
     */
    public function ScaleXY($s, $x='', $y='') {
        $this->Scale($s, $s, $x, $y);
    }

    /**
     * Vertical and horizontal non-proportional Scaling.
     * @param $s_x (float) scaling factor for width as percent. 0 is not allowed.
     * @param $s_y (float) scaling factor for height as percent. 0 is not allowed.
     * @param $x (int) abscissa of the scaling center. Default is current x position
     * @param $y (int) ordinate of the scaling center. Default is current y position
     * @public
     * @since 2.1.000 (2008-01-07)
     * @see StartTransform(), StopTransform()
     */
    public function Scale($s_x, $s_y, $x='', $y='') {
        if ($x === '') {
            $x = $this->x;
        }
        if ($y === '') {
            $y = $this->y;
        }
        if (($s_x == 0) OR ($s_y == 0)) {
            $this->Error('Please do not use values equal to zero for scaling');
        }
        $y = ($this->h - $y) * $this->k;
        $x *= $this->k;
        //calculate elements of transformation matrix
        $s_x /= 100;
        $s_y /= 100;
        $tm = array();
        $tm[0] = $s_x;
        $tm[1] = 0;
        $tm[2] = 0;
        $tm[3] = $s_y;
        $tm[4] = $x * (1 - $s_x);
        $tm[5] = $y * (1 - $s_y);
        //scale the coordinate system
        $this->Transform($tm);
    }

    /**
     * Horizontal Mirroring.
     * @param $x (int) abscissa of the point. Default is current x position
     * @public
     * @since 2.1.000 (2008-01-07)
     * @see StartTransform(), StopTransform()
     */
    public function MirrorH($x='') {
        $this->Scale(-100, 100, $x);
    }

    /**
     * Verical Mirroring.
     * @param $y (int) ordinate of the point. Default is current y position
     * @public
     * @since 2.1.000 (2008-01-07)
     * @see StartTransform(), StopTransform()
     */
    public function MirrorV($y='') {
        $this->Scale(100, -100, '', $y);
    }

    /**
     * Point reflection mirroring.
     * @param $x (int) abscissa of the point. Default is current x position
     * @param $y (int) ordinate of the point. Default is current y position
     * @public
     * @since 2.1.000 (2008-01-07)
     * @see StartTransform(), StopTransform()
     */
    public function MirrorP($x='',$y='') {
        $this->Scale(-100, -100, $x, $y);
    }

    /**
     * Reflection against a straight line through point (x, y) with the gradient angle (angle).
     * @param $angle (float) gradient angle of the straight line. Default is 0 (horizontal line).
     * @param $x (int) abscissa of the point. Default is current x position
     * @param $y (int) ordinate of the point. Default is current y position
     * @public
     * @since 2.1.000 (2008-01-07)
     * @see StartTransform(), StopTransform()
     */
    public function MirrorL($angle=0, $x='',$y='') {
        $this->Scale(-100, 100, $x, $y);
        $this->Rotate(-2*($angle-90), $x, $y);
    }

    /**
     * Translate graphic object horizontally.
     * @param $t_x (int) movement to the right (or left for RTL)
     * @public
     * @since 2.1.000 (2008-01-07)
     * @see StartTransform(), StopTransform()
     */
    public function TranslateX($t_x) {
        $this->Translate($t_x, 0);
    }

    /**
     * Translate graphic object vertically.
     * @param $t_y (int) movement to the bottom
     * @public
     * @since 2.1.000 (2008-01-07)
     * @see StartTransform(), StopTransform()
     */
    public function TranslateY($t_y) {
        $this->Translate(0, $t_y);
    }

    /**
     * Translate graphic object horizontally and vertically.
     * @param $t_x (int) movement to the right
     * @param $t_y (int) movement to the bottom
     * @public
     * @since 2.1.000 (2008-01-07)
     * @see StartTransform(), StopTransform()
     */
    public function Translate($t_x, $t_y) {
        //calculate elements of transformation matrix
        $tm = array();
        $tm[0] = 1;
        $tm[1] = 0;
        $tm[2] = 0;
        $tm[3] = 1;
        $tm[4] = $t_x * $this->k;
        $tm[5] = -$t_y * $this->k;
        //translate the coordinate system
        $this->Transform($tm);
    }

    /**
     * Rotate object.
     * @param $angle (float) angle in degrees for counter-clockwise rotation
     * @param $x (int) abscissa of the rotation center. Default is current x position
     * @param $y (int) ordinate of the rotation center. Default is current y position
     * @public
     * @since 2.1.000 (2008-01-07)
     * @see StartTransform(), StopTransform()
     */
    public function Rotate($angle, $x='', $y='') {
        if ($x === '') {
            $x = $this->x;
        }
        if ($y === '') {
            $y = $this->y;
        }
        $y = ($this->h - $y) * $this->k;
        $x *= $this->k;
        //calculate elements of transformation matrix
        $tm = array();
        $tm[0] = cos(deg2rad($angle));
        $tm[1] = sin(deg2rad($angle));
        $tm[2] = -$tm[1];
        $tm[3] = $tm[0];
        $tm[4] = $x + ($tm[1] * $y) - ($tm[0] * $x);
        $tm[5] = $y - ($tm[0] * $y) - ($tm[1] * $x);
        //rotate the coordinate system around ($x,$y)
        $this->Transform($tm);
    }

    /**
     * Skew horizontally.
     * @param $angle_x (float) angle in degrees between -90 (skew to the left) and 90 (skew to the right)
     * @param $x (int) abscissa of the skewing center. default is current x position
     * @param $y (int) ordinate of the skewing center. default is current y position
     * @public
     * @since 2.1.000 (2008-01-07)
     * @see StartTransform(), StopTransform()
     */
    public function SkewX($angle_x, $x='', $y='') {
        $this->Skew($angle_x, 0, $x, $y);
    }

    /**
     * Skew vertically.
     * @param $angle_y (float) angle in degrees between -90 (skew to the bottom) and 90 (skew to the top)
     * @param $x (int) abscissa of the skewing center. default is current x position
     * @param $y (int) ordinate of the skewing center. default is current y position
     * @public
     * @since 2.1.000 (2008-01-07)
     * @see StartTransform(), StopTransform()
     */
    public function SkewY($angle_y, $x='', $y='') {
        $this->Skew(0, $angle_y, $x, $y);
    }

    /**
     * Skew.
     * @param $angle_x (float) angle in degrees between -90 (skew to the left) and 90 (skew to the right)
     * @param $angle_y (float) angle in degrees between -90 (skew to the bottom) and 90 (skew to the top)
     * @param $x (int) abscissa of the skewing center. default is current x position
     * @param $y (int) ordinate of the skewing center. default is current y position
     * @public
     * @since 2.1.000 (2008-01-07)
     * @see StartTransform(), StopTransform()
     */
    public function Skew($angle_x, $angle_y, $x='', $y='') {
        if ($x === '') {
            $x = $this->x;
        }
        if ($y === '') {
            $y = $this->y;
        }
        if (($angle_x <= -90) OR ($angle_x >= 90) OR ($angle_y <= -90) OR ($angle_y >= 90)) {
            $this->Error('Please use values between -90 and +90 degrees for Skewing.');
        }
        $x *= $this->k;
        $y = ($this->h - $y) * $this->k;
        //calculate elements of transformation matrix
        $tm = array();
        $tm[0] = 1;
        $tm[1] = tan(deg2rad($angle_y));
        $tm[2] = tan(deg2rad($angle_x));
        $tm[3] = 1;
        $tm[4] = -$tm[2] * $y;
        $tm[5] = -$tm[1] * $x;
        //skew the coordinate system
        $this->Transform($tm);
    }

    /**
     * Apply graphic transformations.
     * @param $tm (array) transformation matrix
     * @protected
     * @since 2.1.000 (2008-01-07)
     * @see StartTransform(), StopTransform()
     */
    protected function Transform($tm) {
        if ($this->state != 2) {
            return;
        }
        $this->_out(sprintf('%F %F %F %F %F %F cm', $tm[0], $tm[1], $tm[2], $tm[3], $tm[4], $tm[5]));
        // add tranformation matrix
        $this->transfmatrix[$this->transfmatrix_key][] = array('a' => $tm[0], 'b' => $tm[1], 'c' => $tm[2], 'd' => $tm[3], 'e' => $tm[4], 'f' => $tm[5]);
        // update transformation mark
        if ($this->inxobj) {
            // we are inside an XObject template
            if (end($this->xobjects[$this->xobjid]['transfmrk']) !== false) {
                $key = key($this->xobjects[$this->xobjid]['transfmrk']);
                $this->xobjects[$this->xobjid]['transfmrk'][$key] = strlen($this->xobjects[$this->xobjid]['outdata']);
            }
        } elseif (end($this->transfmrk[$this->page]) !== false) {
            $key = key($this->transfmrk[$this->page]);
            $this->transfmrk[$this->page][$key] = $this->pagelen[$this->page];
        }
    }

    // END TRANSFORMATIONS SECTION -------------------------

    // START GRAPHIC FUNCTIONS SECTION ---------------------
    // The following section is based on the code provided by David Hernandez Sanz

    /**
     * Defines the line width. By default, the value equals 0.2 mm. The method can be called before the first page is created and the value is retained from page to page.
     * @param $width (float) The width.
     * @public
     * @since 1.0
     * @see Line(), Rect(), Cell(), MultiCell()
     */
    public function SetLineWidth($width) {
        //Set line width
        $this->LineWidth = $width;
        $this->linestyleWidth = sprintf('%F w', ($width * $this->k));
        if ($this->state == 2) {
            $this->_out($this->linestyleWidth);
        }
    }

    /**
     * Returns the current the line width.
     * @return int Line width
     * @public
     * @since 2.1.000 (2008-01-07)
     * @see Line(), SetLineWidth()
     */
    public function GetLineWidth() {
        return $this->LineWidth;
    }

    /**
     * Set line style.
     * @param $style (array) Line style. Array with keys among the following:
     * <ul>
     *   <li>width (float): Width of the line in user units.</li>
     *   <li>cap (string): Type of cap to put on the line. Possible values are:
     * butt, round, square. The difference between "square" and "butt" is that
     * "square" projects a flat end past the end of the line.</li>
     *   <li>join (string): Type of join. Possible values are: miter, round,
     * bevel.</li>
     *   <li>dash (mixed): Dash pattern. Is 0 (without dash) or string with
     * series of length values, which are the lengths of the on and off dashes.
     * For example: "2" represents 2 on, 2 off, 2 on, 2 off, ...; "2,1" is 2 on,
     * 1 off, 2 on, 1 off, ...</li>
     *   <li>phase (integer): Modifier on the dash pattern which is used to shift
     * the point at which the pattern starts.</li>
     *   <li>color (array): Draw color. Format: array(GREY) or array(R,G,B) or array(C,M,Y,K) or array(C,M,Y,K,SpotColorName).</li>
     * </ul>
     * @param $ret (boolean) if true do not send the command.
     * @return string the PDF command
     * @public
     * @since 2.1.000 (2008-01-08)
     */
    public function SetLineStyle($style, $ret=false) {
        $s = ''; // string to be returned
        if (!is_array($style)) {
            return;
        }
        if (isset($style['width'])) {
            $this->LineWidth = $style['width'];
            $this->linestyleWidth = sprintf('%F w', ($style['width'] * $this->k));
            $s .= $this->linestyleWidth.' ';
        }
        if (isset($style['cap'])) {
            $ca = array('butt' => 0, 'round'=> 1, 'square' => 2);
            if (isset($ca[$style['cap']])) {
                $this->linestyleCap = $ca[$style['cap']].' J';
                $s .= $this->linestyleCap.' ';
            }
        }
        if (isset($style['join'])) {
            $ja = array('miter' => 0, 'round' => 1, 'bevel' => 2);
            if (isset($ja[$style['join']])) {
                $this->linestyleJoin = $ja[$style['join']].' j';
                $s .= $this->linestyleJoin.' ';
            }
        }
        if (isset($style['dash'])) {
            $dash_string = '';
            if ($style['dash']) {
                if (preg_match('/^.+,/', $style['dash']) > 0) {
                    $tab = explode(',', $style['dash']);
                } else {
                    $tab = array($style['dash']);
                }
                $dash_string = '';
                foreach ($tab as $i => $v) {
                    if ($i) {
                        $dash_string .= ' ';
                    }
                    $dash_string .= sprintf('%F', $v);
                }
            }
            if (!isset($style['phase']) OR !$style['dash']) {
                $style['phase'] = 0;
            }
            $this->linestyleDash = sprintf('[%s] %F d', $dash_string, $style['phase']);
            $s .= $this->linestyleDash.' ';
        }
        if (isset($style['color'])) {
            $s .= $this->SetDrawColorArray($style['color'], true).' ';
        }
        if (!$ret AND ($this->state == 2)) {
            $this->_out($s);
        }
        return $s;
    }

    /**
     * Begin a new subpath by moving the current point to coordinates (x, y), omitting any connecting line segment.
     * @param $x (float) Abscissa of point.
     * @param $y (float) Ordinate of point.
     * @protected
     * @since 2.1.000 (2008-01-08)
     */
    protected function _outPoint($x, $y) {
        if ($this->state == 2) {
            $this->_out(sprintf('%F %F m', ($x * $this->k), (($this->h - $y) * $this->k)));
        }
    }

    /**
     * Append a straight line segment from the current point to the point (x, y).
     * The new current point shall be (x, y).
     * @param $x (float) Abscissa of end point.
     * @param $y (float) Ordinate of end point.
     * @protected
     * @since 2.1.000 (2008-01-08)
     */
    protected function _outLine($x, $y) {
        if ($this->state == 2) {
            $this->_out(sprintf('%F %F l', ($x * $this->k), (($this->h - $y) * $this->k)));
        }
    }

    /**
     * Append a rectangle to the current path as a complete subpath, with lower-left corner (x, y) and dimensions widthand height in user space.
     * @param $x (float) Abscissa of upper-left corner.
     * @param $y (float) Ordinate of upper-left corner.
     * @param $w (float) Width.
     * @param $h (float) Height.
     * @param $op (string) options
     * @protected
     * @since 2.1.000 (2008-01-08)
     */
    protected function _outRect($x, $y, $w, $h, $op) {
        if ($this->state == 2) {
            $this->_out(sprintf('%F %F %F %F re %s', ($x * $this->k), (($this->h - $y) * $this->k), ($w * $this->k), (-$h * $this->k), $op));
        }
    }

    /**
     * Append a cubic Bezier curve to the current path. The curve shall extend from the current point to the point (x3, y3), using (x1, y1) and (x2, y2) as the Bezier control points.
     * The new current point shall be (x3, y3).
     * @param $x1 (float) Abscissa of control point 1.
     * @param $y1 (float) Ordinate of control point 1.
     * @param $x2 (float) Abscissa of control point 2.
     * @param $y2 (float) Ordinate of control point 2.
     * @param $x3 (float) Abscissa of end point.
     * @param $y3 (float) Ordinate of end point.
     * @protected
     * @since 2.1.000 (2008-01-08)
     */
    protected function _outCurve($x1, $y1, $x2, $y2, $x3, $y3) {
        if ($this->state == 2) {
            $this->_out(sprintf('%F %F %F %F %F %F c', ($x1 * $this->k), (($this->h - $y1) * $this->k), ($x2 * $this->k), (($this->h - $y2) * $this->k), ($x3 * $this->k), (($this->h - $y3) * $this->k)));
        }
    }

    /**
     * Append a cubic Bezier curve to the current path. The curve shall extend from the current point to the point (x3, y3), using the current point and (x2, y2) as the Bezier control points.
     * The new current point shall be (x3, y3).
     * @param $x2 (float) Abscissa of control point 2.
     * @param $y2 (float) Ordinate of control point 2.
     * @param $x3 (float) Abscissa of end point.
     * @param $y3 (float) Ordinate of end point.
     * @protected
     * @since 4.9.019 (2010-04-26)
     */
    protected function _outCurveV($x2, $y2, $x3, $y3) {
        if ($this->state == 2) {
            $this->_out(sprintf('%F %F %F %F v', ($x2 * $this->k), (($this->h - $y2) * $this->k), ($x3 * $this->k), (($this->h - $y3) * $this->k)));
        }
    }

    /**
     * Append a cubic Bezier curve to the current path. The curve shall extend from the current point to the point (x3, y3), using (x1, y1) and (x3, y3) as the Bezier control points.
     * The new current point shall be (x3, y3).
     * @param $x1 (float) Abscissa of control point 1.
     * @param $y1 (float) Ordinate of control point 1.
     * @param $x3 (float) Abscissa of end point.
     * @param $y3 (float) Ordinate of end point.
     * @protected
     * @since 2.1.000 (2008-01-08)
     */
    protected function _outCurveY($x1, $y1, $x3, $y3) {
        if ($this->state == 2) {
            $this->_out(sprintf('%F %F %F %F y', ($x1 * $this->k), (($this->h - $y1) * $this->k), ($x3 * $this->k), (($this->h - $y3) * $this->k)));
        }
    }

    /**
     * Draws a line between two points.
     * @param $x1 (float) Abscissa of first point.
     * @param $y1 (float) Ordinate of first point.
     * @param $x2 (float) Abscissa of second point.
     * @param $y2 (float) Ordinate of second point.
     * @param $style (array) Line style. Array like for SetLineStyle(). Default value: default line style (empty array).
     * @public
     * @since 1.0
     * @see SetLineWidth(), SetDrawColor(), SetLineStyle()
     */
    public function Line($x1, $y1, $x2, $y2, $style=array()) {
        if ($this->state != 2) {
            return;
        }
        if (is_array($style)) {
            $this->SetLineStyle($style);
        }
        $this->_outPoint($x1, $y1);
        $this->_outLine($x2, $y2);
        $this->_out('S');
    }

    /**
     * Draws a rectangle.
     * @param $x (float) Abscissa of upper-left corner.
     * @param $y (float) Ordinate of upper-left corner.
     * @param $w (float) Width.
     * @param $h (float) Height.
     * @param $style (string) Style of rendering. See the getPathPaintOperator() function for more information.
     * @param $border_style (array) Border style of rectangle. Array with keys among the following:
     * <ul>
     *   <li>all: Line style of all borders. Array like for SetLineStyle().</li>
     *   <li>L, T, R, B or combinations: Line style of left, top, right or bottom border. Array like for SetLineStyle().</li>
     * </ul>
     * If a key is not present or is null, the correspondent border is not drawn. Default value: default line style (empty array).
     * @param $fill_color (array) Fill color. Format: array(GREY) or array(R,G,B) or array(C,M,Y,K) or array(C,M,Y,K,SpotColorName). Default value: default color (empty array).
     * @public
     * @since 1.0
     * @see SetLineStyle()
     */
    public function Rect($x, $y, $w, $h, $style='', $border_style=array(), $fill_color=array()) {
        if ($this->state != 2) {
            return;
        }
        if (empty($style)) {
            $style = 'S';
        }
        if (!(strpos($style, 'F') === false) AND !empty($fill_color)) {
            // set background color
            $this->SetFillColorArray($fill_color);
        }
        if (!empty($border_style)) {
            if (isset($border_style['all']) AND !empty($border_style['all'])) {
                //set global style for border
                $this->SetLineStyle($border_style['all']);
                $border_style = array();
            } else {
                // remove stroke operator from style
                $opnostroke = array('S' => '', 'D' => '', 's' => '', 'd' => '', 'B' => 'F', 'FD' => 'F', 'DF' => 'F', 'B*' => 'F*', 'F*D' => 'F*', 'DF*' => 'F*', 'b' => 'f', 'fd' => 'f', 'df' => 'f', 'b*' => 'f*', 'f*d' => 'f*', 'df*' => 'f*' );
                if (isset($opnostroke[$style])) {
                    $style = $opnostroke[$style];
                }
            }
        }
        if (!empty($style)) {
            $op = TCPDF_STATIC::getPathPaintOperator($style);
            $this->_outRect($x, $y, $w, $h, $op);
        }
        if (!empty($border_style)) {
            $border_style2 = array();
            foreach ($border_style as $line => $value) {
                $length = strlen($line);
                for ($i = 0; $i < $length; ++$i) {
                    $border_style2[$line[$i]] = $value;
                }
            }
            $border_style = $border_style2;
            if (isset($border_style['L']) AND $border_style['L']) {
                $this->Line($x, $y, $x, $y + $h, $border_style['L']);
            }
            if (isset($border_style['T']) AND $border_style['T']) {
                $this->Line($x, $y, $x + $w, $y, $border_style['T']);
            }
            if (isset($border_style['R']) AND $border_style['R']) {
                $this->Line($x + $w, $y, $x + $w, $y + $h, $border_style['R']);
            }
            if (isset($border_style['B']) AND $border_style['B']) {
                $this->Line($x, $y + $h, $x + $w, $y + $h, $border_style['B']);
            }
        }
    }

    /**
     * Draws a Bezier curve.
     * The Bezier curve is a tangent to the line between the control points at
     * either end of the curve.
     * @param $x0 (float) Abscissa of start point.
     * @param $y0 (float) Ordinate of start point.
     * @param $x1 (float) Abscissa of control point 1.
     * @param $y1 (float) Ordinate of control point 1.
     * @param $x2 (float) Abscissa of control point 2.
     * @param $y2 (float) Ordinate of control point 2.
     * @param $x3 (float) Abscissa of end point.
     * @param $y3 (float) Ordinate of end point.
     * @param $style (string) Style of rendering. See the getPathPaintOperator() function for more information.
     * @param $line_style (array) Line style of curve. Array like for SetLineStyle(). Default value: default line style (empty array).
     * @param $fill_color (array) Fill color. Format: array(GREY) or array(R,G,B) or array(C,M,Y,K) or array(C,M,Y,K,SpotColorName). Default value: default color (empty array).
     * @public
     * @see SetLineStyle()
     * @since 2.1.000 (2008-01-08)
     */
    public function Curve($x0, $y0, $x1, $y1, $x2, $y2, $x3, $y3, $style='', $line_style=array(), $fill_color=array()) {
        if ($this->state != 2) {
            return;
        }
        if (!(false === strpos($style, 'F')) AND isset($fill_color)) {
            $this->SetFillColorArray($fill_color);
        }
        $op = TCPDF_STATIC::getPathPaintOperator($style);
        if ($line_style) {
            $this->SetLineStyle($line_style);
        }
        $this->_outPoint($x0, $y0);
        $this->_outCurve($x1, $y1, $x2, $y2, $x3, $y3);
        $this->_out($op);
    }

    /**
     * Draws a poly-Bezier curve.
     * Each Bezier curve segment is a tangent to the line between the control points at
     * either end of the curve.
     * @param $x0 (float) Abscissa of start point.
     * @param $y0 (float) Ordinate of start point.
     * @param $segments (float) An array of bezier descriptions. Format: array(x1, y1, x2, y2, x3, y3).
     * @param $style (string) Style of rendering. See the getPathPaintOperator() function for more information.
     * @param $line_style (array) Line style of curve. Array like for SetLineStyle(). Default value: default line style (empty array).
     * @param $fill_color (array) Fill color. Format: array(GREY) or array(R,G,B) or array(C,M,Y,K) or array(C,M,Y,K,SpotColorName). Default value: default color (empty array).
     * @public
     * @see SetLineStyle()
     * @since 3.0008 (2008-05-12)
     */
    public function Polycurve($x0, $y0, $segments, $style='', $line_style=array(), $fill_color=array()) {
        if ($this->state != 2) {
            return;
        }
        if (!(false === strpos($style, 'F')) AND isset($fill_color)) {
            $this->SetFillColorArray($fill_color);
        }
        $op = TCPDF_STATIC::getPathPaintOperator($style);
        if ($op == 'f') {
            $line_style = array();
        }
        if ($line_style) {
            $this->SetLineStyle($line_style);
        }
        $this->_outPoint($x0, $y0);
        foreach ($segments as $segment) {
            list($x1, $y1, $x2, $y2, $x3, $y3) = $segment;
            $this->_outCurve($x1, $y1, $x2, $y2, $x3, $y3);
        }
        $this->_out($op);
    }

    /**
     * Draws an ellipse.
     * An ellipse is formed from n Bezier curves.
     * @param $x0 (float) Abscissa of center point.
     * @param $y0 (float) Ordinate of center point.
     * @param $rx (float) Horizontal radius.
     * @param $ry (float) Vertical radius (if ry = 0 then is a circle, see Circle()). Default value: 0.
     * @param $angle: (float) Angle oriented (anti-clockwise). Default value: 0.
     * @param $astart: (float) Angle start of draw line. Default value: 0.
     * @param $afinish: (float) Angle finish of draw line. Default value: 360.
     * @param $style (string) Style of rendering. See the getPathPaintOperator() function for more information.
     * @param $line_style (array) Line style of ellipse. Array like for SetLineStyle(). Default value: default line style (empty array).
     * @param $fill_color (array) Fill color. Format: array(GREY) or array(R,G,B) or array(C,M,Y,K) or array(C,M,Y,K,SpotColorName). Default value: default color (empty array).
     * @param $nc (integer) Number of curves used to draw a 90 degrees portion of ellipse.
     * @author Nicola Asuni
     * @public
     * @since 2.1.000 (2008-01-08)
     */
    public function Ellipse($x0, $y0, $rx, $ry='', $angle=0, $astart=0, $afinish=360, $style='', $line_style=array(), $fill_color=array(), $nc=2) {
        if ($this->state != 2) {
            return;
        }
        if (TCPDF_STATIC::empty_string($ry) OR ($ry == 0)) {
            $ry = $rx;
        }
        if (!(false === strpos($style, 'F')) AND isset($fill_color)) {
            $this->SetFillColorArray($fill_color);
        }
        $op = TCPDF_STATIC::getPathPaintOperator($style);
        if ($op == 'f') {
            $line_style = array();
        }
        if ($line_style) {
            $this->SetLineStyle($line_style);
        }
        $this->_outellipticalarc($x0, $y0, $rx, $ry, $angle, $astart, $afinish, false, $nc, true, true, false);
        $this->_out($op);
    }

    /**
     * Append an elliptical arc to the current path.
     * An ellipse is formed from n Bezier curves.
     * @param $xc (float) Abscissa of center point.
     * @param $yc (float) Ordinate of center point.
     * @param $rx (float) Horizontal radius.
     * @param $ry (float) Vertical radius (if ry = 0 then is a circle, see Circle()). Default value: 0.
     * @param $xang: (float) Angle between the X-axis and the major axis of the ellipse. Default value: 0.
     * @param $angs: (float) Angle start of draw line. Default value: 0.
     * @param $angf: (float) Angle finish of draw line. Default value: 360.
     * @param $pie (boolean) if true do not mark the border point (used to draw pie sectors).
     * @param $nc (integer) Number of curves used to draw a 90 degrees portion of ellipse.
     * @param $startpoint (boolean) if true output a starting point.
     * @param $ccw (boolean) if true draws in counter-clockwise.
     * @param $svg (boolean) if true the angles are in svg mode (already calculated).
     * @return array bounding box coordinates (x min, y min, x max, y max)
     * @author Nicola Asuni
     * @protected
     * @since 4.9.019 (2010-04-26)
     */
    protected function _outellipticalarc($xc, $yc, $rx, $ry, $xang=0, $angs=0, $angf=360, $pie=false, $nc=2, $startpoint=true, $ccw=true, $svg=false) {
        if (($rx <= 0) OR ($ry < 0)) {
            return;
        }
        $k = $this->k;
        if ($nc < 2) {
            $nc = 2;
        }
        $xmin = 2147483647;
        $ymin = 2147483647;
        $xmax = 0;
        $ymax = 0;
        if ($pie) {
            // center of the arc
            $this->_outPoint($xc, $yc);
        }
        $xang = deg2rad((float) $xang);
        $angs = deg2rad((float) $angs);
        $angf = deg2rad((float) $angf);
        if ($svg) {
            $as = $angs;
            $af = $angf;
        } else {
            $as = atan2((sin($angs) / $ry), (cos($angs) / $rx));
            $af = atan2((sin($angf) / $ry), (cos($angf) / $rx));
        }
        if ($as < 0) {
            $as += (2 * M_PI);
        }
        if ($af < 0) {
            $af += (2 * M_PI);
        }
        if ($ccw AND ($as > $af)) {
            // reverse rotation
            $as -= (2 * M_PI);
        } elseif (!$ccw AND ($as < $af)) {
            // reverse rotation
            $af -= (2 * M_PI);
        }
        $total_angle = ($af - $as);
        if ($nc < 2) {
            $nc = 2;
        }
        // total arcs to draw
        $nc *= (2 * abs($total_angle) / M_PI);
        $nc = round($nc) + 1;
        // angle of each arc
        $arcang = ($total_angle / $nc);
        // center point in PDF coordinates
        $x0 = $xc;
        $y0 = ($this->h - $yc);
        // starting angle
        $ang = $as;
        $alpha = sin($arcang) * ((sqrt(4 + (3 * pow(tan(($arcang) / 2), 2))) - 1) / 3);
        $cos_xang = cos($xang);
        $sin_xang = sin($xang);
        $cos_ang = cos($ang);
        $sin_ang = sin($ang);
        // first arc point
        $px1 = $x0 + ($rx * $cos_xang * $cos_ang) - ($ry * $sin_xang * $sin_ang);
        $py1 = $y0 + ($rx * $sin_xang * $cos_ang) + ($ry * $cos_xang * $sin_ang);
        // first Bezier control point
        $qx1 = ($alpha * ((-$rx * $cos_xang * $sin_ang) - ($ry * $sin_xang * $cos_ang)));
        $qy1 = ($alpha * ((-$rx * $sin_xang * $sin_ang) + ($ry * $cos_xang * $cos_ang)));
        if ($pie) {
            // line from center to arc starting point
            $this->_outLine($px1, $this->h - $py1);
        } elseif ($startpoint) {
            // arc starting point
            $this->_outPoint($px1, $this->h - $py1);
        }
        // draw arcs
        for ($i = 1; $i <= $nc; ++$i) {
            // starting angle
            $ang = $as + ($i * $arcang);
            if ($i == $nc) {
                $ang = $af;
            }
            $cos_ang = cos($ang);
            $sin_ang = sin($ang);
            // second arc point
            $px2 = $x0 + ($rx * $cos_xang * $cos_ang) - ($ry * $sin_xang * $sin_ang);
            $py2 = $y0 + ($rx * $sin_xang * $cos_ang) + ($ry * $cos_xang * $sin_ang);
            // second Bezier control point
            $qx2 = ($alpha * ((-$rx * $cos_xang * $sin_ang) - ($ry * $sin_xang * $cos_ang)));
            $qy2 = ($alpha * ((-$rx * $sin_xang * $sin_ang) + ($ry * $cos_xang * $cos_ang)));
            // draw arc
            $cx1 = ($px1 + $qx1);
            $cy1 = ($this->h - ($py1 + $qy1));
            $cx2 = ($px2 - $qx2);
            $cy2 = ($this->h - ($py2 - $qy2));
            $cx3 = $px2;
            $cy3 = ($this->h - $py2);
            $this->_outCurve($cx1, $cy1, $cx2, $cy2, $cx3, $cy3);
            // get bounding box coordinates
            $xmin = min($xmin, $cx1, $cx2, $cx3);
            $ymin = min($ymin, $cy1, $cy2, $cy3);
            $xmax = max($xmax, $cx1, $cx2, $cx3);
            $ymax = max($ymax, $cy1, $cy2, $cy3);
            // move to next point
            $px1 = $px2;
            $py1 = $py2;
            $qx1 = $qx2;
            $qy1 = $qy2;
        }
        if ($pie) {
            $this->_outLine($xc, $yc);
            // get bounding box coordinates
            $xmin = min($xmin, $xc);
            $ymin = min($ymin, $yc);
            $xmax = max($xmax, $xc);
            $ymax = max($ymax, $yc);
        }
        return array($xmin, $ymin, $xmax, $ymax);
    }

    /**
     * Draws a circle.
     * A circle is formed from n Bezier curves.
     * @param $x0 (float) Abscissa of center point.
     * @param $y0 (float) Ordinate of center point.
     * @param $r (float) Radius.
     * @param $angstr: (float) Angle start of draw line. Default value: 0.
     * @param $angend: (float) Angle finish of draw line. Default value: 360.
     * @param $style (string) Style of rendering. See the getPathPaintOperator() function for more information.
     * @param $line_style (array) Line style of circle. Array like for SetLineStyle(). Default value: default line style (empty array).
     * @param $fill_color (array) Fill color. Format: array(red, green, blue). Default value: default color (empty array).
     * @param $nc (integer) Number of curves used to draw a 90 degrees portion of circle.
     * @public
     * @since 2.1.000 (2008-01-08)
     */
    public function Circle($x0, $y0, $r, $angstr=0, $angend=360, $style='', $line_style=array(), $fill_color=array(), $nc=2) {
        $this->Ellipse($x0, $y0, $r, $r, 0, $angstr, $angend, $style, $line_style, $fill_color, $nc);
    }

    /**
     * Draws a polygonal line
     * @param $p (array) Points 0 to ($np - 1). Array with values (x0, y0, x1, y1,..., x(np-1), y(np - 1))
     * @param $style (string) Style of rendering. See the getPathPaintOperator() function for more information.
     * @param $line_style (array) Line style of polygon. Array with keys among the following:
     * <ul>
     *   <li>all: Line style of all lines. Array like for SetLineStyle().</li>
     *   <li>0 to ($np - 1): Line style of each line. Array like for SetLineStyle().</li>
     * </ul>
     * If a key is not present or is null, not draws the line. Default value is default line style (empty array).
     * @param $fill_color (array) Fill color. Format: array(GREY) or array(R,G,B) or array(C,M,Y,K) or array(C,M,Y,K,SpotColorName). Default value: default color (empty array).
     * @since 4.8.003 (2009-09-15)
     * @public
     */
    public function PolyLine($p, $style='', $line_style=array(), $fill_color=array()) {
        $this->Polygon($p, $style, $line_style, $fill_color, false);
    }

    /**
     * Draws a polygon.
     * @param $p (array) Points 0 to ($np - 1). Array with values (x0, y0, x1, y1,..., x(np-1), y(np - 1))
     * @param $style (string) Style of rendering. See the getPathPaintOperator() function for more information.
     * @param $line_style (array) Line style of polygon. Array with keys among the following:
     * <ul>
     *   <li>all: Line style of all lines. Array like for SetLineStyle().</li>
     *   <li>0 to ($np - 1): Line style of each line. Array like for SetLineStyle().</li>
     * </ul>
     * If a key is not present or is null, not draws the line. Default value is default line style (empty array).
     * @param $fill_color (array) Fill color. Format: array(GREY) or array(R,G,B) or array(C,M,Y,K) or array(C,M,Y,K,SpotColorName). Default value: default color (empty array).
     * @param $closed (boolean) if true the polygon is closes, otherwise will remain open
     * @public
     * @since 2.1.000 (2008-01-08)
     */
    public function Polygon($p, $style='', $line_style=array(), $fill_color=array(), $closed=true) {
        if ($this->state != 2) {
            return;
        }
        $nc = count($p); // number of coordinates
        $np = $nc / 2; // number of points
        if ($closed) {
            // close polygon by adding the first 2 points at the end (one line)
            for ($i = 0; $i < 4; ++$i) {
                $p[$nc + $i] = $p[$i];
            }
            // copy style for the last added line
            if (isset($line_style[0])) {
                $line_style[$np] = $line_style[0];
            }
            $nc += 4;
        }
        if (!(false === strpos($style, 'F')) AND isset($fill_color)) {
            $this->SetFillColorArray($fill_color);
        }
        $op = TCPDF_STATIC::getPathPaintOperator($style);
        if ($op == 'f') {
            $line_style = array();
        }
        $draw = true;
        if ($line_style) {
            if (isset($line_style['all'])) {
                $this->SetLineStyle($line_style['all']);
            } else {
                $draw = false;
                if ($op == 'B') {
                    // draw fill
                    $op = 'f';
                    $this->_outPoint($p[0], $p[1]);
                    for ($i = 2; $i < $nc; $i = $i + 2) {
                        $this->_outLine($p[$i], $p[$i + 1]);
                    }
                    $this->_out($op);
                }
                // draw outline
                $this->_outPoint($p[0], $p[1]);
                for ($i = 2; $i < $nc; $i = $i + 2) {
                    $line_num = ($i / 2) - 1;
                    if (isset($line_style[$line_num])) {
                        if ($line_style[$line_num] != 0) {
                            if (is_array($line_style[$line_num])) {
                                $this->_out('S');
                                $this->SetLineStyle($line_style[$line_num]);
                                $this->_outPoint($p[$i - 2], $p[$i - 1]);
                                $this->_outLine($p[$i], $p[$i + 1]);
                                $this->_out('S');
                                $this->_outPoint($p[$i], $p[$i + 1]);
                            } else {
                                $this->_outLine($p[$i], $p[$i + 1]);
                            }
                        }
                    } else {
                        $this->_outLine($p[$i], $p[$i + 1]);
                    }
                }
                $this->_out($op);
            }
        }
        if ($draw) {
            $this->_outPoint($p[0], $p[1]);
            for ($i = 2; $i < $nc; $i = $i + 2) {
                $this->_outLine($p[$i], $p[$i + 1]);
            }
            $this->_out($op);
        }
    }

    /**
     * Draws a regular polygon.
     * @param $x0 (float) Abscissa of center point.
     * @param $y0 (float) Ordinate of center point.
     * @param $r: (float) Radius of inscribed circle.
     * @param $ns (integer) Number of sides.
     * @param $angle (float) Angle oriented (anti-clockwise). Default value: 0.
     * @param $draw_circle (boolean) Draw inscribed circle or not. Default value: false.
     * @param $style (string) Style of rendering. See the getPathPaintOperator() function for more information.
     * @param $line_style (array) Line style of polygon sides. Array with keys among the following:
     * <ul>
     *   <li>all: Line style of all sides. Array like for SetLineStyle().</li>
     *   <li>0 to ($ns - 1): Line style of each side. Array like for SetLineStyle().</li>
     * </ul>
     * If a key is not present or is null, not draws the side. Default value is default line style (empty array).
     * @param $fill_color (array) Fill color. Format: array(red, green, blue). Default value: default color (empty array).
     * @param $circle_style (string) Style of rendering of inscribed circle (if draws). Possible values are:
     * <ul>
     *   <li>D or empty string: Draw (default).</li>
     *   <li>F: Fill.</li>
     *   <li>DF or FD: Draw and fill.</li>
     *   <li>CNZ: Clipping mode (using the even-odd rule to determine which regions lie inside the clipping path).</li>
     *   <li>CEO: Clipping mode (using the nonzero winding number rule to determine which regions lie inside the clipping path).</li>
     * </ul>
     * @param $circle_outLine_style (array) Line style of inscribed circle (if draws). Array like for SetLineStyle(). Default value: default line style (empty array).
     * @param $circle_fill_color (array) Fill color of inscribed circle (if draws). Format: array(red, green, blue). Default value: default color (empty array).
     * @public
     * @since 2.1.000 (2008-01-08)
     */
    public function RegularPolygon($x0, $y0, $r, $ns, $angle=0, $draw_circle=false, $style='', $line_style=array(), $fill_color=array(), $circle_style='', $circle_outLine_style=array(), $circle_fill_color=array()) {
        if (3 > $ns) {
            $ns = 3;
        }
        if ($draw_circle) {
            $this->Circle($x0, $y0, $r, 0, 360, $circle_style, $circle_outLine_style, $circle_fill_color);
        }
        $p = array();
        for ($i = 0; $i < $ns; ++$i) {
            $a = $angle + ($i * 360 / $ns);
            $a_rad = deg2rad((float) $a);
            $p[] = $x0 + ($r * sin($a_rad));
            $p[] = $y0 + ($r * cos($a_rad));
        }
        $this->Polygon($p, $style, $line_style, $fill_color);
    }

    /**
     * Draws a star polygon
     * @param $x0 (float) Abscissa of center point.
     * @param $y0 (float) Ordinate of center point.
     * @param $r (float) Radius of inscribed circle.
     * @param $nv (integer) Number of vertices.
     * @param $ng (integer) Number of gap (if ($ng % $nv = 1) then is a regular polygon).
     * @param $angle: (float) Angle oriented (anti-clockwise). Default value: 0.
     * @param $draw_circle: (boolean) Draw inscribed circle or not. Default value is false.
     * @param $style (string) Style of rendering. See the getPathPaintOperator() function for more information.
     * @param $line_style (array) Line style of polygon sides. Array with keys among the following:
     * <ul>
     *   <li>all: Line style of all sides. Array like for
     * SetLineStyle().</li>
     *   <li>0 to (n - 1): Line style of each side. Array like for SetLineStyle().</li>
     * </ul>
     * If a key is not present or is null, not draws the side. Default value is default line style (empty array).
     * @param $fill_color (array) Fill color. Format: array(red, green, blue). Default value: default color (empty array).
     * @param $circle_style (string) Style of rendering of inscribed circle (if draws). Possible values are:
     * <ul>
     *   <li>D or empty string: Draw (default).</li>
     *   <li>F: Fill.</li>
     *   <li>DF or FD: Draw and fill.</li>
     *   <li>CNZ: Clipping mode (using the even-odd rule to determine which regions lie inside the clipping path).</li>
     *   <li>CEO: Clipping mode (using the nonzero winding number rule to determine which regions lie inside the clipping path).</li>
     * </ul>
     * @param $circle_outLine_style (array) Line style of inscribed circle (if draws). Array like for SetLineStyle(). Default value: default line style (empty array).
     * @param $circle_fill_color (array) Fill color of inscribed circle (if draws). Format: array(red, green, blue). Default value: default color (empty array).
     * @public
     * @since 2.1.000 (2008-01-08)
     */
    public function StarPolygon($x0, $y0, $r, $nv, $ng, $angle=0, $draw_circle=false, $style='', $line_style=array(), $fill_color=array(), $circle_style='', $circle_outLine_style=array(), $circle_fill_color=array()) {
        if ($nv < 2) {
            $nv = 2;
        }
        if ($draw_circle) {
            $this->Circle($x0, $y0, $r, 0, 360, $circle_style, $circle_outLine_style, $circle_fill_color);
        }
        $p2 = array();
        $visited = array();
        for ($i = 0; $i < $nv; ++$i) {
            $a = $angle + ($i * 360 / $nv);
            $a_rad = deg2rad((float) $a);
            $p2[] = $x0 + ($r * sin($a_rad));
            $p2[] = $y0 + ($r * cos($a_rad));
            $visited[] = false;
        }
        $p = array();
        $i = 0;
        do {
            $p[] = $p2[$i * 2];
            $p[] = $p2[($i * 2) + 1];
            $visited[$i] = true;
            $i += $ng;
            $i %= $nv;
        } while (!$visited[$i]);
        $this->Polygon($p, $style, $line_style, $fill_color);
    }

    /**
     * Draws a rounded rectangle.
     * @param $x (float) Abscissa of upper-left corner.
     * @param $y (float) Ordinate of upper-left corner.
     * @param $w (float) Width.
     * @param $h (float) Height.
     * @param $r (float) the radius of the circle used to round off the corners of the rectangle.
     * @param $round_corner (string) Draws rounded corner or not. String with a 0 (not rounded i-corner) or 1 (rounded i-corner) in i-position. Positions are, in order and begin to 0: top right, bottom right, bottom left and top left. Default value: all rounded corner ("1111").
     * @param $style (string) Style of rendering. See the getPathPaintOperator() function for more information.
     * @param $border_style (array) Border style of rectangle. Array like for SetLineStyle(). Default value: default line style (empty array).
     * @param $fill_color (array) Fill color. Format: array(GREY) or array(R,G,B) or array(C,M,Y,K) or array(C,M,Y,K,SpotColorName). Default value: default color (empty array).
     * @public
     * @since 2.1.000 (2008-01-08)
     */
    public function RoundedRect($x, $y, $w, $h, $r, $round_corner='1111', $style='', $border_style=array(), $fill_color=array()) {
        $this->RoundedRectXY($x, $y, $w, $h, $r, $r, $round_corner, $style, $border_style, $fill_color);
    }

    /**
     * Draws a rounded rectangle.
     * @param $x (float) Abscissa of upper-left corner.
     * @param $y (float) Ordinate of upper-left corner.
     * @param $w (float) Width.
     * @param $h (float) Height.
     * @param $rx (float) the x-axis radius of the ellipse used to round off the corners of the rectangle.
     * @param $ry (float) the y-axis radius of the ellipse used to round off the corners of the rectangle.
     * @param $round_corner (string) Draws rounded corner or not. String with a 0 (not rounded i-corner) or 1 (rounded i-corner) in i-position. Positions are, in order and begin to 0: top right, bottom right, bottom left and top left. Default value: all rounded corner ("1111").
     * @param $style (string) Style of rendering. See the getPathPaintOperator() function for more information.
     * @param $border_style (array) Border style of rectangle. Array like for SetLineStyle(). Default value: default line style (empty array).
     * @param $fill_color (array) Fill color. Format: array(GREY) or array(R,G,B) or array(C,M,Y,K) or array(C,M,Y,K,SpotColorName). Default value: default color (empty array).
     * @public
     * @since 4.9.019 (2010-04-22)
     */
    public function RoundedRectXY($x, $y, $w, $h, $rx, $ry, $round_corner='1111', $style='', $border_style=array(), $fill_color=array()) {
        if ($this->state != 2) {
            return;
        }
        if (($round_corner == '0000') OR (($rx == $ry) AND ($rx == 0))) {
            // Not rounded
            $this->Rect($x, $y, $w, $h, $style, $border_style, $fill_color);
            return;
        }
        // Rounded
        if (!(false === strpos($style, 'F')) AND isset($fill_color)) {
            $this->SetFillColorArray($fill_color);
        }
        $op = TCPDF_STATIC::getPathPaintOperator($style);
        if ($op == 'f') {
            $border_style = array();
        }
        if ($border_style) {
            $this->SetLineStyle($border_style);
        }
        $MyArc = 4 / 3 * (sqrt(2) - 1);
        $this->_outPoint($x + $rx, $y);
        $xc = $x + $w - $rx;
        $yc = $y + $ry;
        $this->_outLine($xc, $y);
        if ($round_corner[0]) {
            $this->_outCurve($xc + ($rx * $MyArc), $yc - $ry, $xc + $rx, $yc - ($ry * $MyArc), $xc + $rx, $yc);
        } else {
            $this->_outLine($x + $w, $y);
        }
        $xc = $x + $w - $rx;
        $yc = $y + $h - $ry;
        $this->_outLine($x + $w, $yc);
        if ($round_corner[1]) {
            $this->_outCurve($xc + $rx, $yc + ($ry * $MyArc), $xc + ($rx * $MyArc), $yc + $ry, $xc, $yc + $ry);
        } else {
            $this->_outLine($x + $w, $y + $h);
        }
        $xc = $x + $rx;
        $yc = $y + $h - $ry;
        $this->_outLine($xc, $y + $h);
        if ($round_corner[2]) {
            $this->_outCurve($xc - ($rx * $MyArc), $yc + $ry, $xc - $rx, $yc + ($ry * $MyArc), $xc - $rx, $yc);
        } else {
            $this->_outLine($x, $y + $h);
        }
        $xc = $x + $rx;
        $yc = $y + $ry;
        $this->_outLine($x, $yc);
        if ($round_corner[3]) {
            $this->_outCurve($xc - $rx, $yc - ($ry * $MyArc), $xc - ($rx * $MyArc), $yc - $ry, $xc, $yc - $ry);
        } else {
            $this->_outLine($x, $y);
            $this->_outLine($x + $rx, $y);
        }
        $this->_out($op);
    }

    /**
     * Draws a grahic arrow.
     * @param $x0 (float) Abscissa of first point.
     * @param $y0 (float) Ordinate of first point.
     * @param $x1 (float) Abscissa of second point.
     * @param $y1 (float) Ordinate of second point.
     * @param $head_style (int) (0 = draw only arrowhead arms, 1 = draw closed arrowhead, but no fill, 2 = closed and filled arrowhead, 3 = filled arrowhead)
     * @param $arm_size (float) length of arrowhead arms
     * @param $arm_angle (int) angle between an arm and the shaft
     * @author Piotr Galecki, Nicola Asuni, Andy Meier
     * @since 4.6.018 (2009-07-10)
     */
    public function Arrow($x0, $y0, $x1, $y1, $head_style=0, $arm_size=5, $arm_angle=15) {
        // getting arrow direction angle
        // 0 deg angle is when both arms go along X axis. angle grows clockwise.
        $dir_angle = atan2(($y0 - $y1), ($x0 - $x1));
        if ($dir_angle < 0) {
            $dir_angle += (2 * M_PI);
        }
        $arm_angle = deg2rad($arm_angle);
        $sx1 = $x1;
        $sy1 = $y1;
        if ($head_style > 0) {
            // calculate the stopping point for the arrow shaft
            $sx1 = $x1 + (($arm_size - $this->LineWidth) * cos($dir_angle));
            $sy1 = $y1 + (($arm_size - $this->LineWidth) * sin($dir_angle));
        }
        // main arrow line / shaft
        $this->Line($x0, $y0, $sx1, $sy1);
        // left arrowhead arm tip
        $x2L = $x1 + ($arm_size * cos($dir_angle + $arm_angle));
        $y2L = $y1 + ($arm_size * sin($dir_angle + $arm_angle));
        // right arrowhead arm tip
        $x2R = $x1 + ($arm_size * cos($dir_angle - $arm_angle));
        $y2R = $y1 + ($arm_size * sin($dir_angle - $arm_angle));
        $mode = 'D';
        $style = array();
        switch ($head_style) {
            case 0: {
                // draw only arrowhead arms
                $mode = 'D';
                $style = array(1, 1, 0);
                break;
            }
            case 1: {
                // draw closed arrowhead, but no fill
                $mode = 'D';
                break;
            }
            case 2: {
                // closed and filled arrowhead
                $mode = 'DF';
                break;
            }
            case 3: {
                // filled arrowhead
                $mode = 'F';
                break;
            }
        }
        $this->Polygon(array($x2L, $y2L, $x1, $y1, $x2R, $y2R), $mode, $style, array());
    }

    // END GRAPHIC FUNCTIONS SECTION -----------------------

    /**
     * Add a Named Destination.
     * NOTE: destination names are unique, so only last entry will be saved.
     * @param $name (string) Destination name.
     * @param $y (float) Y position in user units of the destiantion on the selected page (default = -1 = current position; 0 = page start;).
     * @param $page (int|string) Target page number (leave empty for current page). If you prefix a page number with the * character, then this page will not be changed when adding/deleting/moving pages.
     * @param $x (float) X position in user units of the destiantion on the selected page (default = -1 = current position;).
     * @return (string) Stripped named destination identifier or false in case of error.
     * @public
     * @author Christian Deligant, Nicola Asuni
     * @since 5.9.097 (2011-06-23)
     */
    public function setDestination($name, $y=-1, $page='', $x=-1) {
        // remove unsupported characters
        $name = TCPDF_STATIC::encodeNameObject($name);
        if (TCPDF_STATIC::empty_string($name)) {
            return false;
        }
        if ($y == -1) {
            $y = $this->GetY();
        } elseif ($y < 0) {
            $y = 0;
        } elseif ($y > $this->h) {
            $y = $this->h;
        }
        if ($x == -1) {
            $x = $this->GetX();
        } elseif ($x < 0) {
            $x = 0;
        } elseif ($x > $this->w) {
            $x = $this->w;
        }
        $fixed = false;
        if (!empty($page) AND ($page[0] == '*')) {
            $page = intval(substr($page, 1));
            // this page number will not be changed when moving/add/deleting pages
            $fixed = true;
        }
        if (empty($page)) {
            $page = $this->PageNo();
            if (empty($page)) {
                return;
            }
        }
        $this->dests[$name] = array('x' => $x, 'y' => $y, 'p' => $page, 'f' => $fixed);
        return $name;
    }

    /**
     * Return the Named Destination array.
     * @return (array) Named Destination array.
     * @public
     * @author Nicola Asuni
     * @since 5.9.097 (2011-06-23)
     */
    public function getDestination() {
        return $this->dests;
    }

    /**
     * Insert Named Destinations.
     * @protected
     * @author Johannes G\FCntert, Nicola Asuni
     * @since 5.9.098 (2011-06-23)
     */
    protected function _putdests() {
        if (empty($this->dests)) {
            return;
        }
        $this->n_dests = $this->_newobj();
        $out = ' <<';
        foreach($this->dests as $name => $o) {
            $out .= ' /'.$name.' '.sprintf('[%u 0 R /XYZ %F %F null]', $this->page_obj_id[($o['p'])], ($o['x'] * $this->k), ($this->pagedim[$o['p']]['h'] - ($o['y'] * $this->k)));
        }
        $out .= ' >>';
        $out .= "\n".'endobj';
        $this->_out($out);
    }

    /**
     * Adds a bookmark - alias for Bookmark().
     * @param $txt (string) Bookmark description.
     * @param $level (int) Bookmark level (minimum value is 0).
     * @param $y (float) Y position in user units of the bookmark on the selected page (default = -1 = current position; 0 = page start;).
     * @param $page (int|string) Target page number (leave empty for current page). If you prefix a page number with the * character, then this page will not be changed when adding/deleting/moving pages.
     * @param $style (string) Font style: B = Bold, I = Italic, BI = Bold + Italic.
     * @param $color (array) RGB color array (values from 0 to 255).
     * @param $x (float) X position in user units of the bookmark on the selected page (default = -1 = current position;).
     * @param $link (mixed) URL, or numerical link ID, or named destination (# character followed by the destination name), or embedded file (* character followed by the file name).
     * @public
     */
    public function setBookmark($txt, $level=0, $y=-1, $page='', $style='', $color=array(0,0,0), $x=-1, $link='') {
        $this->Bookmark($txt, $level, $y, $page, $style, $color, $x, $link);
    }

    /**
     * Adds a bookmark.
     * @param $txt (string) Bookmark description.
     * @param $level (int) Bookmark level (minimum value is 0).
     * @param $y (float) Y position in user units of the bookmark on the selected page (default = -1 = current position; 0 = page start;).
     * @param $page (int|string) Target page number (leave empty for current page). If you prefix a page number with the * character, then this page will not be changed when adding/deleting/moving pages.
     * @param $style (string) Font style: B = Bold, I = Italic, BI = Bold + Italic.
     * @param $color (array) RGB color array (values from 0 to 255).
     * @param $x (float) X position in user units of the bookmark on the selected page (default = -1 = current position;).
     * @param $link (mixed) URL, or numerical link ID, or named destination (# character followed by the destination name), or embedded file (* character followed by the file name).
     * @public
     * @since 2.1.002 (2008-02-12)
     */
    public function Bookmark($txt, $level=0, $y=-1, $page='', $style='', $color=array(0,0,0), $x=-1, $link='') {
        if ($level < 0) {
            $level = 0;
        }
        if (isset($this->outlines[0])) {
            $lastoutline = end($this->outlines);
            $maxlevel = $lastoutline['l'] + 1;
        } else {
            $maxlevel = 0;
        }
        if ($level > $maxlevel) {
            $level = $maxlevel;
        }
        if ($y == -1) {
            $y = $this->GetY();
        } elseif ($y < 0) {
            $y = 0;
        } elseif ($y > $this->h) {
            $y = $this->h;
        }
        if ($x == -1) {
            $x = $this->GetX();
        } elseif ($x < 0) {
            $x = 0;
        } elseif ($x > $this->w) {
            $x = $this->w;
        }
        $fixed = false;
        if (!empty($page) AND ($page[0] == '*')) {
            $page = intval(substr($page, 1));
            // this page number will not be changed when moving/add/deleting pages
            $fixed = true;
        }
        if (empty($page)) {
            $page = $this->PageNo();
            if (empty($page)) {
                return;
            }
        }
        $this->outlines[] = array('t' => $txt, 'l' => $level, 'x' => $x, 'y' => $y, 'p' => $page, 'f' => $fixed, 's' => strtoupper($style), 'c' => $color, 'u' => $link);
    }

    /**
     * Sort bookmarks for page and key.
     * @protected
     * @since 5.9.119 (2011-09-19)
     */
    protected function sortBookmarks() {
        // get sorting columns
        $outline_p = array();
        $outline_y = array();
        foreach ($this->outlines as $key => $row) {
            $outline_p[$key] = $row['p'];
            $outline_k[$key] = $key;
        }
        // sort outlines by page and original position
        array_multisort($outline_p, SORT_NUMERIC, SORT_ASC, $outline_k, SORT_NUMERIC, SORT_ASC, $this->outlines);
    }

    /**
     * Create a bookmark PDF string.
     * @protected
     * @author Olivier Plathey, Nicola Asuni
     * @since 2.1.002 (2008-02-12)
     */
    protected function _putbookmarks() {
        $nb = count($this->outlines);
        if ($nb == 0) {
            return;
        }
        // sort bookmarks
        $this->sortBookmarks();
        $lru = array();
        $level = 0;
        foreach ($this->outlines as $i => $o) {
            if ($o['l'] > 0) {
                $parent = $lru[($o['l'] - 1)];
                //Set parent and last pointers
                $this->outlines[$i]['parent'] = $parent;
                $this->outlines[$parent]['last'] = $i;
                if ($o['l'] > $level) {
                    //Level increasing: set first pointer
                    $this->outlines[$parent]['first'] = $i;
                }
            } else {
                $this->outlines[$i]['parent'] = $nb;
            }
            if (($o['l'] <= $level) AND ($i > 0)) {
                //Set prev and next pointers
                $prev = $lru[$o['l']];
                $this->outlines[$prev]['next'] = $i;
                $this->outlines[$i]['prev'] = $prev;
            }
            $lru[$o['l']] = $i;
            $level = $o['l'];
        }
        //Outline items
        $n = $this->n + 1;
        $nltags = '/<br[\s]?\/>|<\/(blockquote|dd|dl|div|dt|h1|h2|h3|h4|h5|h6|hr|li|ol|p|pre|ul|tcpdf|table|tr|td)>/si';
        foreach ($this->outlines as $i => $o) {
            $oid = $this->_newobj();
            // covert HTML title to string
            $title = preg_replace($nltags, "\n", $o['t']);
            $title = preg_replace("/[\r]+/si", '', $title);
            $title = preg_replace("/[\n]+/si", "\n", $title);
            $title = strip_tags($title);
            $title = $this->stringTrim($title);
            $out = '<</Title '.$this->_textstring($title, $oid);
            $out .= ' /Parent '.($n + $o['parent']).' 0 R';
            if (isset($o['prev'])) {
                $out .= ' /Prev '.($n + $o['prev']).' 0 R';
            }
            if (isset($o['next'])) {
                $out .= ' /Next '.($n + $o['next']).' 0 R';
            }
            if (isset($o['first'])) {
                $out .= ' /First '.($n + $o['first']).' 0 R';
            }
            if (isset($o['last'])) {
                $out .= ' /Last '.($n + $o['last']).' 0 R';
            }
            if (isset($o['u']) AND !empty($o['u'])) {
                // link
                if (is_string($o['u'])) {
                    if ($o['u'][0] == '#') {
                        // internal destination
                        $out .= ' /Dest /'.TCPDF_STATIC::encodeNameObject(substr($o['u'], 1));
                    } elseif ($o['u'][0] == '%') {
                        // embedded PDF file
                        $filename = basename(substr($o['u'], 1));
                        $out .= ' /A <</S /GoToE /D [0 /Fit] /NewWindow true /T << /R /C /P '.($o['p'] - 1).' /A '.$this->embeddedfiles[$filename]['a'].' >> >>';
                    } elseif ($o['u'][0] == '*') {
                        // embedded generic file
                        $filename = basename(substr($o['u'], 1));
                        $jsa = 'var D=event.target.doc;var MyData=D.dataObjects;for (var i in MyData) if (MyData[i].path=="'.$filename.'") D.exportDataObject( { cName : MyData[i].name, nLaunch : 2});';
                        $out .= ' /A <</S /JavaScript /JS '.$this->_textstring($jsa, $oid).'>>';
                    } else {
                        // external URI link
                        $out .= ' /A <</S /URI /URI '.$this->_datastring($this->unhtmlentities($o['u']), $oid).'>>';
                    }
                } elseif (isset($this->links[$o['u']])) {
                    // internal link ID
                    $l = $this->links[$o['u']];
                    if (isset($this->page_obj_id[($l['p'])])) {
                        $out .= sprintf(' /Dest [%u 0 R /XYZ 0 %F null]', $this->page_obj_id[($l['p'])], ($this->pagedim[$l['p']]['h'] - ($l['y'] * $this->k)));
                    }
                }
            } elseif (isset($this->page_obj_id[($o['p'])])) {
                // link to a page
                $out .= ' '.sprintf('/Dest [%u 0 R /XYZ %F %F null]', $this->page_obj_id[($o['p'])], ($o['x'] * $this->k), ($this->pagedim[$o['p']]['h'] - ($o['y'] * $this->k)));
            }
            // set font style
            $style = 0;
            if (!empty($o['s'])) {
                // bold
                if (strpos($o['s'], 'B') !== false) {
                    $style |= 2;
                }
                // oblique
                if (strpos($o['s'], 'I') !== false) {
                    $style |= 1;
                }
            }
            $out .= sprintf(' /F %d', $style);
            // set bookmark color
            if (isset($o['c']) AND is_array($o['c']) AND (count($o['c']) == 3)) {
                $color = array_values($o['c']);
                $out .= sprintf(' /C [%F %F %F]', ($color[0] / 255), ($color[1] / 255), ($color[2] / 255));
            } else {
                // black
                $out .= ' /C [0.0 0.0 0.0]';
            }
            $out .= ' /Count 0'; // normally closed item
            $out .= ' >>';
            $out .= "\n".'endobj';
            $this->_out($out);
        }
        //Outline root
        $this->OutlineRoot = $this->_newobj();
        $this->_out('<< /Type /Outlines /First '.$n.' 0 R /Last '.($n + $lru[0]).' 0 R >>'."\n".'endobj');
    }

    // --- JAVASCRIPT ------------------------------------------------------

    /**
     * Adds a javascript
     * @param $script (string) Javascript code
     * @public
     * @author Johannes G\FCntert, Nicola Asuni
     * @since 2.1.002 (2008-02-12)
     */
    public function IncludeJS($script) {
        $this->javascript .= $script;
    }

    /**
     * Adds a javascript object and return object ID
     * @param $script (string) Javascript code
     * @param $onload (boolean) if true executes this object when opening the document
     * @return int internal object ID
     * @public
     * @author Nicola Asuni
     * @since 4.8.000 (2009-09-07)
     */
    public function addJavascriptObject($script, $onload=false) {
        if ($this->pdfa_mode) {
            // javascript is not allowed in PDF/A mode
            return false;
        }
        ++$this->n;
        $this->js_objects[$this->n] = array('n' => $this->n, 'js' => $script, 'onload' => $onload);
        return $this->n;
    }

    /**
     * Create a javascript PDF string.
     * @protected
     * @author Johannes G\FCntert, Nicola Asuni
     * @since 2.1.002 (2008-02-12)
     */
    protected function _putjavascript() {
        if ($this->pdfa_mode OR (empty($this->javascript) AND empty($this->js_objects))) {
            return;
        }
        if (strpos($this->javascript, 'this.addField') > 0) {
            if (!$this->ur['enabled']) {
                //$this->setUserRights();
            }
            // the following two lines are used to avoid form fields duplication after saving
            // The addField method only works when releasing user rights (UR3)
            $jsa = sprintf("ftcpdfdocsaved=this.addField('%s','%s',%d,[%F,%F,%F,%F]);", 'tcpdfdocsaved', 'text', 0, 0, 1, 0, 1);
            $jsb = "getField('tcpdfdocsaved').value='saved';";
            $this->javascript = $jsa."\n".$this->javascript."\n".$jsb;
        }
        // name tree for javascript
        $this->n_js = '<< /Names [';
        if (!empty($this->javascript)) {
            $this->n_js .= ' (EmbeddedJS) '.($this->n + 1).' 0 R';
        }
        if (!empty($this->js_objects)) {
            foreach ($this->js_objects as $key => $val) {
                if ($val['onload']) {
                    $this->n_js .= ' (JS'.$key.') '.$key.' 0 R';
                }
            }
        }
        $this->n_js .= ' ] >>';
        // default Javascript object
        if (!empty($this->javascript)) {
            $obj_id = $this->_newobj();
            $out = '<< /S /JavaScript';
            $out .= ' /JS '.$this->_textstring($this->javascript, $obj_id);
            $out .= ' >>';
            $out .= "\n".'endobj';
            $this->_out($out);
        }
        // additional Javascript objects
        if (!empty($this->js_objects)) {
            foreach ($this->js_objects as $key => $val) {
                $out = $this->_getobj($key)."\n".' << /S /JavaScript /JS '.$this->_textstring($val['js'], $key).' >>'."\n".'endobj';
                $this->_out($out);
            }
        }
    }

    /**
     * Adds a javascript form field.
     * @param $type (string) field type
     * @param $name (string) field name
     * @param $x (int) horizontal position
     * @param $y (int) vertical position
     * @param $w (int) width
     * @param $h (int) height
     * @param $prop (array) javascript field properties. Possible values are described on official Javascript for Acrobat API reference.
     * @protected
     * @author Denis Van Nuffelen, Nicola Asuni
     * @since 2.1.002 (2008-02-12)
     */
    protected function _addfield($type, $name, $x, $y, $w, $h, $prop) {
        if ($this->rtl) {
            $x = $x - $w;
        }
        // the followind avoid fields duplication after saving the document
        $this->javascript .= "if (getField('tcpdfdocsaved').value != 'saved') {";
        $k = $this->k;
        $this->javascript .= sprintf("f".$name."=this.addField('%s','%s',%u,[%F,%F,%F,%F]);", $name, $type, $this->PageNo()-1, $x*$k, ($this->h-$y)*$k+1, ($x+$w)*$k, ($this->h-$y-$h)*$k+1)."\n";
        $this->javascript .= 'f'.$name.'.textSize='.$this->FontSizePt.";\n";
        while (list($key, $val) = each($prop)) {
            if (strcmp(substr($key, -5), 'Color') == 0) {
                $val = TCPDF_COLORS::_JScolor($val);
            } else {
                $val = "'".$val."'";
            }
            $this->javascript .= 'f'.$name.'.'.$key.'='.$val.";\n";
        }
        if ($this->rtl) {
            $this->x -= $w;
        } else {
            $this->x += $w;
        }
        $this->javascript .= '}';
    }

    // --- FORM FIELDS -----------------------------------------------------



    /**
     * Set default properties for form fields.
     * @param $prop (array) javascript field properties. Possible values are described on official Javascript for Acrobat API reference.
     * @public
     * @author Nicola Asuni
     * @since 4.8.000 (2009-09-06)
     */
    public function setFormDefaultProp($prop=array()) {
        $this->default_form_prop = $prop;
    }

    /**
     * Return the default properties for form fields.
     * @return array $prop javascript field properties. Possible values are described on official Javascript for Acrobat API reference.
     * @public
     * @author Nicola Asuni
     * @since 4.8.000 (2009-09-06)
     */
    public function getFormDefaultProp() {
        return $this->default_form_prop;
    }

    /**
     * Creates a text field
     * @param $name (string) field name
     * @param $w (float) Width of the rectangle
     * @param $h (float) Height of the rectangle
     * @param $prop (array) javascript field properties. Possible values are described on official Javascript for Acrobat API reference.
     * @param $opt (array) annotation parameters. Possible values are described on official PDF32000_2008 reference.
     * @param $x (float) Abscissa of the upper-left corner of the rectangle
     * @param $y (float) Ordinate of the upper-left corner of the rectangle
     * @param $js (boolean) if true put the field using JavaScript (requires Acrobat Writer to be rendered).
     * @public
     * @author Nicola Asuni
     * @since 4.8.000 (2009-09-07)
     */
    public function TextField($name, $w, $h, $prop=array(), $opt=array(), $x='', $y='', $js=false) {
        if ($x === '') {
            $x = $this->x;
        }
        if ($y === '') {
            $y = $this->y;
        }
        // check page for no-write regions and adapt page margins if necessary
        list($x, $y) = $this->checkPageRegions($h, $x, $y);
        if ($js) {
            $this->_addfield('text', $name, $x, $y, $w, $h, $prop);
            return;
        }
        // get default style
        $prop = array_merge($this->getFormDefaultProp(), $prop);
        // get annotation data
        $popt = TCPDF_STATIC::getAnnotOptFromJSProp($prop, $this->spot_colors, $this->rtl);
        // set default appearance stream
        $this->annotation_fonts[$this->CurrentFont['fontkey']] = $this->CurrentFont['i'];
        $fontstyle = sprintf('/F%d %F Tf %s', $this->CurrentFont['i'], $this->FontSizePt, $this->TextColor);
        $popt['da'] = $fontstyle;
        // build appearance stream
        $popt['ap'] = array();
        $popt['ap']['n'] = '/Tx BMC q '.$fontstyle.' ';
        $text = '';
        if (isset($prop['value']) AND !empty($prop['value'])) {
            $text = $prop['value'];
        } elseif (isset($opt['v']) AND !empty($opt['v'])) {
            $text = $opt['v'];
        }
        $tmpid = $this->startTemplate($w, $h, false);
        $align = '';
        if (isset($popt['q'])) {
            switch ($popt['q']) {
                case 0: {
                    $align = 'L';
                    break;
                }
                case 1: {
                    $align = 'C';
                    break;
                }
                case 2: {
                    $align = 'R';
                    break;
                }
                default: {
                    $align = '';
                    break;
                }
            }
        }
        $this->MultiCell($w, $h, $text, 0, $align, false, 0, 0, 0, true, 0, false, true, 0, 'T', false);
        $this->endTemplate();
        --$this->n;
        $popt['ap']['n'] .= $this->xobjects[$tmpid]['outdata'];
        unset($this->xobjects[$tmpid]);
        $popt['ap']['n'] .= 'Q EMC';
        // merge options
        $opt = array_merge($popt, $opt);
        // remove some conflicting options
        unset($opt['bs']);
        // set remaining annotation data
        $opt['Subtype'] = 'Widget';
        $opt['ft'] = 'Tx';
        $opt['t'] = $name;
        // Additional annotation's parameters (check _putannotsobj() method):
        //$opt['f']
        //$opt['as']
        //$opt['bs']
        //$opt['be']
        //$opt['c']
        //$opt['border']
        //$opt['h']
        //$opt['mk'];
        //$opt['mk']['r']
        //$opt['mk']['bc'];
        //$opt['mk']['bg'];
        unset($opt['mk']['ca']);
        unset($opt['mk']['rc']);
        unset($opt['mk']['ac']);
        unset($opt['mk']['i']);
        unset($opt['mk']['ri']);
        unset($opt['mk']['ix']);
        unset($opt['mk']['if']);
        //$opt['mk']['if']['sw'];
        //$opt['mk']['if']['s'];
        //$opt['mk']['if']['a'];
        //$opt['mk']['if']['fb'];
        unset($opt['mk']['tp']);
        //$opt['tu']
        //$opt['tm']
        //$opt['ff']
        //$opt['v']
        //$opt['dv']
        //$opt['a']
        //$opt['aa']
        //$opt['q']
        $this->Annotation($x, $y, $w, $h, $name, $opt, 0);
        if ($this->rtl) {
            $this->x -= $w;
        } else {
            $this->x += $w;
        }
    }

    /**
     * Creates a RadioButton field.
     * @param $name (string) Field name.
     * @param $w (int) Width of the radio button.
     * @param $prop (array) Javascript field properties. Possible values are described on official Javascript for Acrobat API reference.
     * @param $opt (array) Annotation parameters. Possible values are described on official PDF32000_2008 reference.
     * @param $onvalue (string) Value to be returned if selected.
     * @param $checked (boolean) Define the initial state.
     * @param $x (float) Abscissa of the upper-left corner of the rectangle
     * @param $y (float) Ordinate of the upper-left corner of the rectangle
     * @param $js (boolean) If true put the field using JavaScript (requires Acrobat Writer to be rendered).
     * @public
     * @author Nicola Asuni
     * @since 4.8.000 (2009-09-07)
     */
    public function RadioButton($name, $w, $prop=array(), $opt=array(), $onvalue='On', $checked=false, $x='', $y='', $js=false) {
        if ($x === '') {
            $x = $this->x;
        }
        if ($y === '') {
            $y = $this->y;
        }
        // check page for no-write regions and adapt page margins if necessary
        list($x, $y) = $this->checkPageRegions($w, $x, $y);
        if ($js) {
            $this->_addfield('radiobutton', $name, $x, $y, $w, $w, $prop);
            return;
        }
        if (TCPDF_STATIC::empty_string($onvalue)) {
            $onvalue = 'On';
        }
        if ($checked) {
            $defval = $onvalue;
        } else {
            $defval = 'Off';
        }
        // set font
        $font = 'zapfdingbats';
        if ($this->pdfa_mode) {
            // all fonts must be embedded
            $font = 'pdfa'.$font;
        }
        $this->AddFont($font);
        $tmpfont = $this->getFontBuffer($font);
        // set data for parent group
        if (!isset($this->radiobutton_groups[$this->page])) {
            $this->radiobutton_groups[$this->page] = array();
        }
        if (!isset($this->radiobutton_groups[$this->page][$name])) {
            $this->radiobutton_groups[$this->page][$name] = array();
            ++$this->n;
            $this->radiobutton_groups[$this->page][$name]['n'] = $this->n;
            $this->radio_groups[] = $this->n;
        }
        $kid = ($this->n + 1);
        // save object ID to be added on Kids entry on parent object
        $this->radiobutton_groups[$this->page][$name][] = array('kid' => $kid, 'def' => $defval);
        // get default style
        $prop = array_merge($this->getFormDefaultProp(), $prop);
        $prop['NoToggleToOff'] = 'true';
        $prop['Radio'] = 'true';
        $prop['borderStyle'] = 'inset';
        // get annotation data
        $popt = TCPDF_STATIC::getAnnotOptFromJSProp($prop, $this->spot_colors, $this->rtl);
        // set additional default options
        $this->annotation_fonts[$tmpfont['fontkey']] = $tmpfont['i'];
        $fontstyle = sprintf('/F%d %F Tf %s', $tmpfont['i'], $this->FontSizePt, $this->TextColor);
        $popt['da'] = $fontstyle;
        // build appearance stream
        $popt['ap'] = array();
        $popt['ap']['n'] = array();
        $fx = ((($w - $this->getAbsFontMeasure($tmpfont['cw'][108])) / 2) * $this->k);
        $fy = (($w - ((($tmpfont['desc']['Ascent'] - $tmpfont['desc']['Descent']) * $this->FontSizePt / 1000) / $this->k)) * $this->k);
        $popt['ap']['n'][$onvalue] = sprintf('q %s BT /F%d %F Tf %F %F Td ('.chr(108).') Tj ET Q', $this->TextColor, $tmpfont['i'], $this->FontSizePt, $fx, $fy);
        $popt['ap']['n']['Off'] = sprintf('q %s BT /F%d %F Tf %F %F Td ('.chr(109).') Tj ET Q', $this->TextColor, $tmpfont['i'], $this->FontSizePt, $fx, $fy);
        if (!isset($popt['mk'])) {
            $popt['mk'] = array();
        }
        $popt['mk']['ca'] = '(l)';
        // merge options
        $opt = array_merge($popt, $opt);
        // set remaining annotation data
        $opt['Subtype'] = 'Widget';
        $opt['ft'] = 'Btn';
        if ($checked) {
            $opt['v'] = array('/'.$onvalue);
            $opt['as'] = $onvalue;
        } else {
            $opt['as'] = 'Off';
        }
        // store readonly flag
        if (!isset($this->radiobutton_groups[$this->page][$name]['#readonly#'])) {
            $this->radiobutton_groups[$this->page][$name]['#readonly#'] = false;
        }
        $this->radiobutton_groups[$this->page][$name]['#readonly#'] |= ($opt['f'] & 64);
        $this->Annotation($x, $y, $w, $w, $name, $opt, 0);
        if ($this->rtl) {
            $this->x -= $w;
        } else {
            $this->x += $w;
        }
    }

    /**
     * Creates a List-box field
     * @param $name (string) field name
     * @param $w (int) width
     * @param $h (int) height
     * @param $values (array) array containing the list of values.
     * @param $prop (array) javascript field properties. Possible values are described on official Javascript for Acrobat API reference.
     * @param $opt (array) annotation parameters. Possible values are described on official PDF32000_2008 reference.
     * @param $x (float) Abscissa of the upper-left corner of the rectangle
     * @param $y (float) Ordinate of the upper-left corner of the rectangle
     * @param $js (boolean) if true put the field using JavaScript (requires Acrobat Writer to be rendered).
     * @public
     * @author Nicola Asuni
     * @since 4.8.000 (2009-09-07)
     */
    public function ListBox($name, $w, $h, $values, $prop=array(), $opt=array(), $x='', $y='', $js=false) {
        if ($x === '') {
            $x = $this->x;
        }
        if ($y === '') {
            $y = $this->y;
        }
        // check page for no-write regions and adapt page margins if necessary
        list($x, $y) = $this->checkPageRegions($h, $x, $y);
        if ($js) {
            $this->_addfield('listbox', $name, $x, $y, $w, $h, $prop);
            $s = '';
            foreach ($values as $value) {
                if (is_array($value)) {
                    $s .= ',[\''.addslashes($value[1]).'\',\''.addslashes($value[0]).'\']';
                } else {
                    $s .= ',[\''.addslashes($value).'\',\''.addslashes($value).'\']';
                }
            }
            $this->javascript .= 'f'.$name.'.setItems('.substr($s, 1).');'."\n";
            return;
        }
        // get default style
        $prop = array_merge($this->getFormDefaultProp(), $prop);
        // get annotation data
        $popt = TCPDF_STATIC::getAnnotOptFromJSProp($prop, $this->spot_colors, $this->rtl);
        // set additional default values
        $this->annotation_fonts[$this->CurrentFont['fontkey']] = $this->CurrentFont['i'];
        $fontstyle = sprintf('/F%d %F Tf %s', $this->CurrentFont['i'], $this->FontSizePt, $this->TextColor);
        $popt['da'] = $fontstyle;
        // build appearance stream
        $popt['ap'] = array();
        $popt['ap']['n'] = '/Tx BMC q '.$fontstyle.' ';
        $text = '';
        foreach($values as $item) {
            if (is_array($item)) {
                $text .= $item[1]."\n";
            } else {
                $text .= $item."\n";
            }
        }
        $tmpid = $this->startTemplate($w, $h, false);
        $this->MultiCell($w, $h, $text, 0, '', false, 0, 0, 0, true, 0, false, true, 0, 'T', false);
        $this->endTemplate();
        --$this->n;
        $popt['ap']['n'] .= $this->xobjects[$tmpid]['outdata'];
        unset($this->xobjects[$tmpid]);
        $popt['ap']['n'] .= 'Q EMC';
        // merge options
        $opt = array_merge($popt, $opt);
        // set remaining annotation data
        $opt['Subtype'] = 'Widget';
        $opt['ft'] = 'Ch';
        $opt['t'] = $name;
        $opt['opt'] = $values;
        unset($opt['mk']['ca']);
        unset($opt['mk']['rc']);
        unset($opt['mk']['ac']);
        unset($opt['mk']['i']);
        unset($opt['mk']['ri']);
        unset($opt['mk']['ix']);
        unset($opt['mk']['if']);
        unset($opt['mk']['tp']);
        $this->Annotation($x, $y, $w, $h, $name, $opt, 0);
        if ($this->rtl) {
            $this->x -= $w;
        } else {
            $this->x += $w;
        }
    }

    /**
     * Creates a Combo-box field
     * @param $name (string) field name
     * @param $w (int) width
     * @param $h (int) height
     * @param $values (array) array containing the list of values.
     * @param $prop (array) javascript field properties. Possible values are described on official Javascript for Acrobat API reference.
     * @param $opt (array) annotation parameters. Possible values are described on official PDF32000_2008 reference.
     * @param $x (float) Abscissa of the upper-left corner of the rectangle
     * @param $y (float) Ordinate of the upper-left corner of the rectangle
     * @param $js (boolean) if true put the field using JavaScript (requires Acrobat Writer to be rendered).
     * @public
     * @author Nicola Asuni
     * @since 4.8.000 (2009-09-07)
     */
    public function ComboBox($name, $w, $h, $values, $prop=array(), $opt=array(), $x='', $y='', $js=false) {
        if ($x === '') {
            $x = $this->x;
        }
        if ($y === '') {
            $y = $this->y;
        }
        // check page for no-write regions and adapt page margins if necessary
        list($x, $y) = $this->checkPageRegions($h, $x, $y);
        if ($js) {
            $this->_addfield('combobox', $name, $x, $y, $w, $h, $prop);
            $s = '';
            foreach ($values as $value) {
                if (is_array($value)) {
                    $s .= ',[\''.addslashes($value[1]).'\',\''.addslashes($value[0]).'\']';
                } else {
                    $s .= ',[\''.addslashes($value).'\',\''.addslashes($value).'\']';
                }
            }
            $this->javascript .= 'f'.$name.'.setItems('.substr($s, 1).');'."\n";
            return;
        }
        // get default style
        $prop = array_merge($this->getFormDefaultProp(), $prop);
        $prop['Combo'] = true;
        // get annotation data
        $popt = TCPDF_STATIC::getAnnotOptFromJSProp($prop, $this->spot_colors, $this->rtl);
        // set additional default options
        $this->annotation_fonts[$this->CurrentFont['fontkey']] = $this->CurrentFont['i'];
        $fontstyle = sprintf('/F%d %F Tf %s', $this->CurrentFont['i'], $this->FontSizePt, $this->TextColor);
        $popt['da'] = $fontstyle;
        // build appearance stream
        $popt['ap'] = array();
        $popt['ap']['n'] = '/Tx BMC q '.$fontstyle.' ';
        $text = '';
        foreach($values as $item) {
            if (is_array($item)) {
                $text .= $item[1]."\n";
            } else {
                $text .= $item."\n";
            }
        }
        $tmpid = $this->startTemplate($w, $h, false);
        $this->MultiCell($w, $h, $text, 0, '', false, 0, 0, 0, true, 0, false, true, 0, 'T', false);
        $this->endTemplate();
        --$this->n;
        $popt['ap']['n'] .= $this->xobjects[$tmpid]['outdata'];
        unset($this->xobjects[$tmpid]);
        $popt['ap']['n'] .= 'Q EMC';
        // merge options
        $opt = array_merge($popt, $opt);
        // set remaining annotation data
        $opt['Subtype'] = 'Widget';
        $opt['ft'] = 'Ch';
        $opt['t'] = $name;
        $opt['opt'] = $values;
        unset($opt['mk']['ca']);
        unset($opt['mk']['rc']);
        unset($opt['mk']['ac']);
        unset($opt['mk']['i']);
        unset($opt['mk']['ri']);
        unset($opt['mk']['ix']);
        unset($opt['mk']['if']);
        unset($opt['mk']['tp']);
        $this->Annotation($x, $y, $w, $h, $name, $opt, 0);
        if ($this->rtl) {
            $this->x -= $w;
        } else {
            $this->x += $w;
        }
    }

    /**
     * Creates a CheckBox field
     * @param $name (string) field name
     * @param $w (int) width
     * @param $checked (boolean) define the initial state.
     * @param $prop (array) javascript field properties. Possible values are described on official Javascript for Acrobat API reference.
     * @param $opt (array) annotation parameters. Possible values are described on official PDF32000_2008 reference.
     * @param $onvalue (string) value to be returned if selected.
     * @param $x (float) Abscissa of the upper-left corner of the rectangle
     * @param $y (float) Ordinate of the upper-left corner of the rectangle
     * @param $js (boolean) if true put the field using JavaScript (requires Acrobat Writer to be rendered).
     * @public
     * @author Nicola Asuni
     * @since 4.8.000 (2009-09-07)
     */
    public function CheckBox($name, $w, $checked=false, $prop=array(), $opt=array(), $onvalue='Yes', $x='', $y='', $js=false) {
        if ($x === '') {
            $x = $this->x;
        }
        if ($y === '') {
            $y = $this->y;
        }
        // check page for no-write regions and adapt page margins if necessary
        list($x, $y) = $this->checkPageRegions($w, $x, $y);
        if ($js) {
            $this->_addfield('checkbox', $name, $x, $y, $w, $w, $prop);
            return;
        }
        if (!isset($prop['value'])) {
            $prop['value'] = array('Yes');
        }
        // get default style
        $prop = array_merge($this->getFormDefaultProp(), $prop);
        $prop['borderStyle'] = 'inset';
        // get annotation data
        $popt = TCPDF_STATIC::getAnnotOptFromJSProp($prop, $this->spot_colors, $this->rtl);
        // set additional default options
        $font = 'zapfdingbats';
        if ($this->pdfa_mode) {
            // all fonts must be embedded
            $font = 'pdfa'.$font;
        }
        $this->AddFont($font);
        $tmpfont = $this->getFontBuffer($font);
        $this->annotation_fonts[$tmpfont['fontkey']] = $tmpfont['i'];
        $fontstyle = sprintf('/F%d %F Tf %s', $tmpfont['i'], $this->FontSizePt, $this->TextColor);
        $popt['da'] = $fontstyle;
        // build appearance stream
        $popt['ap'] = array();
        $popt['ap']['n'] = array();
        $fx = ((($w - $this->getAbsFontMeasure($tmpfont['cw'][110])) / 2) * $this->k);
        $fy = (($w - ((($tmpfont['desc']['Ascent'] - $tmpfont['desc']['Descent']) * $this->FontSizePt / 1000) / $this->k)) * $this->k);
        $popt['ap']['n']['Yes'] = sprintf('q %s BT /F%d %F Tf %F %F Td ('.chr(110).') Tj ET Q', $this->TextColor, $tmpfont['i'], $this->FontSizePt, $fx, $fy);
        $popt['ap']['n']['Off'] = sprintf('q %s BT /F%d %F Tf %F %F Td ('.chr(111).') Tj ET Q', $this->TextColor, $tmpfont['i'], $this->FontSizePt, $fx, $fy);
        // merge options
        $opt = array_merge($popt, $opt);
        // set remaining annotation data
        $opt['Subtype'] = 'Widget';
        $opt['ft'] = 'Btn';
        $opt['t'] = $name;
        if (TCPDF_STATIC::empty_string($onvalue)) {
            $onvalue = 'Yes';
        }
        $opt['opt'] = array($onvalue);
        if ($checked) {
            $opt['v'] = array('/Yes');
            $opt['as'] = 'Yes';
        } else {
            $opt['v'] = array('/Off');
            $opt['as'] = 'Off';
        }
        $this->Annotation($x, $y, $w, $w, $name, $opt, 0);
        if ($this->rtl) {
            $this->x -= $w;
        } else {
            $this->x += $w;
        }
    }

    /**
     * Creates a button field
     * @param $name (string) field name
     * @param $w (int) width
     * @param $h (int) height
     * @param $caption (string) caption.
     * @param $action (mixed) action triggered by pressing the button. Use a string to specify a javascript action. Use an array to specify a form action options as on section 12.7.5 of PDF32000_2008.
     * @param $prop (array) javascript field properties. Possible values are described on official Javascript for Acrobat API reference.
     * @param $opt (array) annotation parameters. Possible values are described on official PDF32000_2008 reference.
     * @param $x (float) Abscissa of the upper-left corner of the rectangle
     * @param $y (float) Ordinate of the upper-left corner of the rectangle
     * @param $js (boolean) if true put the field using JavaScript (requires Acrobat Writer to be rendered).
     * @public
     * @author Nicola Asuni
     * @since 4.8.000 (2009-09-07)
     */
    public function Button($name, $w, $h, $caption, $action, $prop=array(), $opt=array(), $x='', $y='', $js=false) {
        if ($x === '') {
            $x = $this->x;
        }
        if ($y === '') {
            $y = $this->y;
        }
        // check page for no-write regions and adapt page margins if necessary
        list($x, $y) = $this->checkPageRegions($h, $x, $y);
        if ($js) {
            $this->_addfield('button', $name, $this->x, $this->y, $w, $h, $prop);
            $this->javascript .= 'f'.$name.".buttonSetCaption('".addslashes($caption)."');\n";
            $this->javascript .= 'f'.$name.".setAction('MouseUp','".addslashes($action)."');\n";
            $this->javascript .= 'f'.$name.".highlight='push';\n";
            $this->javascript .= 'f'.$name.".print=false;\n";
            return;
        }
        // get default style
        $prop = array_merge($this->getFormDefaultProp(), $prop);
        $prop['Pushbutton'] = 'true';
        $prop['highlight'] = 'push';
        $prop['display'] = 'display.noPrint';
        // get annotation data
        $popt = TCPDF_STATIC::getAnnotOptFromJSProp($prop, $this->spot_colors, $this->rtl);
        $this->annotation_fonts[$this->CurrentFont['fontkey']] = $this->CurrentFont['i'];
        $fontstyle = sprintf('/F%d %F Tf %s', $this->CurrentFont['i'], $this->FontSizePt, $this->TextColor);
        $popt['da'] = $fontstyle;
        // build appearance stream
        $popt['ap'] = array();
        $popt['ap']['n'] = '/Tx BMC q '.$fontstyle.' ';
        $tmpid = $this->startTemplate($w, $h, false);
        $bw = (2 / $this->k); // border width
        $border = array(
            'L' => array('width' => $bw, 'cap' => 'square', 'join' => 'miter', 'dash' => 0, 'color' => array(231)),
            'R' => array('width' => $bw, 'cap' => 'square', 'join' => 'miter', 'dash' => 0, 'color' => array(51)),
            'T' => array('width' => $bw, 'cap' => 'square', 'join' => 'miter', 'dash' => 0, 'color' => array(231)),
            'B' => array('width' => $bw, 'cap' => 'square', 'join' => 'miter', 'dash' => 0, 'color' => array(51)));
        $this->SetFillColor(204);
        $this->Cell($w, $h, $caption, $border, 0, 'C', true, '', 1, false, 'T', 'M');
        $this->endTemplate();
        --$this->n;
        $popt['ap']['n'] .= $this->xobjects[$tmpid]['outdata'];
        unset($this->xobjects[$tmpid]);
        $popt['ap']['n'] .= 'Q EMC';
        // set additional default options
        if (!isset($popt['mk'])) {
            $popt['mk'] = array();
        }
        $ann_obj_id = ($this->n + 1);
        if (!empty($action) AND !is_array($action)) {
            $ann_obj_id = ($this->n + 2);
        }
        $popt['mk']['ca'] = $this->_textstring($caption, $ann_obj_id);
        $popt['mk']['rc'] = $this->_textstring($caption, $ann_obj_id);
        $popt['mk']['ac'] = $this->_textstring($caption, $ann_obj_id);
        // merge options
        $opt = array_merge($popt, $opt);
        // set remaining annotation data
        $opt['Subtype'] = 'Widget';
        $opt['ft'] = 'Btn';
        $opt['t'] = $caption;
        $opt['v'] = $name;
        if (!empty($action)) {
            if (is_array($action)) {
                // form action options as on section 12.7.5 of PDF32000_2008.
                $opt['aa'] = '/D <<';
                $bmode = array('SubmitForm', 'ResetForm', 'ImportData');
                foreach ($action AS $key => $val) {
                    if (($key == 'S') AND in_array($val, $bmode)) {
                        $opt['aa'] .= ' /S /'.$val;
                    } elseif (($key == 'F') AND (!empty($val))) {
                        $opt['aa'] .= ' /F '.$this->_datastring($val, $ann_obj_id);
                    } elseif (($key == 'Fields') AND is_array($val) AND !empty($val)) {
                        $opt['aa'] .= ' /Fields [';
                        foreach ($val AS $field) {
                            $opt['aa'] .= ' '.$this->_textstring($field, $ann_obj_id);
                        }
                        $opt['aa'] .= ']';
                    } elseif (($key == 'Flags')) {
                        $ff = 0;
                        if (is_array($val)) {
                            foreach ($val AS $flag) {
                                switch ($flag) {
                                    case 'Include/Exclude': {
                                        $ff += 1 << 0;
                                        break;
                                    }
                                    case 'IncludeNoValueFields': {
                                        $ff += 1 << 1;
                                        break;
                                    }
                                    case 'ExportFormat': {
                                        $ff += 1 << 2;
                                        break;
                                    }
                                    case 'GetMethod': {
                                        $ff += 1 << 3;
                                        break;
                                    }
                                    case 'SubmitCoordinates': {
                                        $ff += 1 << 4;
                                        break;
                                    }
                                    case 'XFDF': {
                                        $ff += 1 << 5;
                                        break;
                                    }
                                    case 'IncludeAppendSaves': {
                                        $ff += 1 << 6;
                                        break;
                                    }
                                    case 'IncludeAnnotations': {
                                        $ff += 1 << 7;
                                        break;
                                    }
                                    case 'SubmitPDF': {
                                        $ff += 1 << 8;
                                        break;
                                    }
                                    case 'CanonicalFormat': {
                                        $ff += 1 << 9;
                                        break;
                                    }
                                    case 'ExclNonUserAnnots': {
                                        $ff += 1 << 10;
                                        break;
                                    }
                                    case 'ExclFKey': {
                                        $ff += 1 << 11;
                                        break;
                                    }
                                    case 'EmbedForm': {
                                        $ff += 1 << 13;
                                        break;
                                    }
                                }
                            }
                        } else {
                            $ff = intval($val);
                        }
                        $opt['aa'] .= ' /Flags '.$ff;
                    }
                }
                $opt['aa'] .= ' >>';
            } else {
                // Javascript action or raw action command
                $js_obj_id = $this->addJavascriptObject($action);
                $opt['aa'] = '/D '.$js_obj_id.' 0 R';
            }
        }
        $this->Annotation($x, $y, $w, $h, $name, $opt, 0);
        if ($this->rtl) {
            $this->x -= $w;
        } else {
            $this->x += $w;
        }
    }

    // --- END FORMS FIELDS ------------------------------------------------

    /**
     * Add certification signature (DocMDP or UR3)
     * You can set only one signature type
     * @protected
     * @author Nicola Asuni
     * @since 4.6.008 (2009-05-07)
     */
    protected function _putsignature() {
        if ((!$this->sign) OR (!isset($this->signature_data['cert_type']))) {
            return;
        }
        $sigobjid = ($this->sig_obj_id + 1);
        $out = $this->_getobj($sigobjid)."\n";
        $out .= '<< /Type /Sig';
        $out .= ' /Filter /Adobe.PPKLite';
        $out .= ' /SubFilter /adbe.pkcs7.detached';
        $out .= ' '.TCPDF_STATIC::$byterange_string;
        $out .= ' /Contents<'.str_repeat('0', $this->signature_max_length).'>';
        if (empty($this->signature_data['approval']) OR ($this->signature_data['approval'] != 'A')) {
            $out .= ' /Reference ['; // array of signature reference dictionaries
            $out .= ' << /Type /SigRef';
            if ($this->signature_data['cert_type'] > 0) {
                $out .= ' /TransformMethod /DocMDP';
                $out .= ' /TransformParams <<';
                $out .= ' /Type /TransformParams';
                $out .= ' /P '.$this->signature_data['cert_type'];
                $out .= ' /V /1.2';
            } else {
                $out .= ' /TransformMethod /UR3';
                $out .= ' /TransformParams <<';
                $out .= ' /Type /TransformParams';
                $out .= ' /V /2.2';
                if (!TCPDF_STATIC::empty_string($this->ur['document'])) {
                    $out .= ' /Document['.$this->ur['document'].']';
                }
                if (!TCPDF_STATIC::empty_string($this->ur['form'])) {
                    $out .= ' /Form['.$this->ur['form'].']';
                }
                if (!TCPDF_STATIC::empty_string($this->ur['signature'])) {
                    $out .= ' /Signature['.$this->ur['signature'].']';
                }
                if (!TCPDF_STATIC::empty_string($this->ur['annots'])) {
                    $out .= ' /Annots['.$this->ur['annots'].']';
                }
                if (!TCPDF_STATIC::empty_string($this->ur['ef'])) {
                    $out .= ' /EF['.$this->ur['ef'].']';
                }
                if (!TCPDF_STATIC::empty_string($this->ur['formex'])) {
                    $out .= ' /FormEX['.$this->ur['formex'].']';
                }
            }
            $out .= ' >>'; // close TransformParams
            // optional digest data (values must be calculated and replaced later)
            //$out .= ' /Data ********** 0 R';
            //$out .= ' /DigestMethod/MD5';
            //$out .= ' /DigestLocation[********** 34]';
            //$out .= ' /DigestValue<********************************>';
            $out .= ' >>';
            $out .= ' ]'; // end of reference
        }
        if (isset($this->signature_data['info']['Name']) AND !TCPDF_STATIC::empty_string($this->signature_data['info']['Name'])) {
            $out .= ' /Name '.$this->_textstring($this->signature_data['info']['Name'], $sigobjid);
        }
        if (isset($this->signature_data['info']['Location']) AND !TCPDF_STATIC::empty_string($this->signature_data['info']['Location'])) {
            $out .= ' /Location '.$this->_textstring($this->signature_data['info']['Location'], $sigobjid);
        }
        if (isset($this->signature_data['info']['Reason']) AND !TCPDF_STATIC::empty_string($this->signature_data['info']['Reason'])) {
            $out .= ' /Reason '.$this->_textstring($this->signature_data['info']['Reason'], $sigobjid);
        }
        if (isset($this->signature_data['info']['ContactInfo']) AND !TCPDF_STATIC::empty_string($this->signature_data['info']['ContactInfo'])) {
            $out .= ' /ContactInfo '.$this->_textstring($this->signature_data['info']['ContactInfo'], $sigobjid);
        }
        $out .= ' /M '.$this->_datestring($sigobjid, $this->doc_modification_timestamp);
        $out .= ' >>';
        $out .= "\n".'endobj';
        $this->_out($out);
    }

    /**
     * Set User's Rights for PDF Reader
     * WARNING: This is experimental and currently do not work.
     * Check the PDF Reference 8.7.1 Transform Methods,
     * Table 8.105 Entries in the UR transform parameters dictionary
     * @param $enable (boolean) if true enable user's rights on PDF reader
     * @param $document (string) Names specifying additional document-wide usage rights for the document. The only defined value is "/FullSave", which permits a user to save the document along with modified form and/or annotation data.
     * @param $annots (string) Names specifying additional annotation-related usage rights for the document. Valid names in PDF 1.5 and later are /Create/Delete/Modify/Copy/Import/Export, which permit the user to perform the named operation on annotations.
     * @param $form (string) Names specifying additional form-field-related usage rights for the document. Valid names are: /Add/Delete/FillIn/Import/Export/SubmitStandalone/SpawnTemplate
     * @param $signature (string) Names specifying additional signature-related usage rights for the document. The only defined value is /Modify, which permits a user to apply a digital signature to an existing signature form field or clear a signed signature form field.
     * @param $ef (string) Names specifying additional usage rights for named embedded files in the document. Valid names are /Create/Delete/Modify/Import, which permit the user to perform the named operation on named embedded files
     Names specifying additional embedded-files-related usage rights for the document.
     * @param $formex (string) Names specifying additional form-field-related usage rights. The only valid name is BarcodePlaintext, which permits text form field data to be encoded as a plaintext two-dimensional barcode.
     * @public
     * @author Nicola Asuni
     * @since 2.9.000 (2008-03-26)
     */
    public function setUserRights(
            $enable=true,
            $document='/FullSave',
            $annots='/Create/Delete/Modify/Copy/Import/Export',
            $form='/Add/Delete/FillIn/Import/Export/SubmitStandalone/SpawnTemplate',
            $signature='/Modify',
            $ef='/Create/Delete/Modify/Import',
            $formex='') {
        $this->ur['enabled'] = $enable;
        $this->ur['document'] = $document;
        $this->ur['annots'] = $annots;
        $this->ur['form'] = $form;
        $this->ur['signature'] = $signature;
        $this->ur['ef'] = $ef;
        $this->ur['formex'] = $formex;
        if (!$this->sign) {
            $this->setSignature('', '', '', '', 0, array());
        }
    }

    /**
     * Enable document signature (requires the OpenSSL Library).
     * The digital signature improve document authenticity and integrity and allows o enable extra features on Acrobat Reader.
     * To create self-signed signature: openssl req -x509 -nodes -days 365000 -newkey rsa:1024 -keyout tcpdf.crt -out tcpdf.crt
     * To export crt to p12: openssl pkcs12 -export -in tcpdf.crt -out tcpdf.p12
     * To convert pfx certificate to pem: openssl pkcs12 -in tcpdf.pfx -out tcpdf.crt -nodes
     * @param $signing_cert (mixed) signing certificate (string or filename prefixed with 'file://')
     * @param $private_key (mixed) private key (string or filename prefixed with 'file://')
     * @param $private_key_password (string) password
     * @param $extracerts (string) specifies the name of a file containing a bunch of extra certificates to include in the signature which can for example be used to help the recipient to verify the certificate that you used.
     * @param $cert_type (int) The access permissions granted for this document. Valid values shall be: 1 = No changes to the document shall be permitted; any change to the document shall invalidate the signature; 2 = Permitted changes shall be filling in forms, instantiating page templates, and signing; other changes shall invalidate the signature; 3 = Permitted changes shall be the same as for 2, as well as annotation creation, deletion, and modification; other changes shall invalidate the signature.
     * @param $info (array) array of option information: Name, Location, Reason, ContactInfo.
     * @param $approval (string) Enable approval signature eg. for PDF incremental update
     * @public
     * @author Nicola Asuni
     * @since 4.6.005 (2009-04-24)
     */
    public function setSignature($signing_cert='', $private_key='', $private_key_password='', $extracerts='', $cert_type=2, $info=array(), $approval='') {
        // to create self-signed signature: openssl req -x509 -nodes -days 365000 -newkey rsa:1024 -keyout tcpdf.crt -out tcpdf.crt
        // to export crt to p12: openssl pkcs12 -export -in tcpdf.crt -out tcpdf.p12
        // to convert pfx certificate to pem: openssl
        //     OpenSSL> pkcs12 -in <cert.pfx> -out <cert.crt> -nodes
        $this->sign = true;
        ++$this->n;
        $this->sig_obj_id = $this->n; // signature widget
        ++$this->n; // signature object ($this->sig_obj_id + 1)
        $this->signature_data = array();
        if (strlen($signing_cert) == 0) {
            $this->Error('Please provide a certificate file and password!');
        }
        if (strlen($private_key) == 0) {
            $private_key = $signing_cert;
        }
        $this->signature_data['signcert'] = $signing_cert;
        $this->signature_data['privkey'] = $private_key;
        $this->signature_data['password'] = $private_key_password;
        $this->signature_data['extracerts'] = $extracerts;
        $this->signature_data['cert_type'] = $cert_type;
        $this->signature_data['info'] = $info;
        $this->signature_data['approval'] = $approval;
    }

    /**
     * Set the digital signature appearance (a cliccable rectangle area to get signature properties)
     * @param $x (float) Abscissa of the upper-left corner.
     * @param $y (float) Ordinate of the upper-left corner.
     * @param $w (float) Width of the signature area.
     * @param $h (float) Height of the signature area.
     * @param $page (int) option page number (if < 0 the current page is used).
     * @param $name (string) Name of the signature.
     * @public
     * @author Nicola Asuni
     * @since 5.3.011 (2010-06-17)
     */
    public function setSignatureAppearance($x=0, $y=0, $w=0, $h=0, $page=-1, $name='') {
        $this->signature_appearance = $this->getSignatureAppearanceArray($x, $y, $w, $h, $page, $name);
    }

    /**
     * Add an empty digital signature appearance (a cliccable rectangle area to get signature properties)
     * @param $x (float) Abscissa of the upper-left corner.
     * @param $y (float) Ordinate of the upper-left corner.
     * @param $w (float) Width of the signature area.
     * @param $h (float) Height of the signature area.
     * @param $page (int) option page number (if < 0 the current page is used).
     * @param $name (string) Name of the signature.
     * @public
     * @author Nicola Asuni
     * @since 5.9.101 (2011-07-06)
     */
    public function addEmptySignatureAppearance($x=0, $y=0, $w=0, $h=0, $page=-1, $name='') {
        ++$this->n;
        $this->empty_signature_appearance[] = array('objid' => $this->n) + $this->getSignatureAppearanceArray($x, $y, $w, $h, $page, $name);
    }

    /**
     * Get the array that defines the signature appearance (page and rectangle coordinates).
     * @param $x (float) Abscissa of the upper-left corner.
     * @param $y (float) Ordinate of the upper-left corner.
     * @param $w (float) Width of the signature area.
     * @param $h (float) Height of the signature area.
     * @param $page (int) option page number (if < 0 the current page is used).
     * @param $name (string) Name of the signature.
     * @return (array) Array defining page and rectangle coordinates of signature appearance.
     * @protected
     * @author Nicola Asuni
     * @since 5.9.101 (2011-07-06)
     */
    protected function getSignatureAppearanceArray($x=0, $y=0, $w=0, $h=0, $page=-1, $name='') {
        $sigapp = array();
        if (($page < 1) OR ($page > $this->numpages)) {
            $sigapp['page'] = $this->page;
        } else {
            $sigapp['page'] = intval($page);
        }
        if (empty($name)) {
            $sigapp['name'] = 'Signature';
        } else {
            $sigapp['name'] = $name;
        }
        $a = $x * $this->k;
        $b = $this->pagedim[($sigapp['page'])]['h'] - (($y + $h) * $this->k);
        $c = $w * $this->k;
        $d = $h * $this->k;
        $sigapp['rect'] = sprintf('%F %F %F %F', $a, $b, ($a + $c), ($b + $d));
        return $sigapp;
    }

    /**
     * Enable document timestamping (requires the OpenSSL Library).
     * The trusted timestamping improve document security that means that no one should be able to change the document once it has been recorded.
     * Use with digital signature only!
     * @param $tsa_host (string) Time Stamping Authority (TSA) server (prefixed with 'https://')
     * @param $tsa_username (string) Specifies the username for TSA authorization (optional) OR specifies the TSA authorization PEM file (see: example_66.php, optional)
     * @param $tsa_password (string) Specifies the password for TSA authorization (optional)
     * @param $tsa_cert (string) Specifies the location of TSA certificate for authorization (optional for cURL)
     * @public
     * @author Richard Stockinger
     * @since 6.0.090 (2014-06-16)
     */
    public function setTimeStamp($tsa_host='', $tsa_username='', $tsa_password='', $tsa_cert='') {
        $this->tsa_data = array();
        if (!function_exists('curl_init')) {
            $this->Error('Please enable cURL PHP extension!');
        }
        if (strlen($tsa_host) == 0) {
            $this->Error('Please specify the host of Time Stamping Authority (TSA)!');
        }
        $this->tsa_data['tsa_host'] = $tsa_host;
        if (is_file($tsa_username)) {
            $this->tsa_data['tsa_auth'] = $tsa_username;
        } else {
            $this->tsa_data['tsa_username'] = $tsa_username;
        }
        $this->tsa_data['tsa_password'] = $tsa_password;
        $this->tsa_data['tsa_cert'] = $tsa_cert;
        $this->tsa_timestamp = true;
    }

    /**
     * NOT YET IMPLEMENTED
     * Request TSA for a timestamp
     * @param $signature (string) Digital signature as binary string
     * @return (string) Timestamped digital signature
     * @protected
     * @author Richard Stockinger
     * @since 6.0.090 (2014-06-16)
     */
    protected function applyTSA($signature) {
        if (!$this->tsa_timestamp) {
            return $signature;
        }
        //@TODO: implement this feature
        return $signature;
    }

    /**
     * Create a new page group.
     * NOTE: call this function before calling AddPage()
     * @param $page (int) starting group page (leave empty for next page).
     * @public
     * @since 3.0.000 (2008-03-27)
     */
    public function startPageGroup($page='') {
        if (empty($page)) {
            $page = $this->page + 1;
        }
        $this->newpagegroup[$page] = sizeof($this->newpagegroup) + 1;
    }

    /**
     * Set the starting page number.
     * @param $num (int) Starting page number.
     * @since 5.9.093 (2011-06-16)
     * @public
     */
    public function setStartingPageNumber($num=1) {
        $this->starting_page_number = max(0, intval($num));
    }

    /**
     * Returns the string alias used right align page numbers.
     * If the current font is unicode type, the returned string wil contain an additional open curly brace.
     * @return string
     * @since 5.9.099 (2011-06-27)
     * @public
     */
    public function getAliasRightShift() {
        // calculate aproximatively the ratio between widths of aliases and replacements.
        $ref = '{'.TCPDF_STATIC::$alias_right_shift.'}{'.TCPDF_STATIC::$alias_tot_pages.'}{'.TCPDF_STATIC::$alias_num_page.'}';
        $rep = str_repeat(' ', $this->GetNumChars($ref));
        $wrep = $this->GetStringWidth($rep);
        if ($wrep > 0) {
            $wdiff = max(1, ($this->GetStringWidth($ref) / $wrep));
        } else {
            $wdiff = 1;
        }
        $sdiff = sprintf('%F', $wdiff);
        $alias = TCPDF_STATIC::$alias_right_shift.$sdiff.'}';
        if ($this->isUnicodeFont()) {
            $alias = '{'.$alias;
        }
        return $alias;
    }

    /**
     * Returns the string alias used for the total number of pages.
     * If the current font is unicode type, the returned string is surrounded by additional curly braces.
     * This alias will be replaced by the total number of pages in the document.
     * @return string
     * @since 4.0.018 (2008-08-08)
     * @public
     */
    public function getAliasNbPages() {
        if ($this->isUnicodeFont()) {
            return '{'.TCPDF_STATIC::$alias_tot_pages.'}';
        }
        return TCPDF_STATIC::$alias_tot_pages;
    }

    /**
     * Returns the string alias used for the page number.
     * If the current font is unicode type, the returned string is surrounded by additional curly braces.
     * This alias will be replaced by the page number.
     * @return string
     * @since 4.5.000 (2009-01-02)
     * @public
     */
    public function getAliasNumPage() {
        if ($this->isUnicodeFont()) {
            return '{'.TCPDF_STATIC::$alias_num_page.'}';
        }
        return TCPDF_STATIC::$alias_num_page;
    }

    /**
     * Return the alias for the total number of pages in the current page group.
     * If the current font is unicode type, the returned string is surrounded by additional curly braces.
     * This alias will be replaced by the total number of pages in this group.
     * @return alias of the current page group
     * @public
     * @since 3.0.000 (2008-03-27)
     */
    public function getPageGroupAlias() {
        if ($this->isUnicodeFont()) {
            return '{'.TCPDF_STATIC::$alias_group_tot_pages.'}';
        }
        return TCPDF_STATIC::$alias_group_tot_pages;
    }

    /**
     * Return the alias for the page number on the current page group.
     * If the current font is unicode type, the returned string is surrounded by additional curly braces.
     * This alias will be replaced by the page number (relative to the belonging group).
     * @return alias of the current page group
     * @public
     * @since 4.5.000 (2009-01-02)
     */
    public function getPageNumGroupAlias() {
        if ($this->isUnicodeFont()) {
            return '{'.TCPDF_STATIC::$alias_group_num_page.'}';
        }
        return TCPDF_STATIC::$alias_group_num_page;
    }

    /**
     * Return the current page in the group.
     * @return current page in the group
     * @public
     * @since 3.0.000 (2008-03-27)
     */
    public function getGroupPageNo() {
        return $this->pagegroups[$this->currpagegroup];
    }

    /**
     * Returns the current group page number formatted as a string.
     * @public
     * @since 4.3.003 (2008-11-18)
     * @see PaneNo(), formatPageNumber()
     */
    public function getGroupPageNoFormatted() {
        return TCPDF_STATIC::formatPageNumber($this->getGroupPageNo());
    }

    /**
     * Returns the current page number formatted as a string.
     * @public
     * @since 4.2.005 (2008-11-06)
     * @see PaneNo(), formatPageNumber()
     */
    public function PageNoFormatted() {
        return TCPDF_STATIC::formatPageNumber($this->PageNo());
    }

    /**
     * Put pdf layers.
     * @protected
     * @since 3.0.000 (2008-03-27)
     */
    protected function _putocg() {
        if (empty($this->pdflayers)) {
            return;
        }
        foreach ($this->pdflayers as $key => $layer) {
             $this->pdflayers[$key]['objid'] = $this->_newobj();
             $out = '<< /Type /OCG';
             $out .= ' /Name '.$this->_textstring($layer['name'], $this->pdflayers[$key]['objid']);
             $out .= ' /Usage <<';
             if (isset($layer['print']) AND ($layer['print'] !== NULL)) {
                $out .= ' /Print <</PrintState /'.($layer['print']?'ON':'OFF').'>>';
             }
             $out .= ' /View <</ViewState /'.($layer['view']?'ON':'OFF').'>>';
             $out .= ' >> >>';
             $out .= "\n".'endobj';
             $this->_out($out);
        }
    }

    /**
     * Start a new pdf layer.
     * @param $name (string) Layer name (only a-z letters and numbers). Leave empty for automatic name.
     * @param $print (boolean|null) Set to TRUE to print this layer, FALSE to not print and NULL to not set this option
     * @param $view (boolean) Set to true to view this layer.
     * @param $lock (boolean) If true lock the layer
     * @public
     * @since 5.9.102 (2011-07-13)
     */
    public function startLayer($name='', $print=true, $view=true, $lock=true) {
        if ($this->state != 2) {
            return;
        }
        $layer = sprintf('LYR%03d', (count($this->pdflayers) + 1));
        if (empty($name)) {
            $name = $layer;
        } else {
            $name = preg_replace('/[^a-zA-Z0-9_\-]/', '', $name);
        }
        $this->pdflayers[] = array('layer' => $layer, 'name' => $name, 'print' => $print, 'view' => $view, 'lock' => $lock);
        $this->openMarkedContent = true;
        $this->_out('/OC /'.$layer.' BDC');
    }

    /**
     * End the current PDF layer.
     * @public
     * @since 5.9.102 (2011-07-13)
     */
    public function endLayer() {
        if ($this->state != 2) {
            return;
        }
        if ($this->openMarkedContent) {
            // close existing open marked-content layer
            $this->_out('EMC');
            $this->openMarkedContent = false;
        }
    }

    /**
     * Set the visibility of the successive elements.
     * This can be useful, for instance, to put a background
     * image or color that will show on screen but won't print.
     * @param $v (string) visibility mode. Legal values are: all, print, screen or view.
     * @public
     * @since 3.0.000 (2008-03-27)
     */
    public function setVisibility($v) {
        if ($this->state != 2) {
            return;
        }
        $this->endLayer();
        switch($v) {
            case 'print': {
                $this->startLayer('Print', true, false);
                break;
            }
            case 'view':
            case 'screen': {
                $this->startLayer('View', false, true);
                break;
            }
            case 'all': {
                $this->_out('');
                break;
            }
            default: {
                $this->Error('Incorrect visibility: '.$v);
                break;
            }
        }
    }

    /**
     * Add transparency parameters to the current extgstate
     * @param $parms (array) parameters
     * @return the number of extgstates
     * @protected
     * @since 3.0.000 (2008-03-27)
     */
    protected function addExtGState($parms) {
        if ($this->pdfa_mode) {
            // transparencies are not allowed in PDF/A mode
            return;
        }
        // check if this ExtGState already exist
        foreach ($this->extgstates as $i => $ext) {
            if ($ext['parms'] == $parms) {
                if ($this->inxobj) {
                    // we are inside an XObject template
                    $this->xobjects[$this->xobjid]['extgstates'][$i] = $ext;
                }
                // return reference to existing ExtGState
                return $i;
            }
        }
        $n = (count($this->extgstates) + 1);
        $this->extgstates[$n] = array('parms' => $parms);
        if ($this->inxobj) {
            // we are inside an XObject template
            $this->xobjects[$this->xobjid]['extgstates'][$n] = $this->extgstates[$n];
        }
        return $n;
    }

    /**
     * Add an extgstate
     * @param $gs (array) extgstate
     * @protected
     * @since 3.0.000 (2008-03-27)
     */
    protected function setExtGState($gs) {
        if ($this->pdfa_mode OR ($this->state != 2)) {
            // transparency is not allowed in PDF/A mode
            return;
        }
        $this->_out(sprintf('/GS%d gs', $gs));
    }

    /**
     * Put extgstates for object transparency
     * @protected
     * @since 3.0.000 (2008-03-27)
     */
    protected function _putextgstates() {
        foreach ($this->extgstates as $i => $ext) {
            $this->extgstates[$i]['n'] = $this->_newobj();
            $out = '<< /Type /ExtGState';
            foreach ($ext['parms'] as $k => $v) {
                if (is_float($v)) {
                    $v = sprintf('%F', $v);
                } elseif ($v === true) {
                    $v = 'true';
                } elseif ($v === false) {
                    $v = 'false';
                }
                $out .= ' /'.$k.' '.$v;
            }
            $out .= ' >>';
            $out .= "\n".'endobj';
            $this->_out($out);
        }
    }

    /**
     * Set overprint mode for stroking (OP) and non-stroking (op) painting operations.
     * (Check the "Entries in a Graphics State Parameter Dictionary" on PDF 32000-1:2008).
     * @param $stroking (boolean) If true apply overprint for stroking operations.
     * @param $nonstroking (boolean) If true apply overprint for painting operations other than stroking.
     * @param $mode (integer) Overprint mode: (0 = each source colour component value replaces the value previously painted for the corresponding device colorant; 1 = a tint value of 0.0 for a source colour component shall leave the corresponding component of the previously painted colour unchanged).
     * @public
     * @since 5.9.152 (2012-03-23)
     */
    public function setOverprint($stroking=true, $nonstroking='', $mode=0) {
        if ($this->state != 2) {
            return;
        }
        $stroking = $stroking ? true : false;
        if (TCPDF_STATIC::empty_string($nonstroking)) {
            // default value if not set
            $nonstroking = $stroking;
        } else {
            $nonstroking = $nonstroking ? true : false;
        }
        if (($mode != 0) AND ($mode != 1)) {
            $mode = 0;
        }
        $this->overprint = array('OP' => $stroking, 'op' => $nonstroking, 'OPM' => $mode);
        $gs = $this->addExtGState($this->overprint);
        $this->setExtGState($gs);
    }

    /**
     * Get the overprint mode array (OP, op, OPM).
     * (Check the "Entries in a Graphics State Parameter Dictionary" on PDF 32000-1:2008).
     * @return array.
     * @public
     * @since 5.9.152 (2012-03-23)
     */
    public function getOverprint() {
        return $this->overprint;
    }

    /**
     * Set alpha for stroking (CA) and non-stroking (ca) operations.
     * @param $stroking (float) Alpha value for stroking operations: real value from 0 (transparent) to 1 (opaque).
     * @param $bm (string) blend mode, one of the following: Normal, Multiply, Screen, Overlay, Darken, Lighten, ColorDodge, ColorBurn, HardLight, SoftLight, Difference, Exclusion, Hue, Saturation, Color, Luminosity
     * @param $nonstroking (float) Alpha value for non-stroking operations: real value from 0 (transparent) to 1 (opaque).
     * @param $ais (boolean)
     * @public
     * @since 3.0.000 (2008-03-27)
     */
    public function setAlpha($stroking=1, $bm='Normal', $nonstroking='', $ais=false) {
        if ($this->pdfa_mode) {
            // transparency is not allowed in PDF/A mode
            return;
        }
        $stroking = floatval($stroking);
        if (TCPDF_STATIC::empty_string($nonstroking)) {
            // default value if not set
            $nonstroking = $stroking;
        } else {
            $nonstroking = floatval($nonstroking);
        }
        if ($bm[0] == '/') {
            // remove trailing slash
            $bm = substr($bm, 1);
        }
        if (!in_array($bm, array('Normal', 'Multiply', 'Screen', 'Overlay', 'Darken', 'Lighten', 'ColorDodge', 'ColorBurn', 'HardLight', 'SoftLight', 'Difference', 'Exclusion', 'Hue', 'Saturation', 'Color', 'Luminosity'))) {
            $bm = 'Normal';
        }
        $ais = $ais ? true : false;
        $this->alpha = array('CA' => $stroking, 'ca' => $nonstroking, 'BM' => '/'.$bm, 'AIS' => $ais);
        $gs = $this->addExtGState($this->alpha);
        $this->setExtGState($gs);
    }

    /**
     * Get the alpha mode array (CA, ca, BM, AIS).
     * (Check the "Entries in a Graphics State Parameter Dictionary" on PDF 32000-1:2008).
     * @return array.
     * @public
     * @since 5.9.152 (2012-03-23)
     */
    public function getAlpha() {
        return $this->alpha;
    }

    /**
     * Set the default JPEG compression quality (1-100)
     * @param $quality (int) JPEG quality, integer between 1 and 100
     * @public
     * @since 3.0.000 (2008-03-27)
     */
    public function setJPEGQuality($quality) {
        if (($quality < 1) OR ($quality > 100)) {
            $quality = 75;
        }
        $this->jpeg_quality = intval($quality);
    }

    /**
     * Set the default number of columns in a row for HTML tables.
     * @param $cols (int) number of columns
     * @public
     * @since 3.0.014 (2008-06-04)
     */
    public function setDefaultTableColumns($cols=4) {
        $this->default_table_columns = intval($cols);
    }

    /**
     * Set the height of the cell (line height) respect the font height.
     * @param $h (int) cell proportion respect font height (typical value = 1.25).
     * @public
     * @since 3.0.014 (2008-06-04)
     */
    public function setCellHeightRatio($h) {
        $this->cell_height_ratio = $h;
    }

    /**
     * return the height of cell repect font height.
     * @public
     * @since 4.0.012 (2008-07-24)
     */
    public function getCellHeightRatio() {
        return $this->cell_height_ratio;
    }

    /**
     * Set the PDF version (check PDF reference for valid values).
     * @param $version (string) PDF document version.
     * @public
     * @since 3.1.000 (2008-06-09)
     */
    public function setPDFVersion($version='1.7') {
        if ($this->pdfa_mode) {
            // PDF/A mode
            $this->PDFVersion = '1.4';
        } else {
            $this->PDFVersion = $version;
        }
    }

    /**
     * Set the viewer preferences dictionary controlling the way the document is to be presented on the screen or in print.
     * (see Section 8.1 of PDF reference, "Viewer Preferences").
     * <ul><li>HideToolbar boolean (Optional) A flag specifying whether to hide the viewer application's tool bars when the document is active. Default value: false.</li><li>HideMenubar boolean (Optional) A flag specifying whether to hide the viewer application's menu bar when the document is active. Default value: false.</li><li>HideWindowUI boolean (Optional) A flag specifying whether to hide user interface elements in the document's window (such as scroll bars and navigation controls), leaving only the document's contents displayed. Default value: false.</li><li>FitWindow boolean (Optional) A flag specifying whether to resize the document's window to fit the size of the first displayed page. Default value: false.</li><li>CenterWindow boolean (Optional) A flag specifying whether to position the document's window in the center of the screen. Default value: false.</li><li>DisplayDocTitle boolean (Optional; PDF 1.4) A flag specifying whether the window's title bar should display the document title taken from the Title entry of the document information dictionary (see Section 10.2.1, "Document Information Dictionary"). If false, the title bar should instead display the name of the PDF file containing the document. Default value: false.</li><li>NonFullScreenPageMode name (Optional) The document's page mode, specifying how to display the document on exiting full-screen mode:<ul><li>UseNone Neither document outline nor thumbnail images visible</li><li>UseOutlines Document outline visible</li><li>UseThumbs Thumbnail images visible</li><li>UseOC Optional content group panel visible</li></ul>This entry is meaningful only if the value of the PageMode entry in the catalog dictionary (see Section 3.6.1, "Document Catalog") is FullScreen; it is ignored otherwise. Default value: UseNone.</li><li>ViewArea name (Optional; PDF 1.4) The name of the page boundary representing the area of a page to be displayed when viewing the document on the screen. Valid values are (see Section 10.10.1, "Page Boundaries").:<ul><li>MediaBox</li><li>CropBox (default)</li><li>BleedBox</li><li>TrimBox</li><li>ArtBox</li></ul></li><li>ViewClip name (Optional; PDF 1.4) The name of the page boundary to which the contents of a page are to be clipped when viewing the document on the screen. Valid values are (see Section 10.10.1, "Page Boundaries").:<ul><li>MediaBox</li><li>CropBox (default)</li><li>BleedBox</li><li>TrimBox</li><li>ArtBox</li></ul></li><li>PrintArea name (Optional; PDF 1.4) The name of the page boundary representing the area of a page to be rendered when printing the document. Valid values are (see Section 10.10.1, "Page Boundaries").:<ul><li>MediaBox</li><li>CropBox (default)</li><li>BleedBox</li><li>TrimBox</li><li>ArtBox</li></ul></li><li>PrintClip name (Optional; PDF 1.4) The name of the page boundary to which the contents of a page are to be clipped when printing the document. Valid values are (see Section 10.10.1, "Page Boundaries").:<ul><li>MediaBox</li><li>CropBox (default)</li><li>BleedBox</li><li>TrimBox</li><li>ArtBox</li></ul></li><li>PrintScaling name (Optional; PDF 1.6) The page scaling option to be selected when a print dialog is displayed for this document. Valid values are: <ul><li>None, which indicates that the print dialog should reflect no page scaling</li><li>AppDefault (default), which indicates that applications should use the current print scaling</li></ul></li><li>Duplex name (Optional; PDF 1.7) The paper handling option to use when printing the file from the print dialog. The following values are valid:<ul><li>Simplex - Print single-sided</li><li>DuplexFlipShortEdge - Duplex and flip on the short edge of the sheet</li><li>DuplexFlipLongEdge - Duplex and flip on the long edge of the sheet</li></ul>Default value: none</li><li>PickTrayByPDFSize boolean (Optional; PDF 1.7) A flag specifying whether the PDF page size is used to select the input paper tray. This setting influences only the preset values used to populate the print dialog presented by a PDF viewer application. If PickTrayByPDFSize is true, the check box in the print dialog associated with input paper tray is checked. Note: This setting has no effect on Mac OS systems, which do not provide the ability to pick the input tray by size.</li><li>PrintPageRange array (Optional; PDF 1.7) The page numbers used to initialize the print dialog box when the file is printed. The first page of the PDF file is denoted by 1. Each pair consists of the first and last pages in the sub-range. An odd number of integers causes this entry to be ignored. Negative numbers cause the entire array to be ignored. Default value: as defined by PDF viewer application</li><li>NumCopies integer (Optional; PDF 1.7) The number of copies to be printed when the print dialog is opened for this file. Supported values are the integers 2 through 5. Values outside this range are ignored. Default value: as defined by PDF viewer application, but typically 1</li></ul>
     * @param $preferences (array) array of options.
     * @author Nicola Asuni
     * @public
     * @since 3.1.000 (2008-06-09)
     */
    public function setViewerPreferences($preferences) {
        $this->viewer_preferences = $preferences;
    }

    /**
     * Paints color transition registration bars
     * @param $x (float) abscissa of the top left corner of the rectangle.
     * @param $y (float) ordinate of the top left corner of the rectangle.
     * @param $w (float) width of the rectangle.
     * @param $h (float) height of the rectangle.
     * @param $transition (boolean) if true prints tcolor transitions to white.
     * @param $vertical (boolean) if true prints bar vertically.
     * @param $colors (string) colors to print separated by comma. Valid values are: A,W,R,G,B,C,M,Y,K,RGB,CMYK,ALL,ALLSPOT,<SPOT_COLOR_NAME>. Where: A = grayscale black, W = grayscale white, R = RGB red, G RGB green, B RGB blue, C = CMYK cyan, M = CMYK magenta, Y = CMYK yellow, K = CMYK key/black, RGB = RGB registration color, CMYK = CMYK registration color, ALL = Spot registration color, ALLSPOT = print all defined spot colors, <SPOT_COLOR_NAME> = name of the spot color to print.
     * @author Nicola Asuni
     * @since 4.9.000 (2010-03-26)
     * @public
     */
    public function colorRegistrationBar($x, $y, $w, $h, $transition=true, $vertical=false, $colors='A,R,G,B,C,M,Y,K') {
        if (strpos($colors, 'ALLSPOT') !== false) {
            // expand spot colors
            $spot_colors = '';
            foreach ($this->spot_colors as $spot_color_name => $v) {
                $spot_colors .= ','.$spot_color_name;
            }
            if (!empty($spot_colors)) {
                $spot_colors = substr($spot_colors, 1);
                $colors = str_replace('ALLSPOT', $spot_colors, $colors);
            } else {
                $colors = str_replace('ALLSPOT', 'NONE', $colors);
            }
        }
        $bars = explode(',', $colors);
        $numbars = count($bars); // number of bars to print
        if ($numbars <= 0) {
            return;
        }
        // set bar measures
        if ($vertical) {
            $coords = array(0, 0, 0, 1);
            $wb = $w / $numbars; // bar width
            $hb = $h; // bar height
            $xd = $wb; // delta x
            $yd = 0; // delta y
        } else {
            $coords = array(1, 0, 0, 0);
            $wb = $w; // bar width
            $hb = $h / $numbars; // bar height
            $xd = 0; // delta x
            $yd = $hb; // delta y
        }
        $xb = $x;
        $yb = $y;
        foreach ($bars as $col) {
            switch ($col) {
                // set transition colors
                case 'A': { // BLACK (GRAYSCALE)
                    $col_a = array(255);
                    $col_b = array(0);
                    break;
                }
                case 'W': { // WHITE (GRAYSCALE)
                    $col_a = array(0);
                    $col_b = array(255);
                    break;
                }
                case 'R': { // RED (RGB)
                    $col_a = array(255,255,255);
                    $col_b = array(255,0,0);
                    break;
                }
                case 'G': { // GREEN (RGB)
                    $col_a = array(255,255,255);
                    $col_b = array(0,255,0);
                    break;
                }
                case 'B': { // BLUE (RGB)
                    $col_a = array(255,255,255);
                    $col_b = array(0,0,255);
                    break;
                }
                case 'C': { // CYAN (CMYK)
                    $col_a = array(0,0,0,0);
                    $col_b = array(100,0,0,0);
                    break;
                }
                case 'M': { // MAGENTA (CMYK)
                    $col_a = array(0,0,0,0);
                    $col_b = array(0,100,0,0);
                    break;
                }
                case 'Y': { // YELLOW (CMYK)
                    $col_a = array(0,0,0,0);
                    $col_b = array(0,0,100,0);
                    break;
                }
                case 'K': { // KEY - BLACK (CMYK)
                    $col_a = array(0,0,0,0);
                    $col_b = array(0,0,0,100);
                    break;
                }
                case 'RGB': { // BLACK REGISTRATION (RGB)
                    $col_a = array(255,255,255);
                    $col_b = array(0,0,0);
                    break;
                }
                case 'CMYK': { // BLACK REGISTRATION (CMYK)
                    $col_a = array(0,0,0,0);
                    $col_b = array(100,100,100,100);
                    break;
                }
                case 'ALL': { // SPOT COLOR REGISTRATION
                    $col_a = array(0,0,0,0,'None');
                    $col_b = array(100,100,100,100,'All');
                    break;
                }
                case 'NONE': { // SKIP THIS COLOR
                    $col_a = array(0,0,0,0,'None');
                    $col_b = array(0,0,0,0,'None');
                    break;
                }
                default: { // SPECIFIC SPOT COLOR NAME
                    $col_a = array(0,0,0,0,'None');
                    $col_b = TCPDF_COLORS::getSpotColor($col, $this->spot_colors);
                    if ($col_b === false) {
                        // in case of error defaults to the registration color
                        $col_b = array(100,100,100,100,'All');
                    }
                    break;
                }
            }
            if ($col != 'NONE') {
                if ($transition) {
                    // color gradient
                    $this->LinearGradient($xb, $yb, $wb, $hb, $col_a, $col_b, $coords);
                } else {
                    $this->SetFillColorArray($col_b);
                    // colored rectangle
                    $this->Rect($xb, $yb, $wb, $hb, 'F', array());
                }
                $xb += $xd;
                $yb += $yd;
            }
        }
    }

    /**
     * Paints crop marks.
     * @param $x (float) abscissa of the crop mark center.
     * @param $y (float) ordinate of the crop mark center.
     * @param $w (float) width of the crop mark.
     * @param $h (float) height of the crop mark.
     * @param $type (string) type of crop mark, one symbol per type separated by comma: T = TOP, F = BOTTOM, L = LEFT, R = RIGHT, TL = A = TOP-LEFT, TR = B = TOP-RIGHT, BL = C = BOTTOM-LEFT, BR = D = BOTTOM-RIGHT.
     * @param $color (array) crop mark color (default spot registration color).
     * @author Nicola Asuni
     * @since 4.9.000 (2010-03-26)
     * @public
     */
    public function cropMark($x, $y, $w, $h, $type='T,R,B,L', $color=array(100,100,100,100,'All')) {
        $this->SetLineStyle(array('width' => (0.5 / $this->k), 'cap' => 'butt', 'join' => 'miter', 'dash' => 0, 'color' => $color));
        $type = strtoupper($type);
        $type = preg_replace('/[^A-Z\-\,]*/', '', $type);
        // split type in single components
        $type = str_replace('-', ',', $type);
        $type = str_replace('TL', 'T,L', $type);
        $type = str_replace('TR', 'T,R', $type);
        $type = str_replace('BL', 'F,L', $type);
        $type = str_replace('BR', 'F,R', $type);
        $type = str_replace('A', 'T,L', $type);
        $type = str_replace('B', 'T,R', $type);
        $type = str_replace('T,RO', 'BO', $type);
        $type = str_replace('C', 'F,L', $type);
        $type = str_replace('D', 'F,R', $type);
        $crops = explode(',', strtoupper($type));
        // remove duplicates
        $crops = array_unique($crops);
        $dw = ($w / 4); // horizontal space to leave before the intersection point
        $dh = ($h / 4); // vertical space to leave before the intersection point
        foreach ($crops as $crop) {
            switch ($crop) {
                case 'T':
                case 'TOP': {
                    $x1 = $x;
                    $y1 = ($y - $h);
                    $x2 = $x;
                    $y2 = ($y - $dh);
                    break;
                }
                case 'F':
                case 'BOTTOM': {
                    $x1 = $x;
                    $y1 = ($y + $dh);
                    $x2 = $x;
                    $y2 = ($y + $h);
                    break;
                }
                case 'L':
                case 'LEFT': {
                    $x1 = ($x - $w);
                    $y1 = $y;
                    $x2 = ($x - $dw);
                    $y2 = $y;
                    break;
                }
                case 'R':
                case 'RIGHT': {
                    $x1 = ($x + $dw);
                    $y1 = $y;
                    $x2 = ($x + $w);
                    $y2 = $y;
                    break;
                }
            }
            $this->Line($x1, $y1, $x2, $y2);
        }
    }

    /**
     * Paints a registration mark
     * @param $x (float) abscissa of the registration mark center.
     * @param $y (float) ordinate of the registration mark center.
     * @param $r (float) radius of the crop mark.
     * @param $double (boolean) if true print two concentric crop marks.
     * @param $cola (array) crop mark color (default spot registration color 'All').
     * @param $colb (array) second crop mark color (default spot registration color 'None').
     * @author Nicola Asuni
     * @since 4.9.000 (2010-03-26)
     * @public
     */
    public function registrationMark($x, $y, $r, $double=false, $cola=array(100,100,100,100,'All'), $colb=array(0,0,0,0,'None')) {
        $line_style = array('width' => max((0.5 / $this->k),($r / 30)), 'cap' => 'butt', 'join' => 'miter', 'dash' => 0, 'color' => $cola);
        $this->SetFillColorArray($cola);
        $this->PieSector($x, $y, $r, 90, 180, 'F');
        $this->PieSector($x, $y, $r, 270, 360, 'F');
        $this->Circle($x, $y, $r, 0, 360, 'C', $line_style, array(), 8);
        if ($double) {
            $ri = $r * 0.5;
            $this->SetFillColorArray($colb);
            $this->PieSector($x, $y, $ri, 90, 180, 'F');
            $this->PieSector($x, $y, $ri, 270, 360, 'F');
            $this->SetFillColorArray($cola);
            $this->PieSector($x, $y, $ri, 0, 90, 'F');
            $this->PieSector($x, $y, $ri, 180, 270, 'F');
            $this->Circle($x, $y, $ri, 0, 360, 'C', $line_style, array(), 8);
        }
    }

    /**
     * Paints a CMYK registration mark
     * @param $x (float) abscissa of the registration mark center.
     * @param $y (float) ordinate of the registration mark center.
     * @param $r (float) radius of the crop mark.
     * @author Nicola Asuni
     * @since 6.0.038 (2013-09-30)
     * @public
     */
    public function registrationMarkCMYK($x, $y, $r) {
        // line width
        $lw = max((0.5 / $this->k),($r / 8));
        // internal radius
        $ri = ($r * 0.6);
        // external radius
        $re = ($r * 1.3);
        // Cyan
        $this->SetFillColorArray(array(100,0,0,0));
        $this->PieSector($x, $y, $ri, 270, 360, 'F');
        // Magenta
        $this->SetFillColorArray(array(0,100,0,0));
        $this->PieSector($x, $y, $ri, 0, 90, 'F');
        // Yellow
        $this->SetFillColorArray(array(0,0,100,0));
        $this->PieSector($x, $y, $ri, 90, 180, 'F');
        // Key - black
        $this->SetFillColorArray(array(0,0,0,100));
        $this->PieSector($x, $y, $ri, 180, 270, 'F');
        // registration color
        $line_style = array('width' => $lw, 'cap' => 'butt', 'join' => 'miter', 'dash' => 0, 'color' => array(100,100,100,100,'All'));
        $this->SetFillColorArray(array(100,100,100,100,'All'));
        // external circle
        $this->Circle($x, $y, $r, 0, 360, 'C', $line_style, array(), 8);
        // cross lines
        $this->Line($x, ($y - $re), $x, ($y - $ri));
        $this->Line($x, ($y + $ri), $x, ($y + $re));
        $this->Line(($x - $re), $y, ($x - $ri), $y);
        $this->Line(($x + $ri), $y, ($x + $re), $y);
    }

    /**
     * Paints a linear colour gradient.
     * @param $x (float) abscissa of the top left corner of the rectangle.
     * @param $y (float) ordinate of the top left corner of the rectangle.
     * @param $w (float) width of the rectangle.
     * @param $h (float) height of the rectangle.
     * @param $col1 (array) first color (Grayscale, RGB or CMYK components).
     * @param $col2 (array) second color (Grayscale, RGB or CMYK components).
     * @param $coords (array) array of the form (x1, y1, x2, y2) which defines the gradient vector (see linear_gradient_coords.jpg). The default value is from left to right (x1=0, y1=0, x2=1, y2=0).
     * @author Andreas W\FCrmser, Nicola Asuni
     * @since 3.1.000 (2008-06-09)
     * @public
     */
    public function LinearGradient($x, $y, $w, $h, $col1=array(), $col2=array(), $coords=array(0,0,1,0)) {
        $this->Clip($x, $y, $w, $h);
        $this->Gradient(2, $coords, array(array('color' => $col1, 'offset' => 0, 'exponent' => 1), array('color' => $col2, 'offset' => 1, 'exponent' => 1)), array(), false);
    }

    /**
     * Paints a radial colour gradient.
     * @param $x (float) abscissa of the top left corner of the rectangle.
     * @param $y (float) ordinate of the top left corner of the rectangle.
     * @param $w (float) width of the rectangle.
     * @param $h (float) height of the rectangle.
     * @param $col1 (array) first color (Grayscale, RGB or CMYK components).
     * @param $col2 (array) second color (Grayscale, RGB or CMYK components).
     * @param $coords (array) array of the form (fx, fy, cx, cy, r) where (fx, fy) is the starting point of the gradient with color1, (cx, cy) is the center of the circle with color2, and r is the radius of the circle (see radial_gradient_coords.jpg). (fx, fy) should be inside the circle, otherwise some areas will not be defined.
     * @author Andreas W\FCrmser, Nicola Asuni
     * @since 3.1.000 (2008-06-09)
     * @public
     */
    public function RadialGradient($x, $y, $w, $h, $col1=array(), $col2=array(), $coords=array(0.5,0.5,0.5,0.5,1)) {
        $this->Clip($x, $y, $w, $h);
        $this->Gradient(3, $coords, array(array('color' => $col1, 'offset' => 0, 'exponent' => 1), array('color' => $col2, 'offset' => 1, 'exponent' => 1)), array(), false);
    }

    /**
     * Paints a coons patch mesh.
     * @param $x (float) abscissa of the top left corner of the rectangle.
     * @param $y (float) ordinate of the top left corner of the rectangle.
     * @param $w (float) width of the rectangle.
     * @param $h (float) height of the rectangle.
     * @param $col1 (array) first color (lower left corner) (RGB components).
     * @param $col2 (array) second color (lower right corner) (RGB components).
     * @param $col3 (array) third color (upper right corner) (RGB components).
     * @param $col4 (array) fourth color (upper left corner) (RGB components).
     * @param $coords (array) <ul><li>for one patch mesh: array(float x1, float y1, .... float x12, float y12): 12 pairs of coordinates (normally from 0 to 1) which specify the Bezier control points that define the patch. First pair is the lower left edge point, next is its right control point (control point 2). Then the other points are defined in the order: control point 1, edge point, control point 2 going counter-clockwise around the patch. Last (x12, y12) is the first edge point's left control point (control point 1).</li><li>for two or more patch meshes: array[number of patches]: arrays with the following keys for each patch: f: where to put that patch (0 = first patch, 1, 2, 3 = right, top and left of precedent patch - I didn't figure this out completely - just try and error ;-) points: 12 pairs of coordinates of the Bezier control points as above for the first patch, 8 pairs of coordinates for the following patches, ignoring the coordinates already defined by the precedent patch (I also didn't figure out the order of these - also: try and see what's happening) colors: must be 4 colors for the first patch, 2 colors for the following patches</li></ul>
     * @param $coords_min (array) minimum value used by the coordinates. If a coordinate's value is smaller than this it will be cut to coords_min. default: 0
     * @param $coords_max (array) maximum value used by the coordinates. If a coordinate's value is greater than this it will be cut to coords_max. default: 1
     * @param $antialias (boolean) A flag indicating whether to filter the shading function to prevent aliasing artifacts.
     * @author Andreas W\FCrmser, Nicola Asuni
     * @since 3.1.000 (2008-06-09)
     * @public
     */
    public function CoonsPatchMesh($x, $y, $w, $h, $col1=array(), $col2=array(), $col3=array(), $col4=array(), $coords=array(0.00,0.0,0.33,0.00,0.67,0.00,1.00,0.00,1.00,0.33,1.00,0.67,1.00,1.00,0.67,1.00,0.33,1.00,0.00,1.00,0.00,0.67,0.00,0.33), $coords_min=0, $coords_max=1, $antialias=false) {
        if ($this->pdfa_mode OR ($this->state != 2)) {
            return;
        }
        $this->Clip($x, $y, $w, $h);
        $n = count($this->gradients) + 1;
        $this->gradients[$n] = array();
        $this->gradients[$n]['type'] = 6; //coons patch mesh
        $this->gradients[$n]['coords'] = array();
        $this->gradients[$n]['antialias'] = $antialias;
        $this->gradients[$n]['colors'] = array();
        $this->gradients[$n]['transparency'] = false;
        //check the coords array if it is the simple array or the multi patch array
        if (!isset($coords[0]['f'])) {
            //simple array -> convert to multi patch array
            if (!isset($col1[1])) {
                $col1[1] = $col1[2] = $col1[0];
            }
            if (!isset($col2[1])) {
                $col2[1] = $col2[2] = $col2[0];
            }
            if (!isset($col3[1])) {
                $col3[1] = $col3[2] = $col3[0];
            }
            if (!isset($col4[1])) {
                $col4[1] = $col4[2] = $col4[0];
            }
            $patch_array[0]['f'] = 0;
            $patch_array[0]['points'] = $coords;
            $patch_array[0]['colors'][0]['r'] = $col1[0];
            $patch_array[0]['colors'][0]['g'] = $col1[1];
            $patch_array[0]['colors'][0]['b'] = $col1[2];
            $patch_array[0]['colors'][1]['r'] = $col2[0];
            $patch_array[0]['colors'][1]['g'] = $col2[1];
            $patch_array[0]['colors'][1]['b'] = $col2[2];
            $patch_array[0]['colors'][2]['r'] = $col3[0];
            $patch_array[0]['colors'][2]['g'] = $col3[1];
            $patch_array[0]['colors'][2]['b'] = $col3[2];
            $patch_array[0]['colors'][3]['r'] = $col4[0];
            $patch_array[0]['colors'][3]['g'] = $col4[1];
            $patch_array[0]['colors'][3]['b'] = $col4[2];
        } else {
            //multi patch array
            $patch_array = $coords;
        }
        $bpcd = 65535; //16 bits per coordinate
        //build the data stream
        $this->gradients[$n]['stream'] = '';
        $count_patch = count($patch_array);
        for ($i=0; $i < $count_patch; ++$i) {
            $this->gradients[$n]['stream'] .= chr($patch_array[$i]['f']); //start with the edge flag as 8 bit
            $count_points = count($patch_array[$i]['points']);
            for ($j=0; $j < $count_points; ++$j) {
                //each point as 16 bit
                $patch_array[$i]['points'][$j] = (($patch_array[$i]['points'][$j] - $coords_min) / ($coords_max - $coords_min)) * $bpcd;
                if ($patch_array[$i]['points'][$j] < 0) {
                    $patch_array[$i]['points'][$j] = 0;
                }
                if ($patch_array[$i]['points'][$j] > $bpcd) {
                    $patch_array[$i]['points'][$j] = $bpcd;
                }
                $this->gradients[$n]['stream'] .= chr(floor($patch_array[$i]['points'][$j] / 256));
                $this->gradients[$n]['stream'] .= chr(floor($patch_array[$i]['points'][$j] % 256));
            }
            $count_cols = count($patch_array[$i]['colors']);
            for ($j=0; $j < $count_cols; ++$j) {
                //each color component as 8 bit
                $this->gradients[$n]['stream'] .= chr($patch_array[$i]['colors'][$j]['r']);
                $this->gradients[$n]['stream'] .= chr($patch_array[$i]['colors'][$j]['g']);
                $this->gradients[$n]['stream'] .= chr($patch_array[$i]['colors'][$j]['b']);
            }
        }
        //paint the gradient
        $this->_out('/Sh'.$n.' sh');
        //restore previous Graphic State
        $this->_outRestoreGraphicsState();
        if ($this->inxobj) {
            // we are inside an XObject template
            $this->xobjects[$this->xobjid]['gradients'][$n] = $this->gradients[$n];
        }
    }

    /**
     * Set a rectangular clipping area.
     * @param $x (float) abscissa of the top left corner of the rectangle (or top right corner for RTL mode).
     * @param $y (float) ordinate of the top left corner of the rectangle.
     * @param $w (float) width of the rectangle.
     * @param $h (float) height of the rectangle.
     * @author Andreas W\FCrmser, Nicola Asuni
     * @since 3.1.000 (2008-06-09)
     * @protected
     */
    protected function Clip($x, $y, $w, $h) {
        if ($this->state != 2) {
             return;
        }
        if ($this->rtl) {
            $x = $this->w - $x - $w;
        }
        //save current Graphic State
        $s = 'q';
        //set clipping area
        $s .= sprintf(' %F %F %F %F re W n', $x*$this->k, ($this->h-$y)*$this->k, $w*$this->k, -$h*$this->k);
        //set up transformation matrix for gradient
        $s .= sprintf(' %F 0 0 %F %F %F cm', $w*$this->k, $h*$this->k, $x*$this->k, ($this->h-($y+$h))*$this->k);
        $this->_out($s);
    }

    /**
     * Output gradient.
     * @param $type (int) type of gradient (1 Function-based shading; 2 Axial shading; 3 Radial shading; 4 Free-form Gouraud-shaded triangle mesh; 5 Lattice-form Gouraud-shaded triangle mesh; 6 Coons patch mesh; 7 Tensor-product patch mesh). (Not all types are currently supported)
     * @param $coords (array) array of coordinates.
     * @param $stops (array) array gradient color components: color = array of GRAY, RGB or CMYK color components; offset = (0 to 1) represents a location along the gradient vector; exponent = exponent of the exponential interpolation function (default = 1).
     * @param $background (array) An array of colour components appropriate to the colour space, specifying a single background colour value.
     * @param $antialias (boolean) A flag indicating whether to filter the shading function to prevent aliasing artifacts.
     * @author Nicola Asuni
     * @since 3.1.000 (2008-06-09)
     * @public
     */
    public function Gradient($type, $coords, $stops, $background=array(), $antialias=false) {
        if ($this->pdfa_mode OR ($this->state != 2)) {
            return;
        }
        $n = count($this->gradients) + 1;
        $this->gradients[$n] = array();
        $this->gradients[$n]['type'] = $type;
        $this->gradients[$n]['coords'] = $coords;
        $this->gradients[$n]['antialias'] = $antialias;
        $this->gradients[$n]['colors'] = array();
        $this->gradients[$n]['transparency'] = false;
        // color space
        $numcolspace = count($stops[0]['color']);
        $bcolor = array_values($background);
        switch($numcolspace) {
            case 5:   // SPOT
            case 4: { // CMYK
                $this->gradients[$n]['colspace'] = 'DeviceCMYK';
                if (!empty($background)) {
                    $this->gradients[$n]['background'] = sprintf('%F %F %F %F', $bcolor[0]/100, $bcolor[1]/100, $bcolor[2]/100, $bcolor[3]/100);
                }
                break;
            }
            case 3: { // RGB
                $this->gradients[$n]['colspace'] = 'DeviceRGB';
                if (!empty($background)) {
                    $this->gradients[$n]['background'] = sprintf('%F %F %F', $bcolor[0]/255, $bcolor[1]/255, $bcolor[2]/255);
                }
                break;
            }
            case 1: { // GRAY SCALE
                $this->gradients[$n]['colspace'] = 'DeviceGray';
                if (!empty($background)) {
                    $this->gradients[$n]['background'] = sprintf('%F', $bcolor[0]/255);
                }
                break;
            }
        }
        $num_stops = count($stops);
        $last_stop_id = $num_stops - 1;
        foreach ($stops as $key => $stop) {
            $this->gradients[$n]['colors'][$key] = array();
            // offset represents a location along the gradient vector
            if (isset($stop['offset'])) {
                $this->gradients[$n]['colors'][$key]['offset'] = $stop['offset'];
            } else {
                if ($key == 0) {
                    $this->gradients[$n]['colors'][$key]['offset'] = 0;
                } elseif ($key == $last_stop_id) {
                    $this->gradients[$n]['colors'][$key]['offset'] = 1;
                } else {
                    $offsetstep = (1 - $this->gradients[$n]['colors'][($key - 1)]['offset']) / ($num_stops - $key);
                    $this->gradients[$n]['colors'][$key]['offset'] = $this->gradients[$n]['colors'][($key - 1)]['offset'] + $offsetstep;
                }
            }
            if (isset($stop['opacity'])) {
                $this->gradients[$n]['colors'][$key]['opacity'] = $stop['opacity'];
                if ((!$this->pdfa_mode) AND ($stop['opacity'] < 1)) {
                    $this->gradients[$n]['transparency'] = true;
                }
            } else {
                $this->gradients[$n]['colors'][$key]['opacity'] = 1;
            }
            // exponent for the exponential interpolation function
            if (isset($stop['exponent'])) {
                $this->gradients[$n]['colors'][$key]['exponent'] = $stop['exponent'];
            } else {
                $this->gradients[$n]['colors'][$key]['exponent'] = 1;
            }
            // set colors
            $color = array_values($stop['color']);
            switch($numcolspace) {
                case 5:   // SPOT
                case 4: { // CMYK
                    $this->gradients[$n]['colors'][$key]['color'] = sprintf('%F %F %F %F', $color[0]/100, $color[1]/100, $color[2]/100, $color[3]/100);
                    break;
                }
                case 3: { // RGB
                    $this->gradients[$n]['colors'][$key]['color'] = sprintf('%F %F %F', $color[0]/255, $color[1]/255, $color[2]/255);
                    break;
                }
                case 1: { // GRAY SCALE
                    $this->gradients[$n]['colors'][$key]['color'] = sprintf('%F', $color[0]/255);
                    break;
                }
            }
        }
        if ($this->gradients[$n]['transparency']) {
            // paint luminosity gradient
            $this->_out('/TGS'.$n.' gs');
        }
        //paint the gradient
        $this->_out('/Sh'.$n.' sh');
        //restore previous Graphic State
        $this->_outRestoreGraphicsState();
        if ($this->inxobj) {
            // we are inside an XObject template
            $this->xobjects[$this->xobjid]['gradients'][$n] = $this->gradients[$n];
        }
    }

    /**
     * Output gradient shaders.
     * @author Nicola Asuni
     * @since 3.1.000 (2008-06-09)
     * @protected
     */
    function _putshaders() {
        if ($this->pdfa_mode) {
            return;
        }
        $idt = count($this->gradients); //index for transparency gradients
        foreach ($this->gradients as $id => $grad) {
            if (($grad['type'] == 2) OR ($grad['type'] == 3)) {
                $fc = $this->_newobj();
                $out = '<<';
                $out .= ' /FunctionType 3';
                $out .= ' /Domain [0 1]';
                $functions = '';
                $bounds = '';
                $encode = '';
                $i = 1;
                $num_cols = count($grad['colors']);
                $lastcols = $num_cols - 1;
                for ($i = 1; $i < $num_cols; ++$i) {
                    $functions .= ($fc + $i).' 0 R ';
                    if ($i < $lastcols) {
                        $bounds .= sprintf('%F ', $grad['colors'][$i]['offset']);
                    }
                    $encode .= '0 1 ';
                }
                $out .= ' /Functions ['.trim($functions).']';
                $out .= ' /Bounds ['.trim($bounds).']';
                $out .= ' /Encode ['.trim($encode).']';
                $out .= ' >>';
                $out .= "\n".'endobj';
                $this->_out($out);
                for ($i = 1; $i < $num_cols; ++$i) {
                    $this->_newobj();
                    $out = '<<';
                    $out .= ' /FunctionType 2';
                    $out .= ' /Domain [0 1]';
                    $out .= ' /C0 ['.$grad['colors'][($i - 1)]['color'].']';
                    $out .= ' /C1 ['.$grad['colors'][$i]['color'].']';
                    $out .= ' /N '.$grad['colors'][$i]['exponent'];
                    $out .= ' >>';
                    $out .= "\n".'endobj';
                    $this->_out($out);
                }
                // set transparency functions
                if ($grad['transparency']) {
                    $ft = $this->_newobj();
                    $out = '<<';
                    $out .= ' /FunctionType 3';
                    $out .= ' /Domain [0 1]';
                    $functions = '';
                    $i = 1;
                    $num_cols = count($grad['colors']);
                    for ($i = 1; $i < $num_cols; ++$i) {
                        $functions .= ($ft + $i).' 0 R ';
                    }
                    $out .= ' /Functions ['.trim($functions).']';
                    $out .= ' /Bounds ['.trim($bounds).']';
                    $out .= ' /Encode ['.trim($encode).']';
                    $out .= ' >>';
                    $out .= "\n".'endobj';
                    $this->_out($out);
                    for ($i = 1; $i < $num_cols; ++$i) {
                        $this->_newobj();
                        $out = '<<';
                        $out .= ' /FunctionType 2';
                        $out .= ' /Domain [0 1]';
                        $out .= ' /C0 ['.$grad['colors'][($i - 1)]['opacity'].']';
                        $out .= ' /C1 ['.$grad['colors'][$i]['opacity'].']';
                        $out .= ' /N '.$grad['colors'][$i]['exponent'];
                        $out .= ' >>';
                        $out .= "\n".'endobj';
                        $this->_out($out);
                    }
                }
            }
            // set shading object
            $this->_newobj();
            $out = '<< /ShadingType '.$grad['type'];
            if (isset($grad['colspace'])) {
                $out .= ' /ColorSpace /'.$grad['colspace'];
            } else {
                $out .= ' /ColorSpace /DeviceRGB';
            }
            if (isset($grad['background']) AND !empty($grad['background'])) {
                $out .= ' /Background ['.$grad['background'].']';
            }
            if (isset($grad['antialias']) AND ($grad['antialias'] === true)) {
                $out .= ' /AntiAlias true';
            }
            if ($grad['type'] == 2) {
                $out .= ' '.sprintf('/Coords [%F %F %F %F]', $grad['coords'][0], $grad['coords'][1], $grad['coords'][2], $grad['coords'][3]);
                $out .= ' /Domain [0 1]';
                $out .= ' /Function '.$fc.' 0 R';
                $out .= ' /Extend [true true]';
                $out .= ' >>';
            } elseif ($grad['type'] == 3) {
                //x0, y0, r0, x1, y1, r1
                //at this this time radius of inner circle is 0
                $out .= ' '.sprintf('/Coords [%F %F 0 %F %F %F]', $grad['coords'][0], $grad['coords'][1], $grad['coords'][2], $grad['coords'][3], $grad['coords'][4]);
                $out .= ' /Domain [0 1]';
                $out .= ' /Function '.$fc.' 0 R';
                $out .= ' /Extend [true true]';
                $out .= ' >>';
            } elseif ($grad['type'] == 6) {
                $out .= ' /BitsPerCoordinate 16';
                $out .= ' /BitsPerComponent 8';
                $out .= ' /Decode[0 1 0 1 0 1 0 1 0 1]';
                $out .= ' /BitsPerFlag 8';
                $stream = $this->_getrawstream($grad['stream']);
                $out .= ' /Length '.strlen($stream);
                $out .= ' >>';
                $out .= ' stream'."\n".$stream."\n".'endstream';
            }
            $out .= "\n".'endobj';
            $this->_out($out);
            if ($grad['transparency']) {
                $shading_transparency = preg_replace('/\/ColorSpace \/[^\s]+/si', '/ColorSpace /DeviceGray', $out);
                $shading_transparency = preg_replace('/\/Function [0-9]+ /si', '/Function '.$ft.' ', $shading_transparency);
            }
            $this->gradients[$id]['id'] = $this->n;
            // set pattern object
            $this->_newobj();
            $out = '<< /Type /Pattern /PatternType 2';
            $out .= ' /Shading '.$this->gradients[$id]['id'].' 0 R';
            $out .= ' >>';
            $out .= "\n".'endobj';
            $this->_out($out);
            $this->gradients[$id]['pattern'] = $this->n;
            // set shading and pattern for transparency mask
            if ($grad['transparency']) {
                // luminosity pattern
                $idgs = $id + $idt;
                $this->_newobj();
                $this->_out($shading_transparency);
                $this->gradients[$idgs]['id'] = $this->n;
                $this->_newobj();
                $out = '<< /Type /Pattern /PatternType 2';
                $out .= ' /Shading '.$this->gradients[$idgs]['id'].' 0 R';
                $out .= ' >>';
                $out .= "\n".'endobj';
                $this->_out($out);
                $this->gradients[$idgs]['pattern'] = $this->n;
                // luminosity XObject
                $oid = $this->_newobj();
                $this->xobjects['LX'.$oid] = array('n' => $oid);
                $filter = '';
                $stream = 'q /a0 gs /Pattern cs /p'.$idgs.' scn 0 0 '.$this->wPt.' '.$this->hPt.' re f Q';
                if ($this->compress) {
                    $filter = ' /Filter /FlateDecode';
                    $stream = gzcompress($stream);
                }
                $stream = $this->_getrawstream($stream);
                $out = '<< /Type /XObject /Subtype /Form /FormType 1'.$filter;
                $out .= ' /Length '.strlen($stream);
                $rect = sprintf('%F %F', $this->wPt, $this->hPt);
                $out .= ' /BBox [0 0 '.$rect.']';
                $out .= ' /Group << /Type /Group /S /Transparency /CS /DeviceGray >>';
                $out .= ' /Resources <<';
                $out .= ' /ExtGState << /a0 << /ca 1 /CA 1 >> >>';
                $out .= ' /Pattern << /p'.$idgs.' '.$this->gradients[$idgs]['pattern'].' 0 R >>';
                $out .= ' >>';
                $out .= ' >> ';
                $out .= ' stream'."\n".$stream."\n".'endstream';
                $out .= "\n".'endobj';
                $this->_out($out);
                // SMask
                $this->_newobj();
                $out = '<< /Type /Mask /S /Luminosity /G '.($this->n - 1).' 0 R >>'."\n".'endobj';
                $this->_out($out);
                // ExtGState
                $this->_newobj();
                $out = '<< /Type /ExtGState /SMask '.($this->n - 1).' 0 R /AIS false >>'."\n".'endobj';
                $this->_out($out);
                $this->extgstates[] = array('n' => $this->n, 'name' => 'TGS'.$id);
            }
        }
    }

    /**
     * Draw the sector of a circle.
     * It can be used for instance to render pie charts.
     * @param $xc (float) abscissa of the center.
     * @param $yc (float) ordinate of the center.
     * @param $r (float) radius.
     * @param $a (float) start angle (in degrees).
     * @param $b (float) end angle (in degrees).
     * @param $style (string) Style of rendering. See the getPathPaintOperator() function for more information.
     * @param $cw: (float) indicates whether to go clockwise (default: true).
     * @param $o: (float) origin of angles (0 for 3 o'clock, 90 for noon, 180 for 9 o'clock, 270 for 6 o'clock). Default: 90.
     * @author Maxime Delorme, Nicola Asuni
     * @since 3.1.000 (2008-06-09)
     * @public
     */
    public function PieSector($xc, $yc, $r, $a, $b, $style='FD', $cw=true, $o=90) {
        $this->PieSectorXY($xc, $yc, $r, $r, $a, $b, $style, $cw, $o);
    }

    /**
     * Draw the sector of an ellipse.
     * It can be used for instance to render pie charts.
     * @param $xc (float) abscissa of the center.
     * @param $yc (float) ordinate of the center.
     * @param $rx (float) the x-axis radius.
     * @param $ry (float) the y-axis radius.
     * @param $a (float) start angle (in degrees).
     * @param $b (float) end angle (in degrees).
     * @param $style (string) Style of rendering. See the getPathPaintOperator() function for more information.
     * @param $cw: (float) indicates whether to go clockwise.
     * @param $o: (float) origin of angles (0 for 3 o'clock, 90 for noon, 180 for 9 o'clock, 270 for 6 o'clock).
     * @param $nc (integer) Number of curves used to draw a 90 degrees portion of arc.
     * @author Maxime Delorme, Nicola Asuni
     * @since 3.1.000 (2008-06-09)
     * @public
     */
    public function PieSectorXY($xc, $yc, $rx, $ry, $a, $b, $style='FD', $cw=false, $o=0, $nc=2) {
        if ($this->state != 2) {
             return;
        }
        if ($this->rtl) {
            $xc = ($this->w - $xc);
        }
        $op = TCPDF_STATIC::getPathPaintOperator($style);
        if ($op == 'f') {
            $line_style = array();
        }
        if ($cw) {
            $d = $b;
            $b = (360 - $a + $o);
            $a = (360 - $d + $o);
        } else {
            $b += $o;
            $a += $o;
        }
        $this->_outellipticalarc($xc, $yc, $rx, $ry, 0, $a, $b, true, $nc);
        $this->_out($op);
    }

    /**
     * Embed vector-based Adobe Illustrator (AI) or AI-compatible EPS files.
     * NOTE: EPS is not yet fully implemented, use the setRasterizeVectorImages() method to enable/disable rasterization of vector images using ImageMagick library.
     * Only vector drawing is supported, not text or bitmap.
     * Although the script was successfully tested with various AI format versions, best results are probably achieved with files that were exported in the AI3 format (tested with Illustrator CS2, Freehand MX and Photoshop CS2).
     * @param $file (string) Name of the file containing the image or a '@' character followed by the EPS/AI data string.
     * @param $x (float) Abscissa of the upper-left corner.
     * @param $y (float) Ordinate of the upper-left corner.
     * @param $w (float) Width of the image in the page. If not specified or equal to zero, it is automatically calculated.
     * @param $h (float) Height of the image in the page. If not specified or equal to zero, it is automatically calculated.
     * @param $link (mixed) URL or identifier returned by AddLink().
     * @param $useBoundingBox (boolean) specifies whether to position the bounding box (true) or the complete canvas (false) at location (x,y). Default value is true.
     * @param $align (string) Indicates the alignment of the pointer next to image insertion relative to image height. The value can be:<ul><li>T: top-right for LTR or top-left for RTL</li><li>M: middle-right for LTR or middle-left for RTL</li><li>B: bottom-right for LTR or bottom-left for RTL</li><li>N: next line</li></ul>
     * @param $palign (string) Allows to center or align the image on the current line. Possible values are:<ul><li>L : left align</li><li>C : center</li><li>R : right align</li><li>'' : empty string : left for LTR or right for RTL</li></ul>
     * @param $border (mixed) Indicates if borders must be drawn around the cell. The value can be a number:<ul><li>0: no border (default)</li><li>1: frame</li></ul> or a string containing some or all of the following characters (in any order):<ul><li>L: left</li><li>T: top</li><li>R: right</li><li>B: bottom</li></ul> or an array of line styles for each border group - for example: array('LTRB' => array('width' => 2, 'cap' => 'butt', 'join' => 'miter', 'dash' => 0, 'color' => array(0, 0, 0)))
     * @param $fitonpage (boolean) if true the image is resized to not exceed page dimensions.
     * @param $fixoutvals (boolean) if true remove values outside the bounding box.
     * @author Valentin Schmidt, Nicola Asuni
     * @since 3.1.000 (2008-06-09)
     * @public
     */
    public function ImageEps($file, $x='', $y='', $w=0, $h=0, $link='', $useBoundingBox=true, $align='', $palign='', $border=0, $fitonpage=false, $fixoutvals=false) {
        if ($this->state != 2) {
             return;
        }
        if ($this->rasterize_vector_images AND ($w > 0) AND ($h > 0)) {
            // convert EPS to raster image using GD or ImageMagick libraries
            return $this->Image($file, $x, $y, $w, $h, 'EPS', $link, $align, true, 300, $palign, false, false, $border, false, false, $fitonpage);
        }
        if ($x === '') {
            $x = $this->x;
        }
        if ($y === '') {
            $y = $this->y;
        }
        // check page for no-write regions and adapt page margins if necessary
        list($x, $y) = $this->checkPageRegions($h, $x, $y);
        $k = $this->k;
        if ($file[0] === '@') { // image from string
            $data = substr($file, 1);
        } else { // EPS/AI file
            $data = TCPDF_STATIC::fileGetContents($file);
        }
        if ($data === FALSE) {
            $this->Error('EPS file not found: '.$file);
        }
        $regs = array();
        // EPS/AI compatibility check (only checks files created by Adobe Illustrator!)
        preg_match("/%%Creator:([^\r\n]+)/", $data, $regs); # find Creator
        if (count($regs) > 1) {
            $version_str = trim($regs[1]); # e.g. "Adobe Illustrator(R) 8.0"
            if (strpos($version_str, 'Adobe Illustrator') !== false) {
                $versexp = explode(' ', $version_str);
                $version = (float)array_pop($versexp);
                if ($version >= 9) {
                    $this->Error('This version of Adobe Illustrator file is not supported: '.$file);
                }
            }
        }
        // strip binary bytes in front of PS-header
        $start = strpos($data, '%!PS-Adobe');
        if ($start > 0) {
            $data = substr($data, $start);
        }
        // find BoundingBox params
        preg_match("/%%BoundingBox:([^\r\n]+)/", $data, $regs);
        if (count($regs) > 1) {
            list($x1, $y1, $x2, $y2) = explode(' ', trim($regs[1]));
        } else {
            $this->Error('No BoundingBox found in EPS/AI file: '.$file);
        }
        $start = strpos($data, '%%EndSetup');
        if ($start === false) {
            $start = strpos($data, '%%EndProlog');
        }
        if ($start === false) {
            $start = strpos($data, '%%BoundingBox');
        }
        $data = substr($data, $start);
        $end = strpos($data, '%%PageTrailer');
        if ($end===false) {
            $end = strpos($data, 'showpage');
        }
        if ($end) {
            $data = substr($data, 0, $end);
        }
        // calculate image width and height on document
        if (($w <= 0) AND ($h <= 0)) {
            $w = ($x2 - $x1) / $k;
            $h = ($y2 - $y1) / $k;
        } elseif ($w <= 0) {
            $w = ($x2-$x1) / $k * ($h / (($y2 - $y1) / $k));
        } elseif ($h <= 0) {
            $h = ($y2 - $y1) / $k * ($w / (($x2 - $x1) / $k));
        }
        // fit the image on available space
        list($w, $h, $x, $y) = $this->fitBlock($w, $h, $x, $y, $fitonpage);
        if ($this->rasterize_vector_images) {
            // convert EPS to raster image using GD or ImageMagick libraries
            return $this->Image($file, $x, $y, $w, $h, 'EPS', $link, $align, true, 300, $palign, false, false, $border, false, false, $fitonpage);
        }
        // set scaling factors
        $scale_x = $w / (($x2 - $x1) / $k);
        $scale_y = $h / (($y2 - $y1) / $k);
        // set alignment
        $this->img_rb_y = $y + $h;
        // set alignment
        if ($this->rtl) {
            if ($palign == 'L') {
                $ximg = $this->lMargin;
            } elseif ($palign == 'C') {
                $ximg = ($this->w + $this->lMargin - $this->rMargin - $w) / 2;
            } elseif ($palign == 'R') {
                $ximg = $this->w - $this->rMargin - $w;
            } else {
                $ximg = $x - $w;
            }
            $this->img_rb_x = $ximg;
        } else {
            if ($palign == 'L') {
                $ximg = $this->lMargin;
            } elseif ($palign == 'C') {
                $ximg = ($this->w + $this->lMargin - $this->rMargin - $w) / 2;
            } elseif ($palign == 'R') {
                $ximg = $this->w - $this->rMargin - $w;
            } else {
                $ximg = $x;
            }
            $this->img_rb_x = $ximg + $w;
        }
        if ($useBoundingBox) {
            $dx = $ximg * $k - $x1;
            $dy = $y * $k - $y1;
        } else {
            $dx = $ximg * $k;
            $dy = $y * $k;
        }
        // save the current graphic state
        $this->_out('q'.$this->epsmarker);
        // translate
        $this->_out(sprintf('%F %F %F %F %F %F cm', 1, 0, 0, 1, $dx, $dy + ($this->hPt - (2 * $y * $k) - ($y2 - $y1))));
        // scale
        if (isset($scale_x)) {
            $this->_out(sprintf('%F %F %F %F %F %F cm', $scale_x, 0, 0, $scale_y, $x1 * (1 - $scale_x), $y2 * (1 - $scale_y)));
        }
        // handle pc/unix/mac line endings
        $lines = preg_split('/[\r\n]+/si', $data, -1, PREG_SPLIT_NO_EMPTY);
        $u=0;
        $cnt = count($lines);
        for ($i=0; $i < $cnt; ++$i) {
            $line = $lines[$i];
            if (($line == '') OR ($line[0] == '%')) {
                continue;
            }
            $len = strlen($line);
            // check for spot color names
            $color_name = '';
            if (strcasecmp('x', substr(trim($line), -1)) == 0) {
                if (preg_match('/\([^\)]*\)/', $line, $matches) > 0) {
                    // extract spot color name
                    $color_name = $matches[0];
                    // remove color name from string
                    $line = str_replace(' '.$color_name, '', $line);
                    // remove pharentesis from color name
                    $color_name = substr($color_name, 1, -1);
                }
            }
            $chunks = explode(' ', $line);
            $cmd = trim(array_pop($chunks));
            // RGB
            if (($cmd == 'Xa') OR ($cmd == 'XA')) {
                $b = array_pop($chunks);
                $g = array_pop($chunks);
                $r = array_pop($chunks);
                $this->_out(''.$r.' '.$g.' '.$b.' '.($cmd=='Xa'?'rg':'RG')); //substr($line, 0, -2).'rg' -> in EPS (AI8): c m y k r g b rg!
                continue;
            }
            $skip = false;
            if ($fixoutvals) {
                // check for values outside the bounding box
                switch ($cmd) {
                    case 'm':
                    case 'l':
                    case 'L': {
                        // skip values outside bounding box
                        foreach ($chunks as $key => $val) {
                            if ((($key % 2) == 0) AND (($val < $x1) OR ($val > $x2))) {
                                $skip = true;
                            } elseif ((($key % 2) != 0) AND (($val < $y1) OR ($val > $y2))) {
                                $skip = true;
                            }
                        }
                    }
                }
            }
            switch ($cmd) {
                case 'm':
                case 'l':
                case 'v':
                case 'y':
                case 'c':
                case 'k':
                case 'K':
                case 'g':
                case 'G':
                case 's':
                case 'S':
                case 'J':
                case 'j':
                case 'w':
                case 'M':
                case 'd':
                case 'n': {
                    if ($skip) {
                        break;
                    }
                    $this->_out($line);
                    break;
                }
                case 'x': {// custom fill color
                    if (empty($color_name)) {
                        // CMYK color
                        list($col_c, $col_m, $col_y, $col_k) = $chunks;
                        $this->_out(''.$col_c.' '.$col_m.' '.$col_y.' '.$col_k.' k');
                    } else {
                        // Spot Color (CMYK + tint)
                        list($col_c, $col_m, $col_y, $col_k, $col_t) = $chunks;
                        $this->AddSpotColor($color_name, ($col_c * 100), ($col_m * 100), ($col_y * 100), ($col_k * 100));
                        $color_cmd = sprintf('/CS%d cs %F scn', $this->spot_colors[$color_name]['i'], (1 - $col_t));
                        $this->_out($color_cmd);
                    }
                    break;
                }
                case 'X': { // custom stroke color
                    if (empty($color_name)) {
                        // CMYK color
                        list($col_c, $col_m, $col_y, $col_k) = $chunks;
                        $this->_out(''.$col_c.' '.$col_m.' '.$col_y.' '.$col_k.' K');
                    } else {
                        // Spot Color (CMYK + tint)
                        list($col_c, $col_m, $col_y, $col_k, $col_t) = $chunks;
                        $this->AddSpotColor($color_name, ($col_c * 100), ($col_m * 100), ($col_y * 100), ($col_k * 100));
                        $color_cmd = sprintf('/CS%d CS %F SCN', $this->spot_colors[$color_name]['i'], (1 - $col_t));
                        $this->_out($color_cmd);
                    }
                    break;
                }
                case 'Y':
                case 'N':
                case 'V':
                case 'L':
                case 'C': {
                    if ($skip) {
                        break;
                    }
                    $line[($len - 1)] = strtolower($cmd);
                    $this->_out($line);
                    break;
                }
                case 'b':
                case 'B': {
                    $this->_out($cmd . '*');
                    break;
                }
                case 'f':
                case 'F': {
                    if ($u > 0) {
                        $isU = false;
                        $max = min(($i + 5), $cnt);
                        for ($j = ($i + 1); $j < $max; ++$j) {
                            $isU = ($isU OR (($lines[$j] == 'U') OR ($lines[$j] == '*U')));
                        }
                        if ($isU) {
                            $this->_out('f*');
                        }
                    } else {
                        $this->_out('f*');
                    }
                    break;
                }
                case '*u': {
                    ++$u;
                    break;
                }
                case '*U': {
                    --$u;
                    break;
                }
            }
        }
        // restore previous graphic state
        $this->_out($this->epsmarker.'Q');
        if (!empty($border)) {
            $bx = $this->x;
            $by = $this->y;
            $this->x = $ximg;
            if ($this->rtl) {
                $this->x += $w;
            }
            $this->y = $y;
            $this->Cell($w, $h, '', $border, 0, '', 0, '', 0, true);
            $this->x = $bx;
            $this->y = $by;
        }
        if ($link) {
            $this->Link($ximg, $y, $w, $h, $link, 0);
        }
        // set pointer to align the next text/objects
        switch($align) {
            case 'T':{
                $this->y = $y;
                $this->x = $this->img_rb_x;
                break;
            }
            case 'M':{
                $this->y = $y + round($h/2);
                $this->x = $this->img_rb_x;
                break;
            }
            case 'B':{
                $this->y = $this->img_rb_y;
                $this->x = $this->img_rb_x;
                break;
            }
            case 'N':{
                $this->SetY($this->img_rb_y);
                break;
            }
            default:{
                break;
            }
        }
        $this->endlinex = $this->img_rb_x;
    }

    /**
     * Set document barcode.
     * @param $bc (string) barcode
     * @public
     */
    public function setBarcode($bc='') {
        $this->barcode = $bc;
    }

    /**
     * Get current barcode.
     * @return string
     * @public
     * @since 4.0.012 (2008-07-24)
     */
    public function getBarcode() {
        return $this->barcode;
    }

    /**
     * Print a Linear Barcode.
     * @param $code (string) code to print
     * @param $type (string) type of barcode (see tcpdf_barcodes_1d.php for supported formats).
     * @param $x (int) x position in user units (empty string = current x position)
     * @param $y (int) y position in user units (empty string = current y position)
     * @param $w (int) width in user units (empty string = remaining page width)
     * @param $h (int) height in user units (empty string = remaining page height)
     * @param $xres (float) width of the smallest bar in user units (empty string = default value = 0.4mm)
     * @param $style (array) array of options:<ul>
     * <li>boolean $style['border'] if true prints a border</li>
     * <li>int $style['padding'] padding to leave around the barcode in user units (set to 'auto' for automatic padding)</li>
     * <li>int $style['hpadding'] horizontal padding in user units (set to 'auto' for automatic padding)</li>
     * <li>int $style['vpadding'] vertical padding in user units (set to 'auto' for automatic padding)</li>
     * <li>array $style['fgcolor'] color array for bars and text</li>
     * <li>mixed $style['bgcolor'] color array for background (set to false for transparent)</li>
     * <li>boolean $style['text'] if true prints text below the barcode</li>
     * <li>string $style['label'] override default label</li>
     * <li>string $style['font'] font name for text</li><li>int $style['fontsize'] font size for text</li>
     * <li>int $style['stretchtext']: 0 = disabled; 1 = horizontal scaling only if necessary; 2 = forced horizontal scaling; 3 = character spacing only if necessary; 4 = forced character spacing.</li>
     * <li>string $style['position'] horizontal position of the containing barcode cell on the page: L = left margin; C = center; R = right margin.</li>
     * <li>string $style['align'] horizontal position of the barcode on the containing rectangle: L = left; C = center; R = right.</li>
     * <li>string $style['stretch'] if true stretch the barcode to best fit the available width, otherwise uses $xres resolution for a single bar.</li>
     * <li>string $style['fitwidth'] if true reduce the width to fit the barcode width + padding. When this option is enabled the 'stretch' option is automatically disabled.</li>
     * <li>string $style['cellfitalign'] this option works only when 'fitwidth' is true and 'position' is unset or empty. Set the horizontal position of the containing barcode cell inside the specified rectangle: L = left; C = center; R = right.</li></ul>
     * @param $align (string) Indicates the alignment of the pointer next to barcode insertion relative to barcode height. The value can be:<ul><li>T: top-right for LTR or top-left for RTL</li><li>M: middle-right for LTR or middle-left for RTL</li><li>B: bottom-right for LTR or bottom-left for RTL</li><li>N: next line</li></ul>
     * @author Nicola Asuni
     * @since 3.1.000 (2008-06-09)
     * @public
     */
    public function write1DBarcode($code, $type, $x='', $y='', $w='', $h='', $xres='', $style='', $align='') {
        if (TCPDF_STATIC::empty_string(trim($code))) {
            return;
        }
        require_once(dirname(__FILE__).'/tcpdf_barcodes_1d.php');
        // save current graphic settings
        $gvars = $this->getGraphicVars();
        // create new barcode object
        $barcodeobj = new TCPDFBarcode($code, $type);
        $arrcode = $barcodeobj->getBarcodeArray();
        if (($arrcode === false) OR empty($arrcode) OR ($arrcode['maxw'] <= 0)) {
            $this->Error('Error in 1D barcode string');
        }
        if ($arrcode['maxh'] <= 0) {
            $arrcode['maxh'] = 1;
        }
        // set default values
        if (!isset($style['position'])) {
            $style['position'] = '';
        } elseif ($style['position'] == 'S') {
            // keep this for backward compatibility
            $style['position'] = '';
            $style['stretch'] = true;
        }
        if (!isset($style['fitwidth'])) {
            if (!isset($style['stretch'])) {
                $style['fitwidth'] = true;
            } else {
                $style['fitwidth'] = false;
            }
        }
        if ($style['fitwidth']) {
            // disable stretch
            $style['stretch'] = false;
        }
        if (!isset($style['stretch'])) {
            if (($w === '') OR ($w <= 0)) {
                $style['stretch'] = false;
            } else {
                $style['stretch'] = true;
            }
        }
        if (!isset($style['fgcolor'])) {
            $style['fgcolor'] = array(0,0,0); // default black
        }
        if (!isset($style['bgcolor'])) {
            $style['bgcolor'] = false; // default transparent
        }
        if (!isset($style['border'])) {
            $style['border'] = false;
        }
        $fontsize = 0;
        if (!isset($style['text'])) {
            $style['text'] = false;
        }
        if ($style['text'] AND isset($style['font'])) {
            if (isset($style['fontsize'])) {
                $fontsize = $style['fontsize'];
            }
            $this->SetFont($style['font'], '', $fontsize);
        }
        if (!isset($style['stretchtext'])) {
            $style['stretchtext'] = 4;
        }
        if ($x === '') {
            $x = $this->x;
        }
        if ($y === '') {
            $y = $this->y;
        }
        // check page for no-write regions and adapt page margins if necessary
        list($x, $y) = $this->checkPageRegions($h, $x, $y);
        if (($w === '') OR ($w <= 0)) {
            if ($this->rtl) {
                $w = $x - $this->lMargin;
            } else {
                $w = $this->w - $this->rMargin - $x;
            }
        }
        // padding
        if (!isset($style['padding'])) {
            $padding = 0;
        } elseif ($style['padding'] === 'auto') {
            $padding = 10 * ($w / ($arrcode['maxw'] + 20));
        } else {
            $padding = floatval($style['padding']);
        }
        // horizontal padding
        if (!isset($style['hpadding'])) {
            $hpadding = $padding;
        } elseif ($style['hpadding'] === 'auto') {
            $hpadding = 10 * ($w / ($arrcode['maxw'] + 20));
        } else {
            $hpadding = floatval($style['hpadding']);
        }
        // vertical padding
        if (!isset($style['vpadding'])) {
            $vpadding = $padding;
        } elseif ($style['vpadding'] === 'auto') {
            $vpadding = ($hpadding / 2);
        } else {
            $vpadding = floatval($style['vpadding']);
        }
        // calculate xres (single bar width)
        $max_xres = ($w - (2 * $hpadding)) / $arrcode['maxw'];
        if ($style['stretch']) {
            $xres = $max_xres;
        } else {
            if (TCPDF_STATIC::empty_string($xres)) {
                $xres = (0.141 * $this->k); // default bar width = 0.4 mm
            }
            if ($xres > $max_xres) {
                // correct xres to fit on $w
                $xres = $max_xres;
            }
            if ((isset($style['padding']) AND ($style['padding'] === 'auto'))
                OR (isset($style['hpadding']) AND ($style['hpadding'] === 'auto'))) {
                $hpadding = 10 * $xres;
                if (isset($style['vpadding']) AND ($style['vpadding'] === 'auto')) {
                    $vpadding = ($hpadding / 2);
                }
            }
        }
        if ($style['fitwidth']) {
            $wold = $w;
            $w = (($arrcode['maxw'] * $xres) + (2 * $hpadding));
            if (isset($style['cellfitalign'])) {
                switch ($style['cellfitalign']) {
                    case 'L': {
                        if ($this->rtl) {
                            $x -= ($wold - $w);
                        }
                        break;
                    }
                    case 'R': {
                        if (!$this->rtl) {
                            $x += ($wold - $w);
                        }
                        break;
                    }
                    case 'C': {
                        if ($this->rtl) {
                            $x -= (($wold - $w) / 2);
                        } else {
                            $x += (($wold - $w) / 2);
                        }
                        break;
                    }
                    default : {
                        break;
                    }
                }
            }
        }
        $text_height = $this->getCellHeight($fontsize / $this->k);
        // height
        if (($h === '') OR ($h <= 0)) {
            // set default height
            $h = (($arrcode['maxw'] * $xres) / 3) + (2 * $vpadding) + $text_height;
        }
        $barh = $h - $text_height - (2 * $vpadding);
        if ($barh <=0) {
            // try to reduce font or padding to fit barcode on available height
            if ($text_height > $h) {
                $fontsize = (($h * $this->k) / (4 * $this->cell_height_ratio));
                $text_height = $this->getCellHeight($fontsize / $this->k);
                $this->SetFont($style['font'], '', $fontsize);
            }
            if ($vpadding > 0) {
                $vpadding = (($h - $text_height) / 4);
            }
            $barh = $h - $text_height - (2 * $vpadding);
        }
        // fit the barcode on available space
        list($w, $h, $x, $y) = $this->fitBlock($w, $h, $x, $y, false);
        // set alignment
        $this->img_rb_y = $y + $h;
        // set alignment
        if ($this->rtl) {
            if ($style['position'] == 'L') {
                $xpos = $this->lMargin;
            } elseif ($style['position'] == 'C') {
                $xpos = ($this->w + $this->lMargin - $this->rMargin - $w) / 2;
            } elseif ($style['position'] == 'R') {
                $xpos = $this->w - $this->rMargin - $w;
            } else {
                $xpos = $x - $w;
            }
            $this->img_rb_x = $xpos;
        } else {
            if ($style['position'] == 'L') {
                $xpos = $this->lMargin;
            } elseif ($style['position'] == 'C') {
                $xpos = ($this->w + $this->lMargin - $this->rMargin - $w) / 2;
            } elseif ($style['position'] == 'R') {
                $xpos = $this->w - $this->rMargin - $w;
            } else {
                $xpos = $x;
            }
            $this->img_rb_x = $xpos + $w;
        }
        $xpos_rect = $xpos;
        if (!isset($style['align'])) {
            $style['align'] = 'C';
        }
        switch ($style['align']) {
            case 'L': {
                $xpos = $xpos_rect + $hpadding;
                break;
            }
            case 'R': {
                $xpos = $xpos_rect + ($w - ($arrcode['maxw'] * $xres)) - $hpadding;
                break;
            }
            case 'C':
            default : {
                $xpos = $xpos_rect + (($w - ($arrcode['maxw'] * $xres)) / 2);
                break;
            }
        }
        $xpos_text = $xpos;
        // barcode is always printed in LTR direction
        $tempRTL = $this->rtl;
        $this->rtl = false;
        // print background color
        if ($style['bgcolor']) {
            $this->Rect($xpos_rect, $y, $w, $h, $style['border'] ? 'DF' : 'F', '', $style['bgcolor']);
        } elseif ($style['border']) {
            $this->Rect($xpos_rect, $y, $w, $h, 'D');
        }
        // set foreground color
        $this->SetDrawColorArray($style['fgcolor']);
        $this->SetTextColorArray($style['fgcolor']);
        // print bars
        foreach ($arrcode['bcode'] as $k => $v) {
            $bw = ($v['w'] * $xres);
            if ($v['t']) {
                // draw a vertical bar
                $ypos = $y + $vpadding + ($v['p'] * $barh / $arrcode['maxh']);
                $this->Rect($xpos, $ypos, $bw, ($v['h'] * $barh / $arrcode['maxh']), 'F', array(), $style['fgcolor']);
            }
            $xpos += $bw;
        }
        // print text
        if ($style['text']) {
            if (isset($style['label']) AND !TCPDF_STATIC::empty_string($style['label'])) {
                $label = $style['label'];
            } else {
                $label = $code;
            }
            $txtwidth = ($arrcode['maxw'] * $xres);
            if ($this->GetStringWidth($label) > $txtwidth) {
                $style['stretchtext'] = 2;
            }
            // print text
            $this->x = $xpos_text;
            $this->y = $y + $vpadding + $barh;
            $cellpadding = $this->cell_padding;
            $this->SetCellPadding(0);
            $this->Cell($txtwidth, '', $label, 0, 0, 'C', false, '', $style['stretchtext'], false, 'T', 'T');
            $this->cell_padding = $cellpadding;
        }
        // restore original direction
        $this->rtl = $tempRTL;
        // restore previous settings
        $this->setGraphicVars($gvars);
        // set pointer to align the next text/objects
        switch($align) {
            case 'T':{
                $this->y = $y;
                $this->x = $this->img_rb_x;
                break;
            }
            case 'M':{
                $this->y = $y + round($h / 2);
                $this->x = $this->img_rb_x;
                break;
            }
            case 'B':{
                $this->y = $this->img_rb_y;
                $this->x = $this->img_rb_x;
                break;
            }
            case 'N':{
                $this->SetY($this->img_rb_y);
                break;
            }
            default:{
                break;
            }
        }
        $this->endlinex = $this->img_rb_x;
    }

    /**
     * Print 2D Barcode.
     * @param $code (string) code to print
     * @param $type (string) type of barcode (see tcpdf_barcodes_2d.php for supported formats).
     * @param $x (int) x position in user units
     * @param $y (int) y position in user units
     * @param $w (int) width in user units
     * @param $h (int) height in user units
     * @param $style (array) array of options:<ul>
     * <li>boolean $style['border'] if true prints a border around the barcode</li>
     * <li>int $style['padding'] padding to leave around the barcode in barcode units (set to 'auto' for automatic padding)</li>
     * <li>int $style['hpadding'] horizontal padding in barcode units (set to 'auto' for automatic padding)</li>
     * <li>int $style['vpadding'] vertical padding in barcode units (set to 'auto' for automatic padding)</li>
     * <li>int $style['module_width'] width of a single module in points</li>
     * <li>int $style['module_height'] height of a single module in points</li>
     * <li>array $style['fgcolor'] color array for bars and text</li>
     * <li>mixed $style['bgcolor'] color array for background or false for transparent</li>
     * <li>string $style['position'] barcode position on the page: L = left margin; C = center; R = right margin; S = stretch</li><li>$style['module_width'] width of a single module in points</li>
     * <li>$style['module_height'] height of a single module in points</li></ul>
     * @param $align (string) Indicates the alignment of the pointer next to barcode insertion relative to barcode height. The value can be:<ul><li>T: top-right for LTR or top-left for RTL</li><li>M: middle-right for LTR or middle-left for RTL</li><li>B: bottom-right for LTR or bottom-left for RTL</li><li>N: next line</li></ul>
     * @param $distort (boolean) if true distort the barcode to fit width and height, otherwise preserve aspect ratio
     * @author Nicola Asuni
     * @since 4.5.037 (2009-04-07)
     * @public
     */
    public function write2DBarcode($code, $type, $x='', $y='', $w='', $h='', $style='', $align='', $distort=false) {
        if (TCPDF_STATIC::empty_string(trim($code))) {
            return;
        }
        require_once(dirname(__FILE__).'/tcpdf_barcodes_2d.php');
        // save current graphic settings
        $gvars = $this->getGraphicVars();
        // create new barcode object
        $barcodeobj = new TCPDF2DBarcode($code, $type);
        $arrcode = $barcodeobj->getBarcodeArray();
        if (($arrcode === false) OR empty($arrcode) OR !isset($arrcode['num_rows']) OR ($arrcode['num_rows'] == 0) OR !isset($arrcode['num_cols']) OR ($arrcode['num_cols'] == 0)) {
            $this->Error('Error in 2D barcode string');
        }
        // set default values
        if (!isset($style['position'])) {
            $style['position'] = '';
        }
        if (!isset($style['fgcolor'])) {
            $style['fgcolor'] = array(0,0,0); // default black
        }
        if (!isset($style['bgcolor'])) {
            $style['bgcolor'] = false; // default transparent
        }
        if (!isset($style['border'])) {
            $style['border'] = false;
        }
        // padding
        if (!isset($style['padding'])) {
            $style['padding'] = 0;
        } elseif ($style['padding'] === 'auto') {
            $style['padding'] = 4;
        }
        if (!isset($style['hpadding'])) {
            $style['hpadding'] = $style['padding'];
        } elseif ($style['hpadding'] === 'auto') {
            $style['hpadding'] = 4;
        }
        if (!isset($style['vpadding'])) {
            $style['vpadding'] = $style['padding'];
        } elseif ($style['vpadding'] === 'auto') {
            $style['vpadding'] = 4;
        }
        $hpad = (2 * $style['hpadding']);
        $vpad = (2 * $style['vpadding']);
        // cell (module) dimension
        if (!isset($style['module_width'])) {
            $style['module_width'] = 1; // width of a single module in points
        }
        if (!isset($style['module_height'])) {
            $style['module_height'] = 1; // height of a single module in points
        }
        if ($x === '') {
            $x = $this->x;
        }
        if ($y === '') {
            $y = $this->y;
        }
        // check page for no-write regions and adapt page margins if necessary
        list($x, $y) = $this->checkPageRegions($h, $x, $y);
        // number of barcode columns and rows
        $rows = $arrcode['num_rows'];
        $cols = $arrcode['num_cols'];
        if (($rows <= 0) || ($cols <= 0)){
            $this->Error('Error in 2D barcode string');
        }
        // module width and height
        $mw = $style['module_width'];
        $mh = $style['module_height'];
        if (($mw <= 0) OR ($mh <= 0)) {
            $this->Error('Error in 2D barcode string');
        }
        // get max dimensions
        if ($this->rtl) {
            $maxw = $x - $this->lMargin;
        } else {
            $maxw = $this->w - $this->rMargin - $x;
        }
        $maxh = ($this->h - $this->tMargin - $this->bMargin);
        $ratioHW = ((($rows * $mh) + $hpad) / (($cols * $mw) + $vpad));
        $ratioWH = ((($cols * $mw) + $vpad) / (($rows * $mh) + $hpad));
        if (!$distort) {
            if (($maxw * $ratioHW) > $maxh) {
                $maxw = $maxh * $ratioWH;
            }
            if (($maxh * $ratioWH) > $maxw) {
                $maxh = $maxw * $ratioHW;
            }
        }
        // set maximum dimensions
        if ($w > $maxw) {
            $w = $maxw;
        }
        if ($h > $maxh) {
            $h = $maxh;
        }
        // set dimensions
        if ((($w === '') OR ($w <= 0)) AND (($h === '') OR ($h <= 0))) {
            $w = ($cols + $hpad) * ($mw / $this->k);
            $h = ($rows + $vpad) * ($mh / $this->k);
        } elseif (($w === '') OR ($w <= 0)) {
            $w = $h * $ratioWH;
        } elseif (($h === '') OR ($h <= 0)) {
            $h = $w * $ratioHW;
        }
        // barcode size (excluding padding)
        $bw = ($w * $cols) / ($cols + $hpad);
        $bh = ($h * $rows) / ($rows + $vpad);
        // dimension of single barcode cell unit
        $cw = $bw / $cols;
        $ch = $bh / $rows;
        if (!$distort) {
            if (($cw / $ch) > ($mw / $mh)) {
                // correct horizontal distortion
                $cw = $ch * $mw / $mh;
                $bw = $cw * $cols;
                $style['hpadding'] = ($w - $bw) / (2 * $cw);
            } else {
                // correct vertical distortion
                $ch = $cw * $mh / $mw;
                $bh = $ch * $rows;
                $style['vpadding'] = ($h - $bh) / (2 * $ch);
            }
        }
        // fit the barcode on available space
        list($w, $h, $x, $y) = $this->fitBlock($w, $h, $x, $y, false);
        // set alignment
        $this->img_rb_y = $y + $h;
        // set alignment
        if ($this->rtl) {
            if ($style['position'] == 'L') {
                $xpos = $this->lMargin;
            } elseif ($style['position'] == 'C') {
                $xpos = ($this->w + $this->lMargin - $this->rMargin - $w) / 2;
            } elseif ($style['position'] == 'R') {
                $xpos = $this->w - $this->rMargin - $w;
            } else {
                $xpos = $x - $w;
            }
            $this->img_rb_x = $xpos;
        } else {
            if ($style['position'] == 'L') {
                $xpos = $this->lMargin;
            } elseif ($style['position'] == 'C') {
                $xpos = ($this->w + $this->lMargin - $this->rMargin - $w) / 2;
            } elseif ($style['position'] == 'R') {
                $xpos = $this->w - $this->rMargin - $w;
            } else {
                $xpos = $x;
            }
            $this->img_rb_x = $xpos + $w;
        }
        $xstart = $xpos + ($style['hpadding'] * $cw);
        $ystart = $y + ($style['vpadding'] * $ch);
        // barcode is always printed in LTR direction
        $tempRTL = $this->rtl;
        $this->rtl = false;
        // print background color
        if ($style['bgcolor']) {
            $this->Rect($xpos, $y, $w, $h, $style['border'] ? 'DF' : 'F', '', $style['bgcolor']);
        } elseif ($style['border']) {
            $this->Rect($xpos, $y, $w, $h, 'D');
        }
        // set foreground color
        $this->SetDrawColorArray($style['fgcolor']);
        // print barcode cells
        // for each row
        for ($r = 0; $r < $rows; ++$r) {
            $xr = $xstart;
            // for each column
            for ($c = 0; $c < $cols; ++$c) {
                if ($arrcode['bcode'][$r][$c] == 1) {
                    // draw a single barcode cell
                    $this->Rect($xr, $ystart, $cw, $ch, 'F', array(), $style['fgcolor']);
                }
                $xr += $cw;
            }
            $ystart += $ch;
        }
        // restore original direction
        $this->rtl = $tempRTL;
        // restore previous settings
        $this->setGraphicVars($gvars);
        // set pointer to align the next text/objects
        switch($align) {
            case 'T':{
                $this->y = $y;
                $this->x = $this->img_rb_x;
                break;
            }
            case 'M':{
                $this->y = $y + round($h/2);
                $this->x = $this->img_rb_x;
                break;
            }
            case 'B':{
                $this->y = $this->img_rb_y;
                $this->x = $this->img_rb_x;
                break;
            }
            case 'N':{
                $this->SetY($this->img_rb_y);
                break;
            }
            default:{
                break;
            }
        }
        $this->endlinex = $this->img_rb_x;
    }

    /**
     * Returns an array containing current margins:
     * <ul>
            <li>$ret['left'] = left margin</li>
            <li>$ret['right'] = right margin</li>
            <li>$ret['top'] = top margin</li>
            <li>$ret['bottom'] = bottom margin</li>
            <li>$ret['header'] = header margin</li>
            <li>$ret['footer'] = footer margin</li>
            <li>$ret['cell'] = cell padding array</li>
            <li>$ret['padding_left'] = cell left padding</li>
            <li>$ret['padding_top'] = cell top padding</li>
            <li>$ret['padding_right'] = cell right padding</li>
            <li>$ret['padding_bottom'] = cell bottom padding</li>
     * </ul>
     * @return array containing all margins measures
     * @public
     * @since 3.2.000 (2008-06-23)
     */
    public function getMargins() {
        $ret = array(
            'left' => $this->lMargin,
            'right' => $this->rMargin,
            'top' => $this->tMargin,
            'bottom' => $this->bMargin,
            'header' => $this->header_margin,
            'footer' => $this->footer_margin,
            'cell' => $this->cell_padding,
            'padding_left' => $this->cell_padding['L'],
            'padding_top' => $this->cell_padding['T'],
            'padding_right' => $this->cell_padding['R'],
            'padding_bottom' => $this->cell_padding['B']
        );
        return $ret;
    }

    /**
     * Returns an array containing original margins:
     * <ul>
            <li>$ret['left'] = left margin</li>
            <li>$ret['right'] = right margin</li>
     * </ul>
     * @return array containing all margins measures
     * @public
     * @since 4.0.012 (2008-07-24)
     */
    public function getOriginalMargins() {
        $ret = array(
            'left' => $this->original_lMargin,
            'right' => $this->original_rMargin
        );
        return $ret;
    }

    /**
     * Returns the current font size.
     * @return current font size
     * @public
     * @since 3.2.000 (2008-06-23)
     */
    public function getFontSize() {
        return $this->FontSize;
    }

    /**
     * Returns the current font size in points unit.
     * @return current font size in points unit
     * @public
     * @since 3.2.000 (2008-06-23)
     */
    public function getFontSizePt() {
        return $this->FontSizePt;
    }

    /**
     * Returns the current font family name.
     * @return string current font family name
     * @public
     * @since 4.3.008 (2008-12-05)
     */
    public function getFontFamily() {
        return $this->FontFamily;
    }

    /**
     * Returns the current font style.
     * @return string current font style
     * @public
     * @since 4.3.008 (2008-12-05)
     */
    public function getFontStyle() {
        return $this->FontStyle;
    }

    /**
     * Cleanup HTML code (requires HTML Tidy library).
     * @param $html (string) htmlcode to fix
     * @param $default_css (string) CSS commands to add
     * @param $tagvs (array) parameters for setHtmlVSpace method
     * @param $tidy_options (array) options for tidy_parse_string function
     * @return string XHTML code cleaned up
     * @author Nicola Asuni
     * @public
     * @since 5.9.017 (2010-11-16)
     * @see setHtmlVSpace()
     */
    public function fixHTMLCode($html, $default_css='', $tagvs='', $tidy_options='') {
        return TCPDF_STATIC::fixHTMLCode($html, $default_css, $tagvs, $tidy_options, $this->tagvspaces);
    }

    /**
     * Returns the border width from CSS property
     * @param $width (string) border width
     * @return int with in user units
     * @protected
     * @since 5.7.000 (2010-08-02)
     */
    protected function getCSSBorderWidth($width) {
        if ($width == 'thin') {
            $width = (2 / $this->k);
        } elseif ($width == 'medium') {
            $width = (4 / $this->k);
        } elseif ($width == 'thick') {
            $width = (6 / $this->k);
        } else {
            $width = $this->getHTMLUnitToUnits($width, 1, 'px', false);
        }
        return $width;
    }

    /**
     * Returns the border dash style from CSS property
     * @param $style (string) border style to convert
     * @return int sash style (return -1 in case of none or hidden border)
     * @protected
     * @since 5.7.000 (2010-08-02)
     */
    protected function getCSSBorderDashStyle($style) {
        switch (strtolower($style)) {
            case 'none':
            case 'hidden': {
                $dash = -1;
                break;
            }
            case 'dotted': {
                $dash = 1;
                break;
            }
            case 'dashed': {
                $dash = 3;
                break;
            }
            case 'double':
            case 'groove':
            case 'ridge':
            case 'inset':
            case 'outset':
            case 'solid':
            default: {
                $dash = 0;
                break;
            }
        }
        return $dash;
    }

    /**
     * Returns the border style array from CSS border properties
     * @param $cssborder (string) border properties
     * @return array containing border properties
     * @protected
     * @since 5.7.000 (2010-08-02)
     */
    protected function getCSSBorderStyle($cssborder) {
        $bprop = preg_split('/[\s]+/', trim($cssborder));
        $border = array(); // value to be returned
        switch (count($bprop)) {
            case 3: {
                $width = $bprop[0];
                $style = $bprop[1];
                $color = $bprop[2];
                break;
            }
            case 2: {
                $width = 'medium';
                $style = $bprop[0];
                $color = $bprop[1];
                break;
            }
            case 1: {
                $width = 'medium';
                $style = $bprop[0];
                $color = 'black';
                break;
            }
            default: {
                $width = 'medium';
                $style = 'solid';
                $color = 'black';
                break;
            }
        }
        if ($style == 'none') {
            return array();
        }
        $border['cap'] = 'square';
        $border['join'] = 'miter';
        $border['dash'] = $this->getCSSBorderDashStyle($style);
        if ($border['dash'] < 0) {
            return array();
        }
        $border['width'] = $this->getCSSBorderWidth($width);
        $border['color'] = TCPDF_COLORS::convertHTMLColorToDec($color, $this->spot_colors);
        return $border;
    }

    /**
     * Get the internal Cell padding from CSS attribute.
     * @param $csspadding (string) padding properties
     * @param $width (float) width of the containing element
     * @return array of cell paddings
     * @public
     * @since 5.9.000 (2010-10-04)
     */
    public function getCSSPadding($csspadding, $width=0) {
        $padding = preg_split('/[\s]+/', trim($csspadding));
        $cell_padding = array(); // value to be returned
        switch (count($padding)) {
            case 4: {
                $cell_padding['T'] = $padding[0];
                $cell_padding['R'] = $padding[1];
                $cell_padding['B'] = $padding[2];
                $cell_padding['L'] = $padding[3];
                break;
            }
            case 3: {
                $cell_padding['T'] = $padding[0];
                $cell_padding['R'] = $padding[1];
                $cell_padding['B'] = $padding[2];
                $cell_padding['L'] = $padding[1];
                break;
            }
            case 2: {
                $cell_padding['T'] = $padding[0];
                $cell_padding['R'] = $padding[1];
                $cell_padding['B'] = $padding[0];
                $cell_padding['L'] = $padding[1];
                break;
            }
            case 1: {
                $cell_padding['T'] = $padding[0];
                $cell_padding['R'] = $padding[0];
                $cell_padding['B'] = $padding[0];
                $cell_padding['L'] = $padding[0];
                break;
            }
            default: {
                return $this->cell_padding;
            }
        }
        if ($width == 0) {
            $width = $this->w - $this->lMargin - $this->rMargin;
        }
        $cell_padding['T'] = $this->getHTMLUnitToUnits($cell_padding['T'], $width, 'px', false);
        $cell_padding['R'] = $this->getHTMLUnitToUnits($cell_padding['R'], $width, 'px', false);
        $cell_padding['B'] = $this->getHTMLUnitToUnits($cell_padding['B'], $width, 'px', false);
        $cell_padding['L'] = $this->getHTMLUnitToUnits($cell_padding['L'], $width, 'px', false);
        return $cell_padding;
    }

    /**
     * Get the internal Cell margin from CSS attribute.
     * @param $cssmargin (string) margin properties
     * @param $width (float) width of the containing element
     * @return array of cell margins
     * @public
     * @since 5.9.000 (2010-10-04)
     */
    public function getCSSMargin($cssmargin, $width=0) {
        $margin = preg_split('/[\s]+/', trim($cssmargin));
        $cell_margin = array(); // value to be returned
        switch (count($margin)) {
            case 4: {
                $cell_margin['T'] = $margin[0];
                $cell_margin['R'] = $margin[1];
                $cell_margin['B'] = $margin[2];
                $cell_margin['L'] = $margin[3];
                break;
            }
            case 3: {
                $cell_margin['T'] = $margin[0];
                $cell_margin['R'] = $margin[1];
                $cell_margin['B'] = $margin[2];
                $cell_margin['L'] = $margin[1];
                break;
            }
            case 2: {
                $cell_margin['T'] = $margin[0];
                $cell_margin['R'] = $margin[1];
                $cell_margin['B'] = $margin[0];
                $cell_margin['L'] = $margin[1];
                break;
            }
            case 1: {
                $cell_margin['T'] = $margin[0];
                $cell_margin['R'] = $margin[0];
                $cell_margin['B'] = $margin[0];
                $cell_margin['L'] = $margin[0];
                break;
            }
            default: {
                return $this->cell_margin;
            }
        }
        if ($width == 0) {
            $width = $this->w - $this->lMargin - $this->rMargin;
        }
        $cell_margin['T'] = $this->getHTMLUnitToUnits(str_replace('auto', '0', $cell_margin['T']), $width, 'px', false);
        $cell_margin['R'] = $this->getHTMLUnitToUnits(str_replace('auto', '0', $cell_margin['R']), $width, 'px', false);
        $cell_margin['B'] = $this->getHTMLUnitToUnits(str_replace('auto', '0', $cell_margin['B']), $width, 'px', false);
        $cell_margin['L'] = $this->getHTMLUnitToUnits(str_replace('auto', '0', $cell_margin['L']), $width, 'px', false);
        return $cell_margin;
    }

    /**
     * Get the border-spacing from CSS attribute.
     * @param $cssbspace (string) border-spacing CSS properties
     * @param $width (float) width of the containing element
     * @return array of border spacings
     * @public
     * @since 5.9.010 (2010-10-27)
     */
    public function getCSSBorderMargin($cssbspace, $width=0) {
        $space = preg_split('/[\s]+/', trim($cssbspace));
        $border_spacing = array(); // value to be returned
        switch (count($space)) {
            case 2: {
                $border_spacing['H'] = $space[0];
                $border_spacing['V'] = $space[1];
                break;
            }
            case 1: {
                $border_spacing['H'] = $space[0];
                $border_spacing['V'] = $space[0];
                break;
            }
            default: {
                return array('H' => 0, 'V' => 0);
            }
        }
        if ($width == 0) {
            $width = $this->w - $this->lMargin - $this->rMargin;
        }
        $border_spacing['H'] = $this->getHTMLUnitToUnits($border_spacing['H'], $width, 'px', false);
        $border_spacing['V'] = $this->getHTMLUnitToUnits($border_spacing['V'], $width, 'px', false);
        return $border_spacing;
    }

    /**
     * Returns the letter-spacing value from CSS value
     * @param $spacing (string) letter-spacing value
     * @param $parent (float) font spacing (tracking) value of the parent element
     * @return float quantity to increases or decreases the space between characters in a text.
     * @protected
     * @since 5.9.000 (2010-10-02)
     */
    protected function getCSSFontSpacing($spacing, $parent=0) {
        $val = 0; // value to be returned
        $spacing = trim($spacing);
        switch ($spacing) {
            case 'normal': {
                $val = 0;
                break;
            }
            case 'inherit': {
                if ($parent == 'normal') {
                    $val = 0;
                } else {
                    $val = $parent;
                }
                break;
            }
            default: {
                $val = $this->getHTMLUnitToUnits($spacing, 0, 'px', false);
            }
        }
        return $val;
    }

    /**
     * Returns the percentage of font stretching from CSS value
     * @param $stretch (string) stretch mode
     * @param $parent (float) stretch value of the parent element
     * @return float font stretching percentage
     * @protected
     * @since 5.9.000 (2010-10-02)
     */
    protected function getCSSFontStretching($stretch, $parent=100) {
        $val = 100; // value to be returned
        $stretch = trim($stretch);
        switch ($stretch) {
            case 'ultra-condensed': {
                $val = 40;
                break;
            }
            case 'extra-condensed': {
                $val = 55;
                break;
            }
            case 'condensed': {
                $val = 70;
                break;
            }
            case 'semi-condensed': {
                $val = 85;
                break;
            }
            case 'normal': {
                $val = 100;
                break;
            }
            case 'semi-expanded': {
                $val = 115;
                break;
            }
            case 'expanded': {
                $val = 130;
                break;
            }
            case 'extra-expanded': {
                $val = 145;
                break;
            }
            case 'ultra-expanded': {
                $val = 160;
                break;
            }
            case 'wider': {
                $val = ($parent + 10);
                break;
            }
            case 'narrower': {
                $val = ($parent - 10);
                break;
            }
            case 'inherit': {
                if ($parent == 'normal') {
                    $val = 100;
                } else {
                    $val = $parent;
                }
                break;
            }
            default: {
                $val = $this->getHTMLUnitToUnits($stretch, 100, '%', false);
            }
        }
        return $val;
    }

    /**
     * Convert HTML string containing font size value to points
     * @param $val (string) String containing font size value and unit.
     * @param $refsize (float) Reference font size in points.
     * @param $parent_size (float) Parent font size in points.
     * @param $defaultunit (string) Default unit (can be one of the following: %, em, ex, px, in, mm, pc, pt).
     * @return float value in points
     * @public
     */
    public function getHTMLFontUnits($val, $refsize=12, $parent_size=12, $defaultunit='pt') {
        $refsize = TCPDF_FONTS::getFontRefSize($refsize);
        $parent_size = TCPDF_FONTS::getFontRefSize($parent_size, $refsize);
        switch ($val) {
            case 'xx-small': {
                $size = ($refsize - 4);
                break;
            }
            case 'x-small': {
                $size = ($refsize - 3);
                break;
            }
            case 'small': {
                $size = ($refsize - 2);
                break;
            }
            case 'medium': {
                $size = $refsize;
                break;
            }
            case 'large': {
                $size = ($refsize + 2);
                break;
            }
            case 'x-large': {
                $size = ($refsize + 4);
                break;
            }
            case 'xx-large': {
                $size = ($refsize + 6);
                break;
            }
            case 'smaller': {
                $size = ($parent_size - 3);
                break;
            }
            case 'larger': {
                $size = ($parent_size + 3);
                break;
            }
            default: {
                $size = $this->getHTMLUnitToUnits($val, $parent_size, $defaultunit, true);
            }
        }
        return $size;
    }

    /**
     * Returns the HTML DOM array.
     * @param $html (string) html code
     * @return array
     * @protected
     * @since 3.2.000 (2008-06-20)
     */
    protected function getHtmlDomArray($html) {
        // array of CSS styles ( selector => properties).
        $css = array();
        // get CSS array defined at previous call
        $matches = array();
        if (preg_match_all('/<cssarray>([^\<]*)<\/cssarray>/isU', $html, $matches) > 0) {
            if (isset($matches[1][0])) {
                $css = array_merge($css, json_decode($this->unhtmlentities($matches[1][0]), true));
            }
            $html = preg_replace('/<cssarray>(.*?)<\/cssarray>/isU', '', $html);
        }
        // extract external CSS files
        $matches = array();
        if (preg_match_all('/<link([^\>]*)>/isU', $html, $matches) > 0) {
            foreach ($matches[1] as $key => $link) {
                $type = array();
                if (preg_match('/type[\s]*=[\s]*"text\/css"/', $link, $type)) {
                    $type = array();
                    preg_match('/media[\s]*=[\s]*"([^"]*)"/', $link, $type);
                    // get 'all' and 'print' media, other media types are discarded
                    // (all, braille, embossed, handheld, print, projection, screen, speech, tty, tv)
                    if (empty($type) OR (isset($type[1]) AND (($type[1] == 'all') OR ($type[1] == 'print')))) {
                        $type = array();
                        if (preg_match('/href[\s]*=[\s]*"([^"]*)"/', $link, $type) > 0) {
                            // read CSS data file
                            $cssdata = TCPDF_STATIC::fileGetContents(trim($type[1]));
                            if (($cssdata !== FALSE) AND (strlen($cssdata) > 0)) {
                                $css = array_merge($css, TCPDF_STATIC::extractCSSproperties($cssdata));
                            }
                        }
                    }
                }
            }
        }
        // extract style tags
        $matches = array();
        if (preg_match_all('/<style([^\>]*)>([^\<]*)<\/style>/isU', $html, $matches) > 0) {
            foreach ($matches[1] as $key => $media) {
                $type = array();
                preg_match('/media[\s]*=[\s]*"([^"]*)"/', $media, $type);
                // get 'all' and 'print' media, other media types are discarded
                // (all, braille, embossed, handheld, print, projection, screen, speech, tty, tv)
                if (empty($type) OR (isset($type[1]) AND (($type[1] == 'all') OR ($type[1] == 'print')))) {
                    $cssdata = $matches[2][$key];
                    $css = array_merge($css, TCPDF_STATIC::extractCSSproperties($cssdata));
                }
            }
        }
        // create a special tag to contain the CSS array (used for table content)
        $csstagarray = '<cssarray>'.htmlentities(json_encode($css)).'</cssarray>';
        // remove head and style blocks
        $html = preg_replace('/<head([^\>]*)>(.*?)<\/head>/siU', '', $html);
        $html = preg_replace('/<style([^\>]*)>([^\<]*)<\/style>/isU', '', $html);
        // define block tags
        $blocktags = array('blockquote','br','dd','dl','div','dt','h1','h2','h3','h4','h5','h6','hr','li','ol','p','pre','ul','tcpdf','table','tr','td');
        // define self-closing tags
        $selfclosingtags = array('area','base','basefont','br','hr','input','img','link','meta');
        // remove all unsupported tags (the line below lists all supported tags)
        $html = strip_tags($html, '<marker/><a><b><blockquote><body><br><br/><dd><del><div><dl><dt><em><font><form><h1><h2><h3><h4><h5><h6><hr><hr/><i><img><input><label><li><ol><option><p><pre><s><select><small><span><strike><strong><sub><sup><table><tablehead><tcpdf><td><textarea><th><thead><tr><tt><u><ul>');
        //replace some blank characters
        $html = preg_replace('/<pre/', '<xre', $html); // preserve pre tag
        $html = preg_replace('/<(table|tr|td|th|tcpdf|blockquote|dd|div|dl|dt|form|h1|h2|h3|h4|h5|h6|br|hr|li|ol|ul|p)([^\>]*)>[\n\r\t]+/', '<\\1\\2>', $html);
        $html = preg_replace('@(\r\n|\r)@', "\n", $html);
        $repTable = array("\t" => ' ', "\0" => ' ', "\x0B" => ' ', "\\" => "\\\\");
        $html = strtr($html, $repTable);
        $offset = 0;
        while (($offset < strlen($html)) AND ($pos = strpos($html, '</pre>', $offset)) !== false) {
            $html_a = substr($html, 0, $offset);
            $html_b = substr($html, $offset, ($pos - $offset + 6));
            while (preg_match("'<xre([^\>]*)>(.*?)\n(.*?)</pre>'si", $html_b)) {
                // preserve newlines on <pre> tag
                $html_b = preg_replace("'<xre([^\>]*)>(.*?)\n(.*?)</pre>'si", "<xre\\1>\\2<br />\\3</pre>", $html_b);
            }
            while (preg_match("'<xre([^\>]*)>(.*?)".$this->re_space['p']."(.*?)</pre>'".$this->re_space['m'], $html_b)) {
                // preserve spaces on <pre> tag
                $html_b = preg_replace("'<xre([^\>]*)>(.*?)".$this->re_space['p']."(.*?)</pre>'".$this->re_space['m'], "<xre\\1>\\2&nbsp;\\3</pre>", $html_b);
            }
            $html = $html_a.$html_b.substr($html, $pos + 6);
            $offset = strlen($html_a.$html_b);
        }
        $offset = 0;
        while (($offset < strlen($html)) AND ($pos = strpos($html, '</textarea>', $offset)) !== false) {
            $html_a = substr($html, 0, $offset);
            $html_b = substr($html, $offset, ($pos - $offset + 11));
            while (preg_match("'<textarea([^\>]*)>(.*?)\n(.*?)</textarea>'si", $html_b)) {
                // preserve newlines on <textarea> tag
                $html_b = preg_replace("'<textarea([^\>]*)>(.*?)\n(.*?)</textarea>'si", "<textarea\\1>\\2<TBR>\\3</textarea>", $html_b);
                $html_b = preg_replace("'<textarea([^\>]*)>(.*?)[\"](.*?)</textarea>'si", "<textarea\\1>\\2''\\3</textarea>", $html_b);
            }
            $html = $html_a.$html_b.substr($html, $pos + 11);
            $offset = strlen($html_a.$html_b);
        }
        $html = preg_replace('/([\s]*)<option/si', '<option', $html);
        $html = preg_replace('/<\/option>([\s]*)/si', '</option>', $html);
        $offset = 0;
        while (($offset < strlen($html)) AND ($pos = strpos($html, '</option>', $offset)) !== false) {
            $html_a = substr($html, 0, $offset);
            $html_b = substr($html, $offset, ($pos - $offset + 9));
            while (preg_match("'<option([^\>]*)>(.*?)</option>'si", $html_b)) {
                $html_b = preg_replace("'<option([\s]+)value=\"([^\"]*)\"([^\>]*)>(.*?)</option>'si", "\\2#!TaB!#\\4#!NwL!#", $html_b);
                $html_b = preg_replace("'<option([^\>]*)>(.*?)</option>'si", "\\2#!NwL!#", $html_b);
            }
            $html = $html_a.$html_b.substr($html, $pos + 9);
            $offset = strlen($html_a.$html_b);
        }
        if (preg_match("'</select'si", $html)) {
            $html = preg_replace("'<select([^\>]*)>'si", "<select\\1 opt=\"", $html);
            $html = preg_replace("'#!NwL!#</select>'si", "\" />", $html);
        }
        $html = str_replace("\n", ' ', $html);
        // restore textarea newlines
        $html = str_replace('<TBR>', "\n", $html);
        // remove extra spaces from code
        $html = preg_replace('/[\s]+<\/(table|tr|ul|ol|dl)>/', '</\\1>', $html);
        $html = preg_replace('/'.$this->re_space['p'].'+<\/(td|th|li|dt|dd)>/'.$this->re_space['m'], '</\\1>', $html);
        $html = preg_replace('/[\s]+<(tr|td|th|li|dt|dd)/', '<\\1', $html);
        $html = preg_replace('/'.$this->re_space['p'].'+<(ul|ol|dl|br)/'.$this->re_space['m'], '<\\1', $html);
        $html = preg_replace('/<\/(table|tr|td|th|blockquote|dd|dt|dl|div|dt|h1|h2|h3|h4|h5|h6|hr|li|ol|ul|p)>[\s]+</', '</\\1><', $html);
        $html = preg_replace('/<\/(td|th)>/', '<marker style="font-size:0"/></\\1>', $html);
        $html = preg_replace('/<\/table>([\s]*)<marker style="font-size:0"\/>/', '</table>', $html);
        $html = preg_replace('/'.$this->re_space['p'].'+<img/'.$this->re_space['m'], chr(32).'<img', $html);
        $html = preg_replace('/<img([^\>]*)>[\s]+([^\<])/xi', '<img\\1>&nbsp;\\2', $html);
        $html = preg_replace('/<img([^\>]*)>/xi', '<img\\1><span><marker style="font-size:0"/></span>', $html);
        $html = preg_replace('/<xre/', '<pre', $html); // restore pre tag
        $html = preg_replace('/<textarea([^\>]*)>([^\<]*)<\/textarea>/xi', '<textarea\\1 value="\\2" />', $html);
        $html = preg_replace('/<li([^\>]*)><\/li>/', '<li\\1>&nbsp;</li>', $html);
        $html = preg_replace('/<li([^\>]*)>'.$this->re_space['p'].'*<img/'.$this->re_space['m'], '<li\\1><font size="1">&nbsp;</font><img', $html);
        $html = preg_replace('/<([^\>\/]*)>[\s]/', '<\\1>&nbsp;', $html); // preserve some spaces
        $html = preg_replace('/[\s]<\/([^\>]*)>/', '&nbsp;</\\1>', $html); // preserve some spaces
        $html = preg_replace('/<su([bp])/', '<zws/><su\\1', $html); // fix sub/sup alignment
        $html = preg_replace('/<\/su([bp])>/', '</su\\1><zws/>', $html); // fix sub/sup alignment
        $html = preg_replace('/'.$this->re_space['p'].'+/'.$this->re_space['m'], chr(32), $html); // replace multiple spaces with a single space
        // trim string
        $html = $this->stringTrim($html);
        // fix br tag after li
        $html = preg_replace('/<li><br([^\>]*)>/', '<li> <br\\1>', $html);
        // fix first image tag alignment
        $html = preg_replace('/^<img/', '<span style="font-size:0"><br /></span> <img', $html, 1);
        // pattern for generic tag
        $tagpattern = '/(<[^>]+>)/';
        // explodes the string
        $a = preg_split($tagpattern, $html, -1, PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY);
        // count elements
        $maxel = count($a);
        $elkey = 0;
        $key = 0;
        // create an array of elements
        $dom = array();
        $dom[$key] = array();
        // set inheritable properties fot the first void element
        // possible inheritable properties are: azimuth, border-collapse, border-spacing, caption-side, color, cursor, direction, empty-cells, font, font-family, font-stretch, font-size, font-size-adjust, font-style, font-variant, font-weight, letter-spacing, line-height, list-style, list-style-image, list-style-position, list-style-type, orphans, page, page-break-inside, quotes, speak, speak-header, text-align, text-indent, text-transform, volume, white-space, widows, word-spacing
        $dom[$key]['tag'] = false;
        $dom[$key]['block'] = false;
        $dom[$key]['value'] = '';
        $dom[$key]['parent'] = 0;
        $dom[$key]['hide'] = false;
        $dom[$key]['fontname'] = $this->FontFamily;
        $dom[$key]['fontstyle'] = $this->FontStyle;
        $dom[$key]['fontsize'] = $this->FontSizePt;
        $dom[$key]['font-stretch'] = $this->font_stretching;
        $dom[$key]['letter-spacing'] = $this->font_spacing;
        $dom[$key]['stroke'] = $this->textstrokewidth;
        $dom[$key]['fill'] = (($this->textrendermode % 2) == 0);
        $dom[$key]['clip'] = ($this->textrendermode > 3);
        $dom[$key]['line-height'] = $this->cell_height_ratio;
        $dom[$key]['bgcolor'] = false;
        $dom[$key]['fgcolor'] = $this->fgcolor; // color
        $dom[$key]['strokecolor'] = $this->strokecolor;
        $dom[$key]['align'] = '';
        $dom[$key]['listtype'] = '';
        $dom[$key]['text-indent'] = 0;
        $dom[$key]['text-transform'] = '';
        $dom[$key]['border'] = array();
        $dom[$key]['dir'] = $this->rtl?'rtl':'ltr';
        $thead = false; // true when we are inside the THEAD tag
        ++$key;
        $level = array();
        array_push($level, 0); // root
        while ($elkey < $maxel) {
            $dom[$key] = array();
            $element = $a[$elkey];
            $dom[$key]['elkey'] = $elkey;
            if (preg_match($tagpattern, $element)) {
                // html tag
                $element = substr($element, 1, -1);
                // get tag name
                preg_match('/[\/]?([a-zA-Z0-9]*)/', $element, $tag);
                $tagname = strtolower($tag[1]);
                // check if we are inside a table header
                if ($tagname == 'thead') {
                    if ($element[0] == '/') {
                        $thead = false;
                    } else {
                        $thead = true;
                    }
                    ++$elkey;
                    continue;
                }
                $dom[$key]['tag'] = true;
                $dom[$key]['value'] = $tagname;
                if (in_array($dom[$key]['value'], $blocktags)) {
                    $dom[$key]['block'] = true;
                } else {
                    $dom[$key]['block'] = false;
                }
                if ($element[0] == '/') {
                    // *** closing html tag
                    $dom[$key]['opening'] = false;
                    $dom[$key]['parent'] = end($level);
                    array_pop($level);
                    $dom[$key]['hide'] = $dom[($dom[($dom[$key]['parent'])]['parent'])]['hide'];
                    $dom[$key]['fontname'] = $dom[($dom[($dom[$key]['parent'])]['parent'])]['fontname'];
                    $dom[$key]['fontstyle'] = $dom[($dom[($dom[$key]['parent'])]['parent'])]['fontstyle'];
                    $dom[$key]['fontsize'] = $dom[($dom[($dom[$key]['parent'])]['parent'])]['fontsize'];
                    $dom[$key]['font-stretch'] = $dom[($dom[($dom[$key]['parent'])]['parent'])]['font-stretch'];
                    $dom[$key]['letter-spacing'] = $dom[($dom[($dom[$key]['parent'])]['parent'])]['letter-spacing'];
                    $dom[$key]['stroke'] = $dom[($dom[($dom[$key]['parent'])]['parent'])]['stroke'];
                    $dom[$key]['fill'] = $dom[($dom[($dom[$key]['parent'])]['parent'])]['fill'];
                    $dom[$key]['clip'] = $dom[($dom[($dom[$key]['parent'])]['parent'])]['clip'];
                    $dom[$key]['line-height'] = $dom[($dom[($dom[$key]['parent'])]['parent'])]['line-height'];
                    $dom[$key]['bgcolor'] = $dom[($dom[($dom[$key]['parent'])]['parent'])]['bgcolor'];
                    $dom[$key]['fgcolor'] = $dom[($dom[($dom[$key]['parent'])]['parent'])]['fgcolor'];
                    $dom[$key]['strokecolor'] = $dom[($dom[($dom[$key]['parent'])]['parent'])]['strokecolor'];
                    $dom[$key]['align'] = $dom[($dom[($dom[$key]['parent'])]['parent'])]['align'];
                    $dom[$key]['text-transform'] = $dom[($dom[($dom[$key]['parent'])]['parent'])]['text-transform'];
                    $dom[$key]['dir'] = $dom[($dom[($dom[$key]['parent'])]['parent'])]['dir'];
                    if (isset($dom[($dom[($dom[$key]['parent'])]['parent'])]['listtype'])) {
                        $dom[$key]['listtype'] = $dom[($dom[($dom[$key]['parent'])]['parent'])]['listtype'];
                    }
                    // set the number of columns in table tag
                    if (($dom[$key]['value'] == 'tr') AND (!isset($dom[($dom[($dom[$key]['parent'])]['parent'])]['cols']))) {
                        $dom[($dom[($dom[$key]['parent'])]['parent'])]['cols'] = $dom[($dom[$key]['parent'])]['cols'];
                    }
                    if (($dom[$key]['value'] == 'td') OR ($dom[$key]['value'] == 'th')) {
                        $dom[($dom[$key]['parent'])]['content'] = $csstagarray;
                        for ($i = ($dom[$key]['parent'] + 1); $i < $key; ++$i) {
                            $dom[($dom[$key]['parent'])]['content'] .= stripslashes($a[$dom[$i]['elkey']]);
                        }
                        $key = $i;
                        // mark nested tables
                        $dom[($dom[$key]['parent'])]['content'] = str_replace('<table', '<table nested="true"', $dom[($dom[$key]['parent'])]['content']);
                        // remove thead sections from nested tables
                        $dom[($dom[$key]['parent'])]['content'] = str_replace('<thead>', '', $dom[($dom[$key]['parent'])]['content']);
                        $dom[($dom[$key]['parent'])]['content'] = str_replace('</thead>', '', $dom[($dom[$key]['parent'])]['content']);
                    }
                    // store header rows on a new table
                    if (($dom[$key]['value'] == 'tr') AND ($dom[($dom[$key]['parent'])]['thead'] === true)) {
                        if (TCPDF_STATIC::empty_string($dom[($dom[($dom[$key]['parent'])]['parent'])]['thead'])) {
                            $dom[($dom[($dom[$key]['parent'])]['parent'])]['thead'] = $csstagarray.$a[$dom[($dom[($dom[$key]['parent'])]['parent'])]['elkey']];
                        }
                        for ($i = $dom[$key]['parent']; $i <= $key; ++$i) {
                            $dom[($dom[($dom[$key]['parent'])]['parent'])]['thead'] .= $a[$dom[$i]['elkey']];
                        }
                        if (!isset($dom[($dom[$key]['parent'])]['attribute'])) {
                            $dom[($dom[$key]['parent'])]['attribute'] = array();
                        }
                        // header elements must be always contained in a single page
                        $dom[($dom[$key]['parent'])]['attribute']['nobr'] = 'true';
                    }
                    if (($dom[$key]['value'] == 'table') AND (!TCPDF_STATIC::empty_string($dom[($dom[$key]['parent'])]['thead']))) {
                        // remove the nobr attributes from the table header
                        $dom[($dom[$key]['parent'])]['thead'] = str_replace(' nobr="true"', '', $dom[($dom[$key]['parent'])]['thead']);
                        $dom[($dom[$key]['parent'])]['thead'] .= '</tablehead>';
                    }
                } else {
                    // *** opening or self-closing html tag
                    $dom[$key]['opening'] = true;
                    $dom[$key]['parent'] = end($level);
                    if ((substr($element, -1, 1) == '/') OR (in_array($dom[$key]['value'], $selfclosingtags))) {
                        // self-closing tag
                        $dom[$key]['self'] = true;
                    } else {
                        // opening tag
                        array_push($level, $key);
                        $dom[$key]['self'] = false;
                    }
                    // copy some values from parent
                    $parentkey = 0;
                    if ($key > 0) {
                        $parentkey = $dom[$key]['parent'];
                        $dom[$key]['hide'] = $dom[$parentkey]['hide'];
                        $dom[$key]['fontname'] = $dom[$parentkey]['fontname'];
                        $dom[$key]['fontstyle'] = $dom[$parentkey]['fontstyle'];
                        $dom[$key]['fontsize'] = $dom[$parentkey]['fontsize'];
                        $dom[$key]['font-stretch'] = $dom[$parentkey]['font-stretch'];
                        $dom[$key]['letter-spacing'] = $dom[$parentkey]['letter-spacing'];
                        $dom[$key]['stroke'] = $dom[$parentkey]['stroke'];
                        $dom[$key]['fill'] = $dom[$parentkey]['fill'];
                        $dom[$key]['clip'] = $dom[$parentkey]['clip'];
                        $dom[$key]['line-height'] = $dom[$parentkey]['line-height'];
                        $dom[$key]['bgcolor'] = $dom[$parentkey]['bgcolor'];
                        $dom[$key]['fgcolor'] = $dom[$parentkey]['fgcolor'];
                        $dom[$key]['strokecolor'] = $dom[$parentkey]['strokecolor'];
                        $dom[$key]['align'] = $dom[$parentkey]['align'];
                        $dom[$key]['listtype'] = $dom[$parentkey]['listtype'];
                        $dom[$key]['text-indent'] = $dom[$parentkey]['text-indent'];
                        $dom[$key]['text-transform'] = $dom[$parentkey]['text-transform'];
                        $dom[$key]['border'] = array();
                        $dom[$key]['dir'] = $dom[$parentkey]['dir'];
                    }
                    // get attributes
                    preg_match_all('/([^=\s]*)[\s]*=[\s]*"([^"]*)"/', $element, $attr_array, PREG_PATTERN_ORDER);
                    $dom[$key]['attribute'] = array(); // reset attribute array
                    while (list($id, $name) = each($attr_array[1])) {
                        $dom[$key]['attribute'][strtolower($name)] = $attr_array[2][$id];
                    }
                    if (!empty($css)) {
                        // merge CSS style to current style
                        list($dom[$key]['csssel'], $dom[$key]['cssdata']) = TCPDF_STATIC::getCSSdataArray($dom, $key, $css);
                        $dom[$key]['attribute']['style'] = TCPDF_STATIC::getTagStyleFromCSSarray($dom[$key]['cssdata']);
                    }
                    // split style attributes
                    if (isset($dom[$key]['attribute']['style']) AND !empty($dom[$key]['attribute']['style'])) {
                        // get style attributes
                        preg_match_all('/([^;:\s]*):([^;]*)/', $dom[$key]['attribute']['style'], $style_array, PREG_PATTERN_ORDER);
                        $dom[$key]['style'] = array(); // reset style attribute array
                        while (list($id, $name) = each($style_array[1])) {
                            // in case of duplicate attribute the last replace the previous
                            $dom[$key]['style'][strtolower($name)] = trim($style_array[2][$id]);
                        }
                        // --- get some style attributes ---
                        // text direction
                        if (isset($dom[$key]['style']['direction'])) {
                            $dom[$key]['dir'] = $dom[$key]['style']['direction'];
                        }
                        // display
                        if (isset($dom[$key]['style']['display'])) {
                            $dom[$key]['hide'] = (trim(strtolower($dom[$key]['style']['display'])) == 'none');
                        }
                        // font family
                        if (isset($dom[$key]['style']['font-family'])) {
                            $dom[$key]['fontname'] = $this->getFontFamilyName($dom[$key]['style']['font-family']);
                        }
                        // list-style-type
                        if (isset($dom[$key]['style']['list-style-type'])) {
                            $dom[$key]['listtype'] = trim(strtolower($dom[$key]['style']['list-style-type']));
                            if ($dom[$key]['listtype'] == 'inherit') {
                                $dom[$key]['listtype'] = $dom[$parentkey]['listtype'];
                            }
                        }
                        // text-indent
                        if (isset($dom[$key]['style']['text-indent'])) {
                            $dom[$key]['text-indent'] = $this->getHTMLUnitToUnits($dom[$key]['style']['text-indent']);
                            if ($dom[$key]['text-indent'] == 'inherit') {
                                $dom[$key]['text-indent'] = $dom[$parentkey]['text-indent'];
                            }
                        }
                        // text-transform
                        if (isset($dom[$key]['style']['text-transform'])) {
                            $dom[$key]['text-transform'] = $dom[$key]['style']['text-transform'];
                        }
                        // font size
                        if (isset($dom[$key]['style']['font-size'])) {
                            $fsize = trim($dom[$key]['style']['font-size']);
                            $dom[$key]['fontsize'] = $this->getHTMLFontUnits($fsize, $dom[0]['fontsize'], $dom[$parentkey]['fontsize'], 'pt');
                        }
                        // font-stretch
                        if (isset($dom[$key]['style']['font-stretch'])) {
                            $dom[$key]['font-stretch'] = $this->getCSSFontStretching($dom[$key]['style']['font-stretch'], $dom[$parentkey]['font-stretch']);
                        }
                        // letter-spacing
                        if (isset($dom[$key]['style']['letter-spacing'])) {
                            $dom[$key]['letter-spacing'] = $this->getCSSFontSpacing($dom[$key]['style']['letter-spacing'], $dom[$parentkey]['letter-spacing']);
                        }
                        // line-height (internally is the cell height ratio)
                        if (isset($dom[$key]['style']['line-height'])) {
                            $lineheight = trim($dom[$key]['style']['line-height']);
                            switch ($lineheight) {
                                // A normal line height. This is default
                                case 'normal': {
                                    $dom[$key]['line-height'] = $dom[0]['line-height'];
                                    break;
                                }
                                case 'inherit': {
                                    $dom[$key]['line-height'] = $dom[$parentkey]['line-height'];
                                }
                                default: {
                                    if (is_numeric($lineheight)) {
                                        // convert to percentage of font height
                                        $lineheight = ($lineheight * 100).'%';
                                    }
                                    $dom[$key]['line-height'] = $this->getHTMLUnitToUnits($lineheight, 1, '%', true);
                                    if (substr($lineheight, -1) !== '%') {
                                        if ($dom[$key]['fontsize'] <= 0) {
                                            $dom[$key]['line-height'] = 1;
                                        } else {
                                            $dom[$key]['line-height'] = (($dom[$key]['line-height'] - $this->cell_padding['T'] - $this->cell_padding['B']) / $dom[$key]['fontsize']);
                                        }
                                    }
                                }
                            }
                        }
                        // font style
                        if (isset($dom[$key]['style']['font-weight'])) {
                            if (strtolower($dom[$key]['style']['font-weight'][0]) == 'n') {
                                if (strpos($dom[$key]['fontstyle'], 'B') !== false) {
                                    $dom[$key]['fontstyle'] = str_replace('B', '', $dom[$key]['fontstyle']);
                                }
                            } elseif (strtolower($dom[$key]['style']['font-weight'][0]) == 'b') {
                                $dom[$key]['fontstyle'] .= 'B';
                            }
                        }
                        if (isset($dom[$key]['style']['font-style']) AND (strtolower($dom[$key]['style']['font-style'][0]) == 'i')) {
                            $dom[$key]['fontstyle'] .= 'I';
                        }
                        // font color
                        if (isset($dom[$key]['style']['color']) AND (!TCPDF_STATIC::empty_string($dom[$key]['style']['color']))) {
                            $dom[$key]['fgcolor'] = TCPDF_COLORS::convertHTMLColorToDec($dom[$key]['style']['color'], $this->spot_colors);
                        } elseif ($dom[$key]['value'] == 'a') {
                            $dom[$key]['fgcolor'] = $this->htmlLinkColorArray;
                        }
                        // background color
                        if (isset($dom[$key]['style']['background-color']) AND (!TCPDF_STATIC::empty_string($dom[$key]['style']['background-color']))) {
                            $dom[$key]['bgcolor'] = TCPDF_COLORS::convertHTMLColorToDec($dom[$key]['style']['background-color'], $this->spot_colors);
                        }
                        // text-decoration
                        if (isset($dom[$key]['style']['text-decoration'])) {
                            $decors = explode(' ', strtolower($dom[$key]['style']['text-decoration']));
                            foreach ($decors as $dec) {
                                $dec = trim($dec);
                                if (!TCPDF_STATIC::empty_string($dec)) {
                                    if ($dec[0] == 'u') {
                                        // underline
                                        $dom[$key]['fontstyle'] .= 'U';
                                    } elseif ($dec[0] == 'l') {
                                        // line-through
                                        $dom[$key]['fontstyle'] .= 'D';
                                    } elseif ($dec[0] == 'o') {
                                        // overline
                                        $dom[$key]['fontstyle'] .= 'O';
                                    }
                                }
                            }
                        } elseif ($dom[$key]['value'] == 'a') {
                            $dom[$key]['fontstyle'] = $this->htmlLinkFontStyle;
                        }
                        // check for width attribute
                        if (isset($dom[$key]['style']['width'])) {
                            $dom[$key]['width'] = $dom[$key]['style']['width'];
                        }
                        // check for height attribute
                        if (isset($dom[$key]['style']['height'])) {
                            $dom[$key]['height'] = $dom[$key]['style']['height'];
                        }
                        // check for text alignment
                        if (isset($dom[$key]['style']['text-align'])) {
                            $dom[$key]['align'] = strtoupper($dom[$key]['style']['text-align'][0]);
                        }
                        // check for CSS border properties
                        if (isset($dom[$key]['style']['border'])) {
                            $borderstyle = $this->getCSSBorderStyle($dom[$key]['style']['border']);
                            if (!empty($borderstyle)) {
                                $dom[$key]['border']['LTRB'] = $borderstyle;
                            }
                        }
                        if (isset($dom[$key]['style']['border-color'])) {
                            $brd_colors = preg_split('/[\s]+/', trim($dom[$key]['style']['border-color']));
                            if (isset($brd_colors[3])) {
                                $dom[$key]['border']['L']['color'] = TCPDF_COLORS::convertHTMLColorToDec($brd_colors[3], $this->spot_colors);
                            }
                            if (isset($brd_colors[1])) {
                                $dom[$key]['border']['R']['color'] = TCPDF_COLORS::convertHTMLColorToDec($brd_colors[1], $this->spot_colors);
                            }
                            if (isset($brd_colors[0])) {
                                $dom[$key]['border']['T']['color'] = TCPDF_COLORS::convertHTMLColorToDec($brd_colors[0], $this->spot_colors);
                            }
                            if (isset($brd_colors[2])) {
                                $dom[$key]['border']['B']['color'] = TCPDF_COLORS::convertHTMLColorToDec($brd_colors[2], $this->spot_colors);
                            }
                        }
                        if (isset($dom[$key]['style']['border-width'])) {
                            $brd_widths = preg_split('/[\s]+/', trim($dom[$key]['style']['border-width']));
                            if (isset($brd_widths[3])) {
                                $dom[$key]['border']['L']['width'] = $this->getCSSBorderWidth($brd_widths[3]);
                            }
                            if (isset($brd_widths[1])) {
                                $dom[$key]['border']['R']['width'] = $this->getCSSBorderWidth($brd_widths[1]);
                            }
                            if (isset($brd_widths[0])) {
                                $dom[$key]['border']['T']['width'] = $this->getCSSBorderWidth($brd_widths[0]);
                            }
                            if (isset($brd_widths[2])) {
                                $dom[$key]['border']['B']['width'] = $this->getCSSBorderWidth($brd_widths[2]);
                            }
                        }
                        if (isset($dom[$key]['style']['border-style'])) {
                            $brd_styles = preg_split('/[\s]+/', trim($dom[$key]['style']['border-style']));
                            if (isset($brd_styles[3]) AND ($brd_styles[3]!='none')) {
                                $dom[$key]['border']['L']['cap'] = 'square';
                                $dom[$key]['border']['L']['join'] = 'miter';
                                $dom[$key]['border']['L']['dash'] = $this->getCSSBorderDashStyle($brd_styles[3]);
                                if ($dom[$key]['border']['L']['dash'] < 0) {
                                    $dom[$key]['border']['L'] = array();
                                }
                            }
                            if (isset($brd_styles[1])) {
                                $dom[$key]['border']['R']['cap'] = 'square';
                                $dom[$key]['border']['R']['join'] = 'miter';
                                $dom[$key]['border']['R']['dash'] = $this->getCSSBorderDashStyle($brd_styles[1]);
                                if ($dom[$key]['border']['R']['dash'] < 0) {
                                    $dom[$key]['border']['R'] = array();
                                }
                            }
                            if (isset($brd_styles[0])) {
                                $dom[$key]['border']['T']['cap'] = 'square';
                                $dom[$key]['border']['T']['join'] = 'miter';
                                $dom[$key]['border']['T']['dash'] = $this->getCSSBorderDashStyle($brd_styles[0]);
                                if ($dom[$key]['border']['T']['dash'] < 0) {
                                    $dom[$key]['border']['T'] = array();
                                }
                            }
                            if (isset($brd_styles[2])) {
                                $dom[$key]['border']['B']['cap'] = 'square';
                                $dom[$key]['border']['B']['join'] = 'miter';
                                $dom[$key]['border']['B']['dash'] = $this->getCSSBorderDashStyle($brd_styles[2]);
                                if ($dom[$key]['border']['B']['dash'] < 0) {
                                    $dom[$key]['border']['B'] = array();
                                }
                            }
                        }
                        $cellside = array('L' => 'left', 'R' => 'right', 'T' => 'top', 'B' => 'bottom');
                        foreach ($cellside as $bsk => $bsv) {
                            if (isset($dom[$key]['style']['border-'.$bsv])) {
                                $borderstyle = $this->getCSSBorderStyle($dom[$key]['style']['border-'.$bsv]);
                                if (!empty($borderstyle)) {
                                    $dom[$key]['border'][$bsk] = $borderstyle;
                                }
                            }
                            if (isset($dom[$key]['style']['border-'.$bsv.'-color'])) {
                                $dom[$key]['border'][$bsk]['color'] = TCPDF_COLORS::convertHTMLColorToDec($dom[$key]['style']['border-'.$bsv.'-color'], $this->spot_colors);
                            }
                            if (isset($dom[$key]['style']['border-'.$bsv.'-width'])) {
                                $dom[$key]['border'][$bsk]['width'] = $this->getCSSBorderWidth($dom[$key]['style']['border-'.$bsv.'-width']);
                            }
                            if (isset($dom[$key]['style']['border-'.$bsv.'-style'])) {
                                $dom[$key]['border'][$bsk]['dash'] = $this->getCSSBorderDashStyle($dom[$key]['style']['border-'.$bsv.'-style']);
                                if ($dom[$key]['border'][$bsk]['dash'] < 0) {
                                    $dom[$key]['border'][$bsk] = array();
                                }
                            }
                        }
                        // check for CSS padding properties
                        if (isset($dom[$key]['style']['padding'])) {
                            $dom[$key]['padding'] = $this->getCSSPadding($dom[$key]['style']['padding']);
                        } else {
                            $dom[$key]['padding'] = $this->cell_padding;
                        }
                        foreach ($cellside as $psk => $psv) {
                            if (isset($dom[$key]['style']['padding-'.$psv])) {
                                $dom[$key]['padding'][$psk] = $this->getHTMLUnitToUnits($dom[$key]['style']['padding-'.$psv], 0, 'px', false);
                            }
                        }
                        // check for CSS margin properties
                        if (isset($dom[$key]['style']['margin'])) {
                            $dom[$key]['margin'] = $this->getCSSMargin($dom[$key]['style']['margin']);
                        } else {
                            $dom[$key]['margin'] = $this->cell_margin;
                        }
                        foreach ($cellside as $psk => $psv) {
                            if (isset($dom[$key]['style']['margin-'.$psv])) {
                                $dom[$key]['margin'][$psk] = $this->getHTMLUnitToUnits(str_replace('auto', '0', $dom[$key]['style']['margin-'.$psv]), 0, 'px', false);
                            }
                        }
                        // check for CSS border-spacing properties
                        if (isset($dom[$key]['style']['border-spacing'])) {
                            $dom[$key]['border-spacing'] = $this->getCSSBorderMargin($dom[$key]['style']['border-spacing']);
                        }
                        // page-break-inside
                        if (isset($dom[$key]['style']['page-break-inside']) AND ($dom[$key]['style']['page-break-inside'] == 'avoid')) {
                            $dom[$key]['attribute']['nobr'] = 'true';
                        }
                        // page-break-before
                        if (isset($dom[$key]['style']['page-break-before'])) {
                            if ($dom[$key]['style']['page-break-before'] == 'always') {
                                $dom[$key]['attribute']['pagebreak'] = 'true';
                            } elseif ($dom[$key]['style']['page-break-before'] == 'left') {
                                $dom[$key]['attribute']['pagebreak'] = 'left';
                            } elseif ($dom[$key]['style']['page-break-before'] == 'right') {
                                $dom[$key]['attribute']['pagebreak'] = 'right';
                            }
                        }
                        // page-break-after
                        if (isset($dom[$key]['style']['page-break-after'])) {
                            if ($dom[$key]['style']['page-break-after'] == 'always') {
                                $dom[$key]['attribute']['pagebreakafter'] = 'true';
                            } elseif ($dom[$key]['style']['page-break-after'] == 'left') {
                                $dom[$key]['attribute']['pagebreakafter'] = 'left';
                            } elseif ($dom[$key]['style']['page-break-after'] == 'right') {
                                $dom[$key]['attribute']['pagebreakafter'] = 'right';
                            }
                        }
                    }
                    if (isset($dom[$key]['attribute']['display'])) {
                        $dom[$key]['hide'] = (trim(strtolower($dom[$key]['attribute']['display'])) == 'none');
                    }
                    if (isset($dom[$key]['attribute']['border']) AND ($dom[$key]['attribute']['border'] != 0)) {
                        $borderstyle = $this->getCSSBorderStyle($dom[$key]['attribute']['border'].' solid black');
                        if (!empty($borderstyle)) {
                            $dom[$key]['border']['LTRB'] = $borderstyle;
                        }
                    }
                    // check for font tag
                    if ($dom[$key]['value'] == 'font') {
                        // font family
                        if (isset($dom[$key]['attribute']['face'])) {
                            $dom[$key]['fontname'] = $this->getFontFamilyName($dom[$key]['attribute']['face']);
                        }
                        // font size
                        if (isset($dom[$key]['attribute']['size'])) {
                            if ($key > 0) {
                                if ($dom[$key]['attribute']['size'][0] == '+') {
                                    $dom[$key]['fontsize'] = $dom[($dom[$key]['parent'])]['fontsize'] + intval(substr($dom[$key]['attribute']['size'], 1));
                                } elseif ($dom[$key]['attribute']['size'][0] == '-') {
                                    $dom[$key]['fontsize'] = $dom[($dom[$key]['parent'])]['fontsize'] - intval(substr($dom[$key]['attribute']['size'], 1));
                                } else {
                                    $dom[$key]['fontsize'] = intval($dom[$key]['attribute']['size']);
                                }
                            } else {
                                $dom[$key]['fontsize'] = intval($dom[$key]['attribute']['size']);
                            }
                        }
                    }
                    // force natural alignment for lists
                    if ((($dom[$key]['value'] == 'ul') OR ($dom[$key]['value'] == 'ol') OR ($dom[$key]['value'] == 'dl'))
                        AND (!isset($dom[$key]['align']) OR TCPDF_STATIC::empty_string($dom[$key]['align']) OR ($dom[$key]['align'] != 'J'))) {
                        if ($this->rtl) {
                            $dom[$key]['align'] = 'R';
                        } else {
                            $dom[$key]['align'] = 'L';
                        }
                    }
                    if (($dom[$key]['value'] == 'small') OR ($dom[$key]['value'] == 'sup') OR ($dom[$key]['value'] == 'sub')) {
                        if (!isset($dom[$key]['attribute']['size']) AND !isset($dom[$key]['style']['font-size'])) {
                            $dom[$key]['fontsize'] = $dom[$key]['fontsize'] * K_SMALL_RATIO;
                        }
                    }
                    if (($dom[$key]['value'] == 'strong') OR ($dom[$key]['value'] == 'b')) {
                        $dom[$key]['fontstyle'] .= 'B';
                    }
                    if (($dom[$key]['value'] == 'em') OR ($dom[$key]['value'] == 'i')) {
                        $dom[$key]['fontstyle'] .= 'I';
                    }
                    if ($dom[$key]['value'] == 'u') {
                        $dom[$key]['fontstyle'] .= 'U';
                    }
                    if (($dom[$key]['value'] == 'del') OR ($dom[$key]['value'] == 's') OR ($dom[$key]['value'] == 'strike')) {
                        $dom[$key]['fontstyle'] .= 'D';
                    }
                    if (!isset($dom[$key]['style']['text-decoration']) AND ($dom[$key]['value'] == 'a')) {
                        $dom[$key]['fontstyle'] = $this->htmlLinkFontStyle;
                    }
                    if (($dom[$key]['value'] == 'pre') OR ($dom[$key]['value'] == 'tt')) {
                        $dom[$key]['fontname'] = $this->default_monospaced_font;
                    }
                    if (!empty($dom[$key]['value']) AND ($dom[$key]['value'][0] == 'h') AND (intval($dom[$key]['value']{1}) > 0) AND (intval($dom[$key]['value']{1}) < 7)) {
                        // headings h1, h2, h3, h4, h5, h6
                        if (!isset($dom[$key]['attribute']['size']) AND !isset($dom[$key]['style']['font-size'])) {
                            $headsize = (4 - intval($dom[$key]['value']{1})) * 2;
                            $dom[$key]['fontsize'] = $dom[0]['fontsize'] + $headsize;
                        }
                        if (!isset($dom[$key]['style']['font-weight'])) {
                            $dom[$key]['fontstyle'] .= 'B';
                        }
                    }
                    if (($dom[$key]['value'] == 'table')) {
                        $dom[$key]['rows'] = 0; // number of rows
                        $dom[$key]['trids'] = array(); // IDs of TR elements
                        $dom[$key]['thead'] = ''; // table header rows
                    }
                    if (($dom[$key]['value'] == 'tr')) {
                        $dom[$key]['cols'] = 0;
                        if ($thead) {
                            $dom[$key]['thead'] = true;
                            // rows on thead block are printed as a separate table
                        } else {
                            $dom[$key]['thead'] = false;
                            // store the number of rows on table element
                            ++$dom[($dom[$key]['parent'])]['rows'];
                            // store the TR elements IDs on table element
                            array_push($dom[($dom[$key]['parent'])]['trids'], $key);
                        }
                    }
                    if (($dom[$key]['value'] == 'th') OR ($dom[$key]['value'] == 'td')) {
                        if (isset($dom[$key]['attribute']['colspan'])) {
                            $colspan = intval($dom[$key]['attribute']['colspan']);
                        } else {
                            $colspan = 1;
                        }
                        $dom[$key]['attribute']['colspan'] = $colspan;
                        $dom[($dom[$key]['parent'])]['cols'] += $colspan;
                    }
                    // text direction
                    if (isset($dom[$key]['attribute']['dir'])) {
                        $dom[$key]['dir'] = $dom[$key]['attribute']['dir'];
                    }
                    // set foreground color attribute
                    if (isset($dom[$key]['attribute']['color']) AND (!TCPDF_STATIC::empty_string($dom[$key]['attribute']['color']))) {
                        $dom[$key]['fgcolor'] = TCPDF_COLORS::convertHTMLColorToDec($dom[$key]['attribute']['color'], $this->spot_colors);
                    } elseif (!isset($dom[$key]['style']['color']) AND ($dom[$key]['value'] == 'a')) {
                        $dom[$key]['fgcolor'] = $this->htmlLinkColorArray;
                    }
                    // set background color attribute
                    if (isset($dom[$key]['attribute']['bgcolor']) AND (!TCPDF_STATIC::empty_string($dom[$key]['attribute']['bgcolor']))) {
                        $dom[$key]['bgcolor'] = TCPDF_COLORS::convertHTMLColorToDec($dom[$key]['attribute']['bgcolor'], $this->spot_colors);
                    }
                    // set stroke color attribute
                    if (isset($dom[$key]['attribute']['strokecolor']) AND (!TCPDF_STATIC::empty_string($dom[$key]['attribute']['strokecolor']))) {
                        $dom[$key]['strokecolor'] = TCPDF_COLORS::convertHTMLColorToDec($dom[$key]['attribute']['strokecolor'], $this->spot_colors);
                    }
                    // check for width attribute
                    if (isset($dom[$key]['attribute']['width'])) {
                        $dom[$key]['width'] = $dom[$key]['attribute']['width'];
                    }
                    // check for height attribute
                    if (isset($dom[$key]['attribute']['height'])) {
                        $dom[$key]['height'] = $dom[$key]['attribute']['height'];
                    }
                    // check for text alignment
                    if (isset($dom[$key]['attribute']['align']) AND (!TCPDF_STATIC::empty_string($dom[$key]['attribute']['align'])) AND ($dom[$key]['value'] !== 'img')) {
                        $dom[$key]['align'] = strtoupper($dom[$key]['attribute']['align'][0]);
                    }
                    // check for text rendering mode (the following attributes do not exist in HTML)
                    if (isset($dom[$key]['attribute']['stroke'])) {
                        // font stroke width
                        $dom[$key]['stroke'] = $this->getHTMLUnitToUnits($dom[$key]['attribute']['stroke'], $dom[$key]['fontsize'], 'pt', true);
                    }
                    if (isset($dom[$key]['attribute']['fill'])) {
                        // font fill
                        if ($dom[$key]['attribute']['fill'] == 'true') {
                            $dom[$key]['fill'] = true;
                        } else {
                            $dom[$key]['fill'] = false;
                        }
                    }
                    if (isset($dom[$key]['attribute']['clip'])) {
                        // clipping mode
                        if ($dom[$key]['attribute']['clip'] == 'true') {
                            $dom[$key]['clip'] = true;
                        } else {
                            $dom[$key]['clip'] = false;
                        }
                    }
                } // end opening tag
            } else {
                // text
                $dom[$key]['tag'] = false;
                $dom[$key]['block'] = false;
                $dom[$key]['parent'] = end($level);
                $dom[$key]['dir'] = $dom[$dom[$key]['parent']]['dir'];
                if (!empty($dom[$dom[$key]['parent']]['text-transform'])) {
                    // text-transform for unicode requires mb_convert_case (Multibyte String Functions)
                    if (function_exists('mb_convert_case')) {
                        $ttm = array('capitalize' => MB_CASE_TITLE, 'uppercase' => MB_CASE_UPPER, 'lowercase' => MB_CASE_LOWER);
                        if (isset($ttm[$dom[$dom[$key]['parent']]['text-transform']])) {
                            $element = mb_convert_case($element, $ttm[$dom[$dom[$key]['parent']]['text-transform']], $this->encoding);
                        }
                    } elseif (!$this->isunicode) {
                        switch ($dom[$dom[$key]['parent']]['text-transform']) {
                            case 'capitalize': {
                                $element = ucwords(strtolower($element));
                                break;
                            }
                            case 'uppercase': {
                                $element = strtoupper($element);
                                break;
                            }
                            case 'lowercase': {
                                $element = strtolower($element);
                                break;
                            }
                        }
                    }
                }
                $dom[$key]['value'] = stripslashes($this->unhtmlentities($element));
            }
            ++$elkey;
            ++$key;
        }
        return $dom;
    }

    /**
     * Returns the string used to find spaces
     * @return string
     * @protected
     * @author Nicola Asuni
     * @since 4.8.024 (2010-01-15)
     */
    protected function getSpaceString() {
        $spacestr = chr(32);
        if ($this->isUnicodeFont()) {
            $spacestr = chr(0).chr(32);
        }
        return $spacestr;
    }

    /**
     * Return an hash code used to ensure that the serialized data has been generated by this TCPDF instance.
     * @param $data (string) serialized data
     * @return string
     * @public static
     */
    protected function getHashForTCPDFtagParams($data) {
        return md5(strlen($data).$this->file_id.$data);
    }

    /**
     * Serialize an array of parameters to be used with TCPDF tag in HTML code.
     * @param $data (array) parameters array
     * @return string containing serialized data
     * @public static
     */
    public function serializeTCPDFtagParameters($data) {
        $encoded = urlencode(json_encode($data));
        return $this->getHashForTCPDFtagParams($encoded).$encoded;
    }

    /**
     * Unserialize parameters to be used with TCPDF tag in HTML code.
     * @param $data (string) serialized data
     * @return array containing unserialized data
     * @protected static
     */
    protected function unserializeTCPDFtagParameters($data) {
        $hash = substr($data, 0, 32);
        $encoded = substr($data, 32);
        if ($hash != $this->getHashForTCPDFtagParams($encoded)) {
            $this->Error('Invalid parameters');
        }
        return json_decode(urldecode($encoded), true);
    }

    /**
     * Prints a cell (rectangular area) with optional borders, background color and html text string.
     * The upper-left corner of the cell corresponds to the current position. After the call, the current position moves to the right or to the next line.<br />
     * If automatic page breaking is enabled and the cell goes beyond the limit, a page break is done before outputting.
     * IMPORTANT: The HTML must be well formatted - try to clean-up it using an application like HTML-Tidy before submitting.
     * Supported tags are: a, b, blockquote, br, dd, del, div, dl, dt, em, font, h1, h2, h3, h4, h5, h6, hr, i, img, li, ol, p, pre, small, span, strong, sub, sup, table, tcpdf, td, th, thead, tr, tt, u, ul
     * NOTE: all the HTML attributes must be enclosed in double-quote.
     * @param $w (float) Cell width. If 0, the cell extends up to the right margin.
     * @param $h (float) Cell minimum height. The cell extends automatically if needed.
     * @param $x (float) upper-left corner X coordinate
     * @param $y (float) upper-left corner Y coordinate
     * @param $html (string) html text to print. Default value: empty string.
     * @param $border (mixed) Indicates if borders must be drawn around the cell. The value can be a number:<ul><li>0: no border (default)</li><li>1: frame</li></ul> or a string containing some or all of the following characters (in any order):<ul><li>L: left</li><li>T: top</li><li>R: right</li><li>B: bottom</li></ul> or an array of line styles for each border group - for example: array('LTRB' => array('width' => 2, 'cap' => 'butt', 'join' => 'miter', 'dash' => 0, 'color' => array(0, 0, 0)))
     * @param $ln (int) Indicates where the current position should go after the call. Possible values are:<ul><li>0: to the right (or left for RTL language)</li><li>1: to the beginning of the next line</li><li>2: below</li></ul>
Putting 1 is equivalent to putting 0 and calling Ln() just after. Default value: 0.
     * @param $fill (boolean) Indicates if the cell background must be painted (true) or transparent (false).
     * @param $reseth (boolean) if true reset the last cell height (default true).
     * @param $align (string) Allows to center or align the text. Possible values are:<ul><li>L : left align</li><li>C : center</li><li>R : right align</li><li>'' : empty string : left for LTR or right for RTL</li></ul>
     * @param $autopadding (boolean) if true, uses internal padding and automatically adjust it to account for line width.
     * @see Multicell(), writeHTML()
     * @public
     */
    public function writeHTMLCell($w, $h, $x, $y, $html='', $border=0, $ln=0, $fill=false, $reseth=true, $align='', $autopadding=true) {
        return $this->MultiCell($w, $h, $html, $border, $align, $fill, $ln, $x, $y, $reseth, 0, true, $autopadding, 0, 'T', false);
    }

    /**
     * Allows to preserve some HTML formatting (limited support).<br />
     * IMPORTANT: The HTML must be well formatted - try to clean-up it using an application like HTML-Tidy before submitting.
     * Supported tags are: a, b, blockquote, br, dd, del, div, dl, dt, em, font, h1, h2, h3, h4, h5, h6, hr, i, img, li, ol, p, pre, small, span, strong, sub, sup, table, tcpdf, td, th, thead, tr, tt, u, ul
     * NOTE: all the HTML attributes must be enclosed in double-quote.
     * @param $html (string) text to display
     * @param $ln (boolean) if true add a new line after text (default = true)
     * @param $fill (boolean) Indicates if the background must be painted (true) or transparent (false).
     * @param $reseth (boolean) if true reset the last cell height (default false).
     * @param $cell (boolean) if true add the current left (or right for RTL) padding to each Write (default false).
     * @param $align (string) Allows to center or align the text. Possible values are:<ul><li>L : left align</li><li>C : center</li><li>R : right align</li><li>'' : empty string : left for LTR or right for RTL</li></ul>
     * @public
     */
    public function writeHTML($html, $ln=true, $fill=false, $reseth=false, $cell=false, $align='') {
        $gvars = $this->getGraphicVars();
        // store current values
        $prev_cell_margin = $this->cell_margin;
        $prev_cell_padding = $this->cell_padding;
        $prevPage = $this->page;
        $prevlMargin = $this->lMargin;
        $prevrMargin = $this->rMargin;
        $curfontname = $this->FontFamily;
        $curfontstyle = $this->FontStyle;
        $curfontsize = $this->FontSizePt;
        $curfontascent = $this->getFontAscent($curfontname, $curfontstyle, $curfontsize);
        $curfontdescent = $this->getFontDescent($curfontname, $curfontstyle, $curfontsize);
        $curfontstretcing = $this->font_stretching;
        $curfonttracking = $this->font_spacing;
        $this->newline = true;
        $newline = true;
        $startlinepage = $this->page;
        $minstartliney = $this->y;
        $maxbottomliney = 0;
        $startlinex = $this->x;
        $startliney = $this->y;
        $yshift = 0;
        $loop = 0;
        $curpos = 0;
        $this_method_vars = array();
        $undo = false;
        $fontaligned = false;
        $reverse_dir = false; // true when the text direction is reversed
        $this->premode = false;
        if ($this->inxobj) {
            // we are inside an XObject template
            $pask = count($this->xobjects[$this->xobjid]['annotations']);
        } elseif (isset($this->PageAnnots[$this->page])) {
            $pask = count($this->PageAnnots[$this->page]);
        } else {
            $pask = 0;
        }
        if ($this->inxobj) {
            // we are inside an XObject template
            $startlinepos = strlen($this->xobjects[$this->xobjid]['outdata']);
        } elseif (!$this->InFooter) {
            if (isset($this->footerlen[$this->page])) {
                $this->footerpos[$this->page] = $this->pagelen[$this->page] - $this->footerlen[$this->page];
            } else {
                $this->footerpos[$this->page] = $this->pagelen[$this->page];
            }
            $startlinepos = $this->footerpos[$this->page];
        } else {
            // we are inside the footer
            $startlinepos = $this->pagelen[$this->page];
        }
        $lalign = $align;
        $plalign = $align;
        if ($this->rtl) {
            $w = $this->x - $this->lMargin;
        } else {
            $w = $this->w - $this->rMargin - $this->x;
        }
        $w -= ($this->cell_padding['L'] + $this->cell_padding['R']);
        if ($cell) {
            if ($this->rtl) {
                $this->x -= $this->cell_padding['R'];
                $this->lMargin += $this->cell_padding['R'];
            } else {
                $this->x += $this->cell_padding['L'];
                $this->rMargin += $this->cell_padding['L'];
            }
        }
        if ($this->customlistindent >= 0) {
            $this->listindent = $this->customlistindent;
        } else {
            $this->listindent = $this->GetStringWidth('000000');
        }
        $this->listindentlevel = 0;
        // save previous states
        $prev_cell_height_ratio = $this->cell_height_ratio;
        $prev_listnum = $this->listnum;
        $prev_listordered = $this->listordered;
        $prev_listcount = $this->listcount;
        $prev_lispacer = $this->lispacer;
        $this->listnum = 0;
        $this->listordered = array();
        $this->listcount = array();
        $this->lispacer = '';
        if ((TCPDF_STATIC::empty_string($this->lasth)) OR ($reseth)) {
            // reset row height
            $this->resetLastH();
        }
        $dom = $this->getHtmlDomArray($html);
        $maxel = count($dom);
        $key = 0;
        while ($key < $maxel) {
            if ($dom[$key]['tag'] AND $dom[$key]['opening'] AND $dom[$key]['hide']) {
                // store the node key
                $hidden_node_key = $key;
                if ($dom[$key]['self']) {
                    // skip just this self-closing tag
                    ++$key;
                } else {
                    // skip this and all children tags
                    while (($key < $maxel) AND (!$dom[$key]['tag'] OR $dom[$key]['opening'] OR ($dom[$key]['parent'] != $hidden_node_key))) {
                        // skip hidden objects
                        ++$key;
                    }
                    ++$key;
                }
            }
            if ($dom[$key]['tag'] AND isset($dom[$key]['attribute']['pagebreak'])) {
                // check for pagebreak
                if (($dom[$key]['attribute']['pagebreak'] == 'true') OR ($dom[$key]['attribute']['pagebreak'] == 'left') OR ($dom[$key]['attribute']['pagebreak'] == 'right')) {
                    // add a page (or trig AcceptPageBreak() for multicolumn mode)
                    $this->checkPageBreak($this->PageBreakTrigger + 1);
                    $this->htmlvspace = ($this->PageBreakTrigger + 1);
                }
                if ((($dom[$key]['attribute']['pagebreak'] == 'left') AND (((!$this->rtl) AND (($this->page % 2) == 0)) OR (($this->rtl) AND (($this->page % 2) != 0))))
                    OR (($dom[$key]['attribute']['pagebreak'] == 'right') AND (((!$this->rtl) AND (($this->page % 2) != 0)) OR (($this->rtl) AND (($this->page % 2) == 0))))) {
                    // add a page (or trig AcceptPageBreak() for multicolumn mode)
                    $this->checkPageBreak($this->PageBreakTrigger + 1);
                    $this->htmlvspace = ($this->PageBreakTrigger + 1);
                }
            }
            if ($dom[$key]['tag'] AND $dom[$key]['opening'] AND isset($dom[$key]['attribute']['nobr']) AND ($dom[$key]['attribute']['nobr'] == 'true')) {
                if (isset($dom[($dom[$key]['parent'])]['attribute']['nobr']) AND ($dom[($dom[$key]['parent'])]['attribute']['nobr'] == 'true')) {
                    $dom[$key]['attribute']['nobr'] = false;
                } else {
                    // store current object
                    $this->startTransaction();
                    // save this method vars
                    $this_method_vars['html'] = $html;
                    $this_method_vars['ln'] = $ln;
                    $this_method_vars['fill'] = $fill;
                    $this_method_vars['reseth'] = $reseth;
                    $this_method_vars['cell'] = $cell;
                    $this_method_vars['align'] = $align;
                    $this_method_vars['gvars'] = $gvars;
                    $this_method_vars['prevPage'] = $prevPage;
                    $this_method_vars['prev_cell_margin'] = $prev_cell_margin;
                    $this_method_vars['prev_cell_padding'] = $prev_cell_padding;
                    $this_method_vars['prevlMargin'] = $prevlMargin;
                    $this_method_vars['prevrMargin'] = $prevrMargin;
                    $this_method_vars['curfontname'] = $curfontname;
                    $this_method_vars['curfontstyle'] = $curfontstyle;
                    $this_method_vars['curfontsize'] = $curfontsize;
                    $this_method_vars['curfontascent'] = $curfontascent;
                    $this_method_vars['curfontdescent'] = $curfontdescent;
                    $this_method_vars['curfontstretcing'] = $curfontstretcing;
                    $this_method_vars['curfonttracking'] = $curfonttracking;
                    $this_method_vars['minstartliney'] = $minstartliney;
                    $this_method_vars['maxbottomliney'] = $maxbottomliney;
                    $this_method_vars['yshift'] = $yshift;
                    $this_method_vars['startlinepage'] = $startlinepage;
                    $this_method_vars['startlinepos'] = $startlinepos;
                    $this_method_vars['startlinex'] = $startlinex;
                    $this_method_vars['startliney'] = $startliney;
                    $this_method_vars['newline'] = $newline;
                    $this_method_vars['loop'] = $loop;
                    $this_method_vars['curpos'] = $curpos;
                    $this_method_vars['pask'] = $pask;
                    $this_method_vars['lalign'] = $lalign;
                    $this_method_vars['plalign'] = $plalign;
                    $this_method_vars['w'] = $w;
                    $this_method_vars['prev_cell_height_ratio'] = $prev_cell_height_ratio;
                    $this_method_vars['prev_listnum'] = $prev_listnum;
                    $this_method_vars['prev_listordered'] = $prev_listordered;
                    $this_method_vars['prev_listcount'] = $prev_listcount;
                    $this_method_vars['prev_lispacer'] = $prev_lispacer;
                    $this_method_vars['fontaligned'] = $fontaligned;
                    $this_method_vars['key'] = $key;
                    $this_method_vars['dom'] = $dom;
                }
            }
            // print THEAD block
            if (($dom[$key]['value'] == 'tr') AND isset($dom[$key]['thead']) AND $dom[$key]['thead']) {
                if (isset($dom[$key]['parent']) AND isset($dom[$dom[$key]['parent']]['thead']) AND !TCPDF_STATIC::empty_string($dom[$dom[$key]['parent']]['thead'])) {
                    $this->inthead = true;
                    // print table header (thead)
                    $this->writeHTML($this->thead, false, false, false, false, '');
                    // check if we are on a new page or on a new column
                    if (($this->y < $this->start_transaction_y) OR ($this->checkPageBreak($this->lasth, '', false))) {
                        // we are on a new page or on a new column and the total object height is less than the available vertical space.
                        // restore previous object
                        $this->rollbackTransaction(true);
                        // restore previous values
                        foreach ($this_method_vars as $vkey => $vval) {
                            $$vkey = $vval;
                        }
                        // disable table header
                        $tmp_thead = $this->thead;
                        $this->thead = '';
                        // add a page (or trig AcceptPageBreak() for multicolumn mode)
                        $pre_y = $this->y;
                        if ((!$this->checkPageBreak($this->PageBreakTrigger + 1)) AND ($this->y < $pre_y)) {
                            // fix for multicolumn mode
                            $startliney = $this->y;
                        }
                        $this->start_transaction_page = $this->page;
                        $this->start_transaction_y = $this->y;
                        // restore table header
                        $this->thead = $tmp_thead;
                        // fix table border properties
                        if (isset($dom[$dom[$key]['parent']]['attribute']['cellspacing'])) {
                            $tmp_cellspacing = $this->getHTMLUnitToUnits($dom[$dom[$key]['parent']]['attribute']['cellspacing'], 1, 'px');
                        } elseif (isset($dom[$dom[$key]['parent']]['border-spacing'])) {
                            $tmp_cellspacing = $dom[$dom[$key]['parent']]['border-spacing']['V'];
                        } else {
                            $tmp_cellspacing = 0;
                        }
                        $dom[$dom[$key]['parent']]['borderposition']['page'] = $this->page;
                        $dom[$dom[$key]['parent']]['borderposition']['column'] = $this->current_column;
                        $dom[$dom[$key]['parent']]['borderposition']['y'] = $this->y + $tmp_cellspacing;
                        $xoffset = ($this->x - $dom[$dom[$key]['parent']]['borderposition']['x']);
                        $dom[$dom[$key]['parent']]['borderposition']['x'] += $xoffset;
                        $dom[$dom[$key]['parent']]['borderposition']['xmax'] += $xoffset;
                        // print table header (thead)
                        $this->writeHTML($this->thead, false, false, false, false, '');
                    }
                }
                // move $key index forward to skip THEAD block
                while ( ($key < $maxel) AND (!(
                    ($dom[$key]['tag'] AND $dom[$key]['opening'] AND ($dom[$key]['value'] == 'tr') AND (!isset($dom[$key]['thead']) OR !$dom[$key]['thead']))
                    OR ($dom[$key]['tag'] AND (!$dom[$key]['opening']) AND ($dom[$key]['value'] == 'table'))) )) {
                    ++$key;
                }
            }
            if ($dom[$key]['tag'] OR ($key == 0)) {
                if ((($dom[$key]['value'] == 'table') OR ($dom[$key]['value'] == 'tr')) AND (isset($dom[$key]['align']))) {
                    $dom[$key]['align'] = ($this->rtl) ? 'R' : 'L';
                }
                // vertically align image in line
                if ((!$this->newline) AND ($dom[$key]['value'] == 'img') AND (isset($dom[$key]['height'])) AND ($dom[$key]['height'] > 0)) {
                    // get image height
                    $imgh = $this->getHTMLUnitToUnits($dom[$key]['height'], ($dom[$key]['fontsize'] / $this->k), 'px');
                    $autolinebreak = false;
                    if (!empty($dom[$key]['width'])) {
                        $imgw = $this->getHTMLUnitToUnits($dom[$key]['width'], ($dom[$key]['fontsize'] / $this->k), 'px', false);
                        if (($imgw <= ($this->w - $this->lMargin - $this->rMargin - $this->cell_padding['L'] - $this->cell_padding['R']))
                            AND ((($this->rtl) AND (($this->x - $imgw) < ($this->lMargin + $this->cell_padding['L'])))
                            OR ((!$this->rtl) AND (($this->x + $imgw) > ($this->w - $this->rMargin - $this->cell_padding['R']))))) {
                            // add automatic line break
                            $autolinebreak = true;
                            $this->Ln('', $cell);
                            if ((!$dom[($key-1)]['tag']) AND ($dom[($key-1)]['value'] == ' ')) {
                                // go back to evaluate this line break
                                --$key;
                            }
                        }
                    }
                    if (!$autolinebreak) {
                        if ($this->inPageBody()) {
                            $pre_y = $this->y;
                            // check for page break
                            if ((!$this->checkPageBreak($imgh)) AND ($this->y < $pre_y)) {
                                // fix for multicolumn mode
                                $startliney = $this->y;
                            }
                        }
                        if ($this->page > $startlinepage) {
                            // fix line splitted over two pages
                            if (isset($this->footerlen[$startlinepage])) {
                                $curpos = $this->pagelen[$startlinepage] - $this->footerlen[$startlinepage];
                            }
                            // line to be moved one page forward
                            $pagebuff = $this->getPageBuffer($startlinepage);
                            $linebeg = substr($pagebuff, $startlinepos, ($curpos - $startlinepos));
                            $tstart = substr($pagebuff, 0, $startlinepos);
                            $tend = substr($this->getPageBuffer($startlinepage), $curpos);
                            // remove line from previous page
                            $this->setPageBuffer($startlinepage, $tstart.''.$tend);
                            $pagebuff = $this->getPageBuffer($this->page);
                            $tstart = substr($pagebuff, 0, $this->cntmrk[$this->page]);
                            $tend = substr($pagebuff, $this->cntmrk[$this->page]);
                            // add line start to current page
                            $yshift = ($minstartliney - $this->y);
                            if ($fontaligned) {
                                $yshift += ($curfontsize / $this->k);
                            }
                            $try = sprintf('1 0 0 1 0 %F cm', ($yshift * $this->k));
                            $this->setPageBuffer($this->page, $tstart."\nq\n".$try."\n".$linebeg."\nQ\n".$tend);
                            // shift the annotations and links
                            if (isset($this->PageAnnots[$this->page])) {
                                $next_pask = count($this->PageAnnots[$this->page]);
                            } else {
                                $next_pask = 0;
                            }
                            if (isset($this->PageAnnots[$startlinepage])) {
                                foreach ($this->PageAnnots[$startlinepage] as $pak => $pac) {
                                    if ($pak >= $pask) {
                                        $this->PageAnnots[$this->page][] = $pac;
                                        unset($this->PageAnnots[$startlinepage][$pak]);
                                        $npak = count($this->PageAnnots[$this->page]) - 1;
                                        $this->PageAnnots[$this->page][$npak]['y'] -= $yshift;
                                    }
                                }
                            }
                            $pask = $next_pask;
                            $startlinepos = $this->cntmrk[$this->page];
                            $startlinepage = $this->page;
                            $startliney = $this->y;
                            $this->newline = false;
                        }
                        $this->y += ($this->getCellHeight($curfontsize / $this->k) - ($curfontdescent * $this->cell_height_ratio) - $imgh);
                        $minstartliney = min($this->y, $minstartliney);
                        $maxbottomliney = ($startliney + $this->getCellHeight($curfontsize / $this->k));
                    }
                } elseif (isset($dom[$key]['fontname']) OR isset($dom[$key]['fontstyle']) OR isset($dom[$key]['fontsize']) OR isset($dom[$key]['line-height'])) {
                    // account for different font size
                    $pfontname = $curfontname;
                    $pfontstyle = $curfontstyle;
                    $pfontsize = $curfontsize;
                    $fontname = (isset($dom[$key]['fontname']) ? $dom[$key]['fontname'] : $curfontname);
                    $fontstyle = (isset($dom[$key]['fontstyle']) ? $dom[$key]['fontstyle'] : $curfontstyle);
                    $fontsize = (isset($dom[$key]['fontsize']) ? $dom[$key]['fontsize'] : $curfontsize);
                    $fontascent = $this->getFontAscent($fontname, $fontstyle, $fontsize);
                    $fontdescent = $this->getFontDescent($fontname, $fontstyle, $fontsize);
                    if (($fontname != $curfontname) OR ($fontstyle != $curfontstyle) OR ($fontsize != $curfontsize)
                        OR ($this->cell_height_ratio != $dom[$key]['line-height'])
                        OR ($dom[$key]['tag'] AND $dom[$key]['opening'] AND ($dom[$key]['value'] == 'li')) ) {
                        if (($key < ($maxel - 1)) AND (
                                ($dom[$key]['tag'] AND $dom[$key]['opening'] AND ($dom[$key]['value'] == 'li'))
                                OR ($this->cell_height_ratio != $dom[$key]['line-height'])
                                OR (!$this->newline AND is_numeric($fontsize) AND is_numeric($curfontsize)
                                AND ($fontsize >= 0) AND ($curfontsize >= 0)
                                AND (($fontsize != $curfontsize) OR ($fontstyle != $curfontstyle) OR ($fontname != $curfontname)))
                            )) {
                            if ($this->page > $startlinepage) {
                                // fix lines splitted over two pages
                                if (isset($this->footerlen[$startlinepage])) {
                                    $curpos = $this->pagelen[$startlinepage] - $this->footerlen[$startlinepage];
                                }
                                // line to be moved one page forward
                                $pagebuff = $this->getPageBuffer($startlinepage);
                                $linebeg = substr($pagebuff, $startlinepos, ($curpos - $startlinepos));
                                $tstart = substr($pagebuff, 0, $startlinepos);
                                $tend = substr($this->getPageBuffer($startlinepage), $curpos);
                                // remove line start from previous page
                                $this->setPageBuffer($startlinepage, $tstart.''.$tend);
                                $pagebuff = $this->getPageBuffer($this->page);
                                $tstart = substr($pagebuff, 0, $this->cntmrk[$this->page]);
                                $tend = substr($pagebuff, $this->cntmrk[$this->page]);
                                // add line start to current page
                                $yshift = ($minstartliney - $this->y);
                                $try = sprintf('1 0 0 1 0 %F cm', ($yshift * $this->k));
                                $this->setPageBuffer($this->page, $tstart."\nq\n".$try."\n".$linebeg."\nQ\n".$tend);
                                // shift the annotations and links
                                if (isset($this->PageAnnots[$this->page])) {
                                    $next_pask = count($this->PageAnnots[$this->page]);
                                } else {
                                    $next_pask = 0;
                                }
                                if (isset($this->PageAnnots[$startlinepage])) {
                                    foreach ($this->PageAnnots[$startlinepage] as $pak => $pac) {
                                        if ($pak >= $pask) {
                                            $this->PageAnnots[$this->page][] = $pac;
                                            unset($this->PageAnnots[$startlinepage][$pak]);
                                            $npak = count($this->PageAnnots[$this->page]) - 1;
                                            $this->PageAnnots[$this->page][$npak]['y'] -= $yshift;
                                        }
                                    }
                                }
                                $pask = $next_pask;
                                $startlinepos = $this->cntmrk[$this->page];
                                $startlinepage = $this->page;
                                $startliney = $this->y;
                            }
                            if (!isset($dom[$key]['line-height'])) {
                                $dom[$key]['line-height'] = $this->cell_height_ratio;
                            }
                            if (!$dom[$key]['block']) {
                                if (!(isset($dom[($key + 1)]) AND $dom[($key + 1)]['tag'] AND (!$dom[($key + 1)]['opening']) AND ($dom[($key + 1)]['value'] != 'li') AND $dom[$key]['tag'] AND (!$dom[$key]['opening']))) {
                                    $this->y += (((($curfontsize * $this->cell_height_ratio) - ($fontsize * $dom[$key]['line-height'])) / $this->k) + $curfontascent - $fontascent - $curfontdescent + $fontdescent) / 2;
                                }
                                if (($dom[$key]['value'] != 'sup') AND ($dom[$key]['value'] != 'sub')) {
                                    $current_line_align_data = array($key, $minstartliney, $maxbottomliney);
                                    if (isset($line_align_data) AND (($line_align_data[0] == ($key - 1)) OR (($line_align_data[0] == ($key - 2)) AND (isset($dom[($key - 1)])) AND (preg_match('/^([\s]+)$/', $dom[($key - 1)]['value']) > 0)))) {
                                        $minstartliney = min($this->y, $line_align_data[1]);
                                        $maxbottomliney = max(($this->y + $this->getCellHeight($fontsize / $this->k)), $line_align_data[2]);
                                    } else {
                                        $minstartliney = min($this->y, $minstartliney);
                                        $maxbottomliney = max(($this->y + $this->getCellHeight($fontsize / $this->k)), $maxbottomliney);
                                    }
                                    $line_align_data = $current_line_align_data;
                                }
                            }
                            $this->cell_height_ratio = $dom[$key]['line-height'];
                            $fontaligned = true;
                        }
                        $this->SetFont($fontname, $fontstyle, $fontsize);
                        // reset row height
                        $this->resetLastH();
                        $curfontname = $fontname;
                        $curfontstyle = $fontstyle;
                        $curfontsize = $fontsize;
                        $curfontascent = $fontascent;
                        $curfontdescent = $fontdescent;
                    }
                }
                // set text rendering mode
                $textstroke = isset($dom[$key]['stroke']) ? $dom[$key]['stroke'] : $this->textstrokewidth;
                $textfill = isset($dom[$key]['fill']) ? $dom[$key]['fill'] : (($this->textrendermode % 2) == 0);
                $textclip = isset($dom[$key]['clip']) ? $dom[$key]['clip'] : ($this->textrendermode > 3);
                $this->setTextRenderingMode($textstroke, $textfill, $textclip);
                if (isset($dom[$key]['font-stretch']) AND ($dom[$key]['font-stretch'] !== false)) {
                    $this->setFontStretching($dom[$key]['font-stretch']);
                }
                if (isset($dom[$key]['letter-spacing']) AND ($dom[$key]['letter-spacing'] !== false)) {
                    $this->setFontSpacing($dom[$key]['letter-spacing']);
                }
                if (($plalign == 'J') AND $dom[$key]['block']) {
                    $plalign = '';
                }
                // get current position on page buffer
                $curpos = $this->pagelen[$startlinepage];
                if (isset($dom[$key]['bgcolor']) AND ($dom[$key]['bgcolor'] !== false)) {
                    $this->SetFillColorArray($dom[$key]['bgcolor']);
                    $wfill = true;
                } else {
                    $wfill = $fill | false;
                }
                if (isset($dom[$key]['fgcolor']) AND ($dom[$key]['fgcolor'] !== false)) {
                    $this->SetTextColorArray($dom[$key]['fgcolor']);
                }
                if (isset($dom[$key]['strokecolor']) AND ($dom[$key]['strokecolor'] !== false)) {
                    $this->SetDrawColorArray($dom[$key]['strokecolor']);
                }
                if (isset($dom[$key]['align'])) {
                    $lalign = $dom[$key]['align'];
                }
                if (TCPDF_STATIC::empty_string($lalign)) {
                    $lalign = $align;
                }
            }
            // align lines
            if ($this->newline AND (strlen($dom[$key]['value']) > 0) AND ($dom[$key]['value'] != 'td') AND ($dom[$key]['value'] != 'th')) {
                $newline = true;
                $fontaligned = false;
                // we are at the beginning of a new line
                if (isset($startlinex)) {
                    $yshift = ($minstartliney - $startliney);
                    if (($yshift > 0) OR ($this->page > $startlinepage)) {
                        $yshift = 0;
                    }
                    $t_x = 0;
                    // the last line must be shifted to be aligned as requested
                    $linew = abs($this->endlinex - $startlinex);
                    if ($this->inxobj) {
                        // we are inside an XObject template
                        $pstart = substr($this->xobjects[$this->xobjid]['outdata'], 0, $startlinepos);
                        if (isset($opentagpos)) {
                            $midpos = $opentagpos;
                        } else {
                            $midpos = 0;
                        }
                        if ($midpos > 0) {
                            $pmid = substr($this->xobjects[$this->xobjid]['outdata'], $startlinepos, ($midpos - $startlinepos));
                            $pend = substr($this->xobjects[$this->xobjid]['outdata'], $midpos);
                        } else {
                            $pmid = substr($this->xobjects[$this->xobjid]['outdata'], $startlinepos);
                            $pend = '';
                        }
                    } else {
                        $pstart = substr($this->getPageBuffer($startlinepage), 0, $startlinepos);
                        if (isset($opentagpos) AND isset($this->footerlen[$startlinepage]) AND (!$this->InFooter)) {
                            $this->footerpos[$startlinepage] = $this->pagelen[$startlinepage] - $this->footerlen[$startlinepage];
                            $midpos = min($opentagpos, $this->footerpos[$startlinepage]);
                        } elseif (isset($opentagpos)) {
                            $midpos = $opentagpos;
                        } elseif (isset($this->footerlen[$startlinepage]) AND (!$this->InFooter)) {
                            $this->footerpos[$startlinepage] = $this->pagelen[$startlinepage] - $this->footerlen[$startlinepage];
                            $midpos = $this->footerpos[$startlinepage];
                        } else {
                            $midpos = 0;
                        }
                        if ($midpos > 0) {
                            $pmid = substr($this->getPageBuffer($startlinepage), $startlinepos, ($midpos - $startlinepos));
                            $pend = substr($this->getPageBuffer($startlinepage), $midpos);
                        } else {
                            $pmid = substr($this->getPageBuffer($startlinepage), $startlinepos);
                            $pend = '';
                        }
                    }
                    if ((isset($plalign) AND ((($plalign == 'C') OR ($plalign == 'J') OR (($plalign == 'R') AND (!$this->rtl)) OR (($plalign == 'L') AND ($this->rtl)))))) {
                        // calculate shifting amount
                        $tw = $w;
                        if (($plalign == 'J') AND $this->isRTLTextDir() AND ($this->num_columns > 1)) {
                            $tw += $this->cell_padding['R'];
                        }
                        if ($this->lMargin != $prevlMargin) {
                            $tw += ($prevlMargin - $this->lMargin);
                        }
                        if ($this->rMargin != $prevrMargin) {
                            $tw += ($prevrMargin - $this->rMargin);
                        }
                        $one_space_width = $this->GetStringWidth(chr(32));
                        $no = 0; // number of spaces on a line contained on a single block
                        if ($this->isRTLTextDir()) { // RTL
                            // remove left space if exist
                            $pos1 = TCPDF_STATIC::revstrpos($pmid, '[(');
                            if ($pos1 > 0) {
                                $pos1 = intval($pos1);
                                if ($this->isUnicodeFont()) {
                                    $pos2 = intval(TCPDF_STATIC::revstrpos($pmid, '[('.chr(0).chr(32)));
                                    $spacelen = 2;
                                } else {
                                    $pos2 = intval(TCPDF_STATIC::revstrpos($pmid, '[('.chr(32)));
                                    $spacelen = 1;
                                }
                                if ($pos1 == $pos2) {
                                    $pmid = substr($pmid, 0, ($pos1 + 2)).substr($pmid, ($pos1 + 2 + $spacelen));
                                    if (substr($pmid, $pos1, 4) == '[()]') {
                                        $linew -= $one_space_width;
                                    } elseif ($pos1 == strpos($pmid, '[(')) {
                                        $no = 1;
                                    }
                                }
                            }
                        } else { // LTR
                            // remove right space if exist
                            $pos1 = TCPDF_STATIC::revstrpos($pmid, ')]');
                            if ($pos1 > 0) {
                                $pos1 = intval($pos1);
                                if ($this->isUnicodeFont()) {
                                    $pos2 = intval(TCPDF_STATIC::revstrpos($pmid, chr(0).chr(32).')]')) + 2;
                                    $spacelen = 2;
                                } else {
                                    $pos2 = intval(TCPDF_STATIC::revstrpos($pmid, chr(32).')]')) + 1;
                                    $spacelen = 1;
                                }
                                if ($pos1 == $pos2) {
                                    $pmid = substr($pmid, 0, ($pos1 - $spacelen)).substr($pmid, $pos1);
                                    $linew -= $one_space_width;
                                }
                            }
                        }
                        $mdiff = ($tw - $linew);
                        if ($plalign == 'C') {
                            if ($this->rtl) {
                                $t_x = -($mdiff / 2);
                            } else {
                                $t_x = ($mdiff / 2);
                            }
                        } elseif ($plalign == 'R') {
                            // right alignment on LTR document
                            $t_x = $mdiff;
                        } elseif ($plalign == 'L') {
                            // left alignment on RTL document
                            $t_x = -$mdiff;
                        } elseif (($plalign == 'J') AND ($plalign == $lalign)) {
                            // Justification
                            if ($this->isRTLTextDir()) {
                                // align text on the left
                                $t_x = -$mdiff;
                            }
                            $ns = 0; // number of spaces
                            $pmidtemp = $pmid;
                            // escape special characters
                            $pmidtemp = preg_replace('/[\\\][\(]/x', '\\#!#OP#!#', $pmidtemp);
                            $pmidtemp = preg_replace('/[\\\][\)]/x', '\\#!#CP#!#', $pmidtemp);
                            // search spaces
                            if (preg_match_all('/\[\(([^\)]*)\)\]/x', $pmidtemp, $lnstring, PREG_PATTERN_ORDER)) {
                                $spacestr = $this->getSpaceString();
                                $maxkk = count($lnstring[1]) - 1;
                                for ($kk=0; $kk <= $maxkk; ++$kk) {
                                    // restore special characters
                                    $lnstring[1][$kk] = str_replace('#!#OP#!#', '(', $lnstring[1][$kk]);
                                    $lnstring[1][$kk] = str_replace('#!#CP#!#', ')', $lnstring[1][$kk]);
                                    // store number of spaces on the strings
                                    $lnstring[2][$kk] = substr_count($lnstring[1][$kk], $spacestr);
                                    // count total spaces on line
                                    $ns += $lnstring[2][$kk];
                                    $lnstring[3][$kk] = $ns;
                                }
                                if ($ns == 0) {
                                    $ns = 1;
                                }
                                // calculate additional space to add to each existing space
                                $spacewidth = ($mdiff / ($ns - $no)) * $this->k;
                                if ($this->FontSize <= 0) {
                                    $this->FontSize = 1;
                                }
                                $spacewidthu = -1000 * ($mdiff + (($ns + $no) * $one_space_width)) / $ns / $this->FontSize;
                                if ($this->font_spacing != 0) {
                                    // fixed spacing mode
                                    $osw = -1000 * $this->font_spacing / $this->FontSize;
                                    $spacewidthu += $osw;
                                }
                                $nsmax = $ns;
                                $ns = 0;
                                reset($lnstring);
                                $offset = 0;
                                $strcount = 0;
                                $prev_epsposbeg = 0;
                                $textpos = 0;
                                if ($this->isRTLTextDir()) {
                                    $textpos = $this->wPt;
                                }
                                while (preg_match('/([0-9\.\+\-]*)[\s](Td|cm|m|l|c|re)[\s]/x', $pmid, $strpiece, PREG_OFFSET_CAPTURE, $offset) == 1) {
                                    // check if we are inside a string section '[( ... )]'
                                    $stroffset = strpos($pmid, '[(', $offset);
                                    if (($stroffset !== false) AND ($stroffset <= $strpiece[2][1])) {
                                        // set offset to the end of string section
                                        $offset = strpos($pmid, ')]', $stroffset);
                                        while (($offset !== false) AND ($pmid[($offset - 1)] == '\\')) {
                                            $offset = strpos($pmid, ')]', ($offset + 1));
                                        }
                                        if ($offset === false) {
                                            $this->Error('HTML Justification: malformed PDF code.');
                                        }
                                        continue;
                                    }
                                    if ($this->isRTLTextDir()) {
                                        $spacew = ($spacewidth * ($nsmax - $ns));
                                    } else {
                                        $spacew = ($spacewidth * $ns);
                                    }
                                    $offset = $strpiece[2][1] + strlen($strpiece[2][0]);
                                    $epsposend = strpos($pmid, $this->epsmarker.'Q', $offset);              
                                    if ($epsposend !== null) {
                                        $epsposend += strlen($this->epsmarker.'Q');
                                        $epsposbeg = strpos($pmid, 'q'.$this->epsmarker, $offset);
                                        if ($epsposbeg === null) {
                                            $epsposbeg = strpos($pmid, 'q'.$this->epsmarker, ($prev_epsposbeg - 6));
                                            $prev_epsposbeg = $epsposbeg;
                                        }
                                        if (($epsposbeg > 0) AND ($epsposend > 0) AND ($offset > $epsposbeg) AND ($offset < $epsposend)) {
                                            // shift EPS images
                                            $trx = sprintf('1 0 0 1 %F 0 cm', $spacew);
                                            $pmid_b = substr($pmid, 0, $epsposbeg);
                                            $pmid_m = substr($pmid, $epsposbeg, ($epsposend - $epsposbeg));
                                            $pmid_e = substr($pmid, $epsposend);
                                            $pmid = $pmid_b."\nq\n".$trx."\n".$pmid_m."\nQ\n".$pmid_e;
                                            $offset = $epsposend;
                                            continue;
                                        }
                                    }
                                    $currentxpos = 0;
                                    // shift blocks of code
                                    switch ($strpiece[2][0]) {
                                        case 'Td':
                                        case 'cm':
                                        case 'm':
                                        case 'l': {
                                            // get current X position
                                            preg_match('/([0-9\.\+\-]*)[\s]('.$strpiece[1][0].')[\s]('.$strpiece[2][0].')([\s]*)/x', $pmid, $xmatches);
                                            if (!isset($xmatches[1])) {
                                                break;
                                            }
                                            $currentxpos = $xmatches[1];
                                            $textpos = $currentxpos;
                                            if (($strcount <= $maxkk) AND ($strpiece[2][0] == 'Td')) {
                                                $ns = $lnstring[3][$strcount];
                                                if ($this->isRTLTextDir()) {
                                                    $spacew = ($spacewidth * ($nsmax - $ns));
                                                }
                                                ++$strcount;
                                            }
                                            // justify block
                                            if (preg_match('/([0-9\.\+\-]*)[\s]('.$strpiece[1][0].')[\s]('.$strpiece[2][0].')([\s]*)/x', $pmid, $pmatch) == 1) {
                                                $newpmid = sprintf('%F',(floatval($pmatch[1]) + $spacew)).' '.$pmatch[2].' x*#!#*x'.$pmatch[3].$pmatch[4];
                                                $pmid = str_replace($pmatch[0], $newpmid, $pmid);
                                                unset($pmatch, $newpmid);
                                            }
                                            break;
                                        }
                                        case 're': {
                                            // justify block
                                            if (!TCPDF_STATIC::empty_string($this->lispacer)) {
                                                $this->lispacer = '';
                                                continue;
                                            }
                                            preg_match('/([0-9\.\+\-]*)[\s]([0-9\.\+\-]*)[\s]([0-9\.\+\-]*)[\s]('.$strpiece[1][0].')[\s](re)([\s]*)/x', $pmid, $xmatches);
                                            if (!isset($xmatches[1])) {
                                                break;
                                            }
                                            $currentxpos = $xmatches[1];
                                            $x_diff = 0;
                                            $w_diff = 0;
                                            if ($this->isRTLTextDir()) { // RTL
                                                if ($currentxpos < $textpos) {
                                                    $x_diff = ($spacewidth * ($nsmax - $lnstring[3][$strcount]));
                                                    $w_diff = ($spacewidth * $lnstring[2][$strcount]);
                                                } else {
                                                    if ($strcount > 0) {
                                                        $x_diff = ($spacewidth * ($nsmax - $lnstring[3][($strcount - 1)]));
                                                        $w_diff = ($spacewidth * $lnstring[2][($strcount - 1)]);
                                                    }
                                                }
                                            } else { // LTR
                                                if ($currentxpos > $textpos) {
                                                    if ($strcount > 0) {
                                                        $x_diff = ($spacewidth * $lnstring[3][($strcount - 1)]);
                                                    }
                                                    $w_diff = ($spacewidth * $lnstring[2][$strcount]);
                                                } else {
                                                    if ($strcount > 1) {
                                                        $x_diff = ($spacewidth * $lnstring[3][($strcount - 2)]);
                                                    }
                                                    if ($strcount > 0) {
                                                        $w_diff = ($spacewidth * $lnstring[2][($strcount - 1)]);
                                                    }
                                                }
                                            }
                                            if (preg_match('/('.$xmatches[1].')[\s]('.$xmatches[2].')[\s]('.$xmatches[3].')[\s]('.$strpiece[1][0].')[\s](re)([\s]*)/x', $pmid, $pmatch) == 1) {
                                                $newx = sprintf('%F',(floatval($pmatch[1]) + $x_diff));
                                                $neww = sprintf('%F',(floatval($pmatch[3]) + $w_diff));
                                                $newpmid = $newx.' '.$pmatch[2].' '.$neww.' '.$pmatch[4].' x*#!#*x'.$pmatch[5].$pmatch[6];
                                                $pmid = str_replace($pmatch[0], $newpmid, $pmid);
                                                unset($pmatch, $newpmid, $newx, $neww);
                                            }
                                            break;
                                        }
                                        case 'c': {
                                            // get current X position
                                            preg_match('/([0-9\.\+\-]*)[\s]([0-9\.\+\-]*)[\s]([0-9\.\+\-]*)[\s]([0-9\.\+\-]*)[\s]([0-9\.\+\-]*)[\s]('.$strpiece[1][0].')[\s](c)([\s]*)/x', $pmid, $xmatches);
                                            if (!isset($xmatches[1])) {
                                                break;
                                            }
                                            $currentxpos = $xmatches[1];
                                            // justify block
                                            if (preg_match('/('.$xmatches[1].')[\s]('.$xmatches[2].')[\s]('.$xmatches[3].')[\s]('.$xmatches[4].')[\s]('.$xmatches[5].')[\s]('.$strpiece[1][0].')[\s](c)([\s]*)/x', $pmid, $pmatch) == 1) {
                                                $newx1 = sprintf('%F',(floatval($pmatch[1]) + $spacew));
                                                $newx2 = sprintf('%F',(floatval($pmatch[3]) + $spacew));
                                                $newx3 = sprintf('%F',(floatval($pmatch[5]) + $spacew));
                                                $newpmid = $newx1.' '.$pmatch[2].' '.$newx2.' '.$pmatch[4].' '.$newx3.' '.$pmatch[6].' x*#!#*x'.$pmatch[7].$pmatch[8];
                                                $pmid = str_replace($pmatch[0], $newpmid, $pmid);
                                                unset($pmatch, $newpmid, $newx1, $newx2, $newx3);
                                            }
                                            break;
                                        }
                                    }
                                    // shift the annotations and links
                                    $cxpos = ($currentxpos / $this->k);
                                    $lmpos = ($this->lMargin + $this->cell_padding['L'] + $this->feps);
                                    if ($this->inxobj) {
                                        // we are inside an XObject template
                                        foreach ($this->xobjects[$this->xobjid]['annotations'] as $pak => $pac) {
                                            if (($pac['y'] >= $minstartliney) AND (($pac['x'] * $this->k) >= ($currentxpos - $this->feps)) AND (($pac['x'] * $this->k) <= ($currentxpos + $this->feps))) {
                                                if ($cxpos > $lmpos) {
                                                    $this->xobjects[$this->xobjid]['annotations'][$pak]['x'] += ($spacew / $this->k);
                                                    $this->xobjects[$this->xobjid]['annotations'][$pak]['w'] += (($spacewidth * $pac['numspaces']) / $this->k);
                                                } else {
                                                    $this->xobjects[$this->xobjid]['annotations'][$pak]['w'] += (($spacewidth * $pac['numspaces']) / $this->k);
                                                }
                                                break;
                                            }
                                        }
                                    } elseif (isset($this->PageAnnots[$this->page])) {
                                        foreach ($this->PageAnnots[$this->page] as $pak => $pac) {
                                            if (($pac['y'] >= $minstartliney) AND (($pac['x'] * $this->k) >= ($currentxpos - $this->feps)) AND (($pac['x'] * $this->k) <= ($currentxpos + $this->feps))) {
                                                if ($cxpos > $lmpos) {
                                                    $this->PageAnnots[$this->page][$pak]['x'] += ($spacew / $this->k);
                                                    $this->PageAnnots[$this->page][$pak]['w'] += (($spacewidth * $pac['numspaces']) / $this->k);
                                                } else {
                                                    $this->PageAnnots[$this->page][$pak]['w'] += (($spacewidth * $pac['numspaces']) / $this->k);
                                                }
                                                break;
                                            }
                                        }
                                    }
                                } // end of while
                                // remove markers
                                $pmid = str_replace('x*#!#*x', '', $pmid);
                                if ($this->isUnicodeFont()) {
                                    // multibyte characters
                                    $spacew = $spacewidthu;
                                    if ($this->font_stretching != 100) {
                                        // word spacing is affected by stretching
                                        $spacew /= ($this->font_stretching / 100);
                                    }
                                    // escape special characters
                                    $pos = 0;
                                    $pmid = preg_replace('/[\\\][\(]/x', '\\#!#OP#!#', $pmid);
                                    $pmid = preg_replace('/[\\\][\)]/x', '\\#!#CP#!#', $pmid);
                                    if (preg_match_all('/\[\(([^\)]*)\)\]/x', $pmid, $pamatch) > 0) {
                                        foreach($pamatch[0] as $pk => $pmatch) {
                                            $replace = $pamatch[1][$pk];
                                            $replace = str_replace('#!#OP#!#', '(', $replace);
                                            $replace = str_replace('#!#CP#!#', ')', $replace);
                                            $newpmid = '[('.str_replace(chr(0).chr(32), ') '.sprintf('%F', $spacew).' (', $replace).')]';
                                            $pos = strpos($pmid, $pmatch, $pos);
                                            if ($pos !== FALSE) {
                                                $pmid = substr_replace($pmid, $newpmid, $pos, strlen($pmatch));
                                            }
                                            ++$pos;
                                        }
                                        unset($pamatch);
                                    }
                                    if ($this->inxobj) {
                                        // we are inside an XObject template
                                        $this->xobjects[$this->xobjid]['outdata'] = $pstart."\n".$pmid."\n".$pend;
                                    } else {
                                        $this->setPageBuffer($startlinepage, $pstart."\n".$pmid."\n".$pend);
                                    }
                                    $endlinepos = strlen($pstart."\n".$pmid."\n");
                                } else {
                                    // non-unicode (single-byte characters)
                                    if ($this->font_stretching != 100) {
                                        // word spacing (Tw) is affected by stretching
                                        $spacewidth /= ($this->font_stretching / 100);
                                    }
                                    $rs = sprintf('%F Tw', $spacewidth);
                                    $pmid = preg_replace("/\[\(/x", $rs.' [(', $pmid);
                                    if ($this->inxobj) {
                                        // we are inside an XObject template
                                        $this->xobjects[$this->xobjid]['outdata'] = $pstart."\n".$pmid."\nBT 0 Tw ET\n".$pend;
                                    } else {
                                        $this->setPageBuffer($startlinepage, $pstart."\n".$pmid."\nBT 0 Tw ET\n".$pend);
                                    }
                                    $endlinepos = strlen($pstart."\n".$pmid."\nBT 0 Tw ET\n");
                                }
                            }
                        } // end of J
                    } // end if $startlinex
                    if (($t_x != 0) OR ($yshift < 0)) {
                        // shift the line
                        $trx = sprintf('1 0 0 1 %F %F cm', ($t_x * $this->k), ($yshift * $this->k));
                        $pstart .= "\nq\n".$trx."\n".$pmid."\nQ\n";
                        $endlinepos = strlen($pstart);
                        if ($this->inxobj) {
                            // we are inside an XObject template
                            $this->xobjects[$this->xobjid]['outdata'] = $pstart.$pend;
                            foreach ($this->xobjects[$this->xobjid]['annotations'] as $pak => $pac) {
                                if ($pak >= $pask) {
                                    $this->xobjects[$this->xobjid]['annotations'][$pak]['x'] += $t_x;
                                    $this->xobjects[$this->xobjid]['annotations'][$pak]['y'] -= $yshift;
                                }
                            }
                        } else {
                            $this->setPageBuffer($startlinepage, $pstart.$pend);
                            // shift the annotations and links
                            if (isset($this->PageAnnots[$this->page])) {
                                foreach ($this->PageAnnots[$this->page] as $pak => $pac) {
                                    if ($pak >= $pask) {
                                        $this->PageAnnots[$this->page][$pak]['x'] += $t_x;
                                        $this->PageAnnots[$this->page][$pak]['y'] -= $yshift;
                                    }
                                }
                            }
                        }
                        $this->y -= $yshift;
                    }
                }
                $pbrk = $this->checkPageBreak($this->lasth);
                $this->newline = false;
                $startlinex = $this->x;
                $startliney = $this->y;
                if ($dom[$dom[$key]['parent']]['value'] == 'sup') {
                    $startliney -= ((0.3 * $this->FontSizePt) / $this->k);
                } elseif ($dom[$dom[$key]['parent']]['value'] == 'sub') {
                    $startliney -= (($this->FontSizePt / 0.7) / $this->k);
                } else {
                    $minstartliney = $startliney;
                    $maxbottomliney = ($this->y + $this->getCellHeight($fontsize / $this->k));
                }
                $startlinepage = $this->page;
                if (isset($endlinepos) AND (!$pbrk)) {
                    $startlinepos = $endlinepos;
                } else {
                    if ($this->inxobj) {
                        // we are inside an XObject template
                        $startlinepos = strlen($this->xobjects[$this->xobjid]['outdata']);
                    } elseif (!$this->InFooter) {
                        if (isset($this->footerlen[$this->page])) {
                            $this->footerpos[$this->page] = $this->pagelen[$this->page] - $this->footerlen[$this->page];
                        } else {
                            $this->footerpos[$this->page] = $this->pagelen[$this->page];
                        }
                        $startlinepos = $this->footerpos[$this->page];
                    } else {
                        $startlinepos = $this->pagelen[$this->page];
                    }
                }
                unset($endlinepos);
                $plalign = $lalign;
                if (isset($this->PageAnnots[$this->page])) {
                    $pask = count($this->PageAnnots[$this->page]);
                } else {
                    $pask = 0;
                }
                if (!($dom[$key]['tag'] AND !$dom[$key]['opening'] AND ($dom[$key]['value'] == 'table')
                    AND (isset($this->emptypagemrk[$this->page]))
                    AND ($this->emptypagemrk[$this->page] == $this->pagelen[$this->page]))) {
                    $this->SetFont($fontname, $fontstyle, $fontsize);
                    if ($wfill) {
                        $this->SetFillColorArray($this->bgcolor);
                    }
                }
            } // end newline
            if (isset($opentagpos)) {
                unset($opentagpos);
            }
            if ($dom[$key]['tag']) {
                if ($dom[$key]['opening']) {
                    // get text indentation (if any)
                    if (isset($dom[$key]['text-indent']) AND $dom[$key]['block']) {
                        $this->textindent = $dom[$key]['text-indent'];
                        $this->newline = true;
                    }
                    // table
                    if (($dom[$key]['value'] == 'table') AND isset($dom[$key]['cols']) AND ($dom[$key]['cols'] > 0)) {
                        // available page width
                        if ($this->rtl) {
                            $wtmp = $this->x - $this->lMargin;
                        } else {
                            $wtmp = $this->w - $this->rMargin - $this->x;
                        }
                        // get cell spacing
                        if (isset($dom[$key]['attribute']['cellspacing'])) {
                            $clsp = $this->getHTMLUnitToUnits($dom[$key]['attribute']['cellspacing'], 1, 'px');
                            $cellspacing = array('H' => $clsp, 'V' => $clsp);
                        } elseif (isset($dom[$key]['border-spacing'])) {
                            $cellspacing = $dom[$key]['border-spacing'];
                        } else {
                            $cellspacing = array('H' => 0, 'V' => 0);
                        }
                        // table width
                        if (isset($dom[$key]['width'])) {
                            $table_width = $this->getHTMLUnitToUnits($dom[$key]['width'], $wtmp, 'px');
                        } else {
                            $table_width = $wtmp;
                        }
                        $table_width -= (2 * $cellspacing['H']);
                        if (!$this->inthead) {
                            $this->y += $cellspacing['V'];
                        }
                        if ($this->rtl) {
                            $cellspacingx = -$cellspacing['H'];
                        } else {
                            $cellspacingx = $cellspacing['H'];
                        }
                        // total table width without cellspaces
                        $table_columns_width = ($table_width - ($cellspacing['H'] * ($dom[$key]['cols'] - 1)));
                        // minimum column width
                        $table_min_column_width = ($table_columns_width / $dom[$key]['cols']);
                        // array of custom column widths
                        $table_colwidths = array_fill(0, $dom[$key]['cols'], $table_min_column_width);
                    }
                    // table row
                    if ($dom[$key]['value'] == 'tr') {
                        // reset column counter
                        $colid = 0;
                    }
                    // table cell
                    if (($dom[$key]['value'] == 'td') OR ($dom[$key]['value'] == 'th')) {
                        $trid = $dom[$key]['parent'];
                        $table_el = $dom[$trid]['parent'];
                        if (!isset($dom[$table_el]['cols'])) {
                            $dom[$table_el]['cols'] = $dom[$trid]['cols'];
                        }
                        // store border info
                        $tdborder = 0;
                        if (isset($dom[$key]['border']) AND !empty($dom[$key]['border'])) {
                            $tdborder = $dom[$key]['border'];
                        }
                        $colspan = intval($dom[$key]['attribute']['colspan']);
                        if ($colspan <= 0) {
                            $colspan = 1;
                        }
                        $old_cell_padding = $this->cell_padding;
                        if (isset($dom[($dom[$trid]['parent'])]['attribute']['cellpadding'])) {
                            $crclpd = $this->getHTMLUnitToUnits($dom[($dom[$trid]['parent'])]['attribute']['cellpadding'], 1, 'px');
                            $current_cell_padding = array('L' => $crclpd, 'T' => $crclpd, 'R' => $crclpd, 'B' => $crclpd);
                        } elseif (isset($dom[($dom[$trid]['parent'])]['padding'])) {
                            $current_cell_padding = $dom[($dom[$trid]['parent'])]['padding'];
                        } else {
                            $current_cell_padding = array('L' => 0, 'T' => 0, 'R' => 0, 'B' => 0);
                        }
                        $this->cell_padding = $current_cell_padding;
                        if (isset($dom[$key]['height'])) {
                            // minimum cell height
                            $cellh = $this->getHTMLUnitToUnits($dom[$key]['height'], 0, 'px');
                        } else {
                            $cellh = 0;
                        }
                        if (isset($dom[$key]['content'])) {
                            $cell_content = $dom[$key]['content'];
                        } else {
                            $cell_content = '&nbsp;';
                        }
                        $tagtype = $dom[$key]['value'];
                        $parentid = $key;
                        while (($key < $maxel) AND (!(($dom[$key]['tag']) AND (!$dom[$key]['opening']) AND ($dom[$key]['value'] == $tagtype) AND ($dom[$key]['parent'] == $parentid)))) {
                            // move $key index forward
                            ++$key;
                        }
                        if (!isset($dom[$trid]['startpage'])) {
                            $dom[$trid]['startpage'] = $this->page;
                        } else {
                            $this->setPage($dom[$trid]['startpage']);
                        }
                        if (!isset($dom[$trid]['startcolumn'])) {
                            $dom[$trid]['startcolumn'] = $this->current_column;
                        } elseif ($this->current_column != $dom[$trid]['startcolumn']) {
                            $tmpx = $this->x;
                            $this->selectColumn($dom[$trid]['startcolumn']);
                            $this->x = $tmpx;
                        }
                        if (!isset($dom[$trid]['starty'])) {
                            $dom[$trid]['starty'] = $this->y;
                        } else {
                            $this->y = $dom[$trid]['starty'];
                        }
                        if (!isset($dom[$trid]['startx'])) {
                            $dom[$trid]['startx'] = $this->x;
                            $this->x += $cellspacingx;
                        } else {
                            $this->x += ($cellspacingx / 2);
                        }
                        if (isset($dom[$parentid]['attribute']['rowspan'])) {
                            $rowspan = intval($dom[$parentid]['attribute']['rowspan']);
                        } else {
                            $rowspan = 1;
                        }
                        // skip row-spanned cells started on the previous rows
                        if (isset($dom[$table_el]['rowspans'])) {
                            $rsk = 0;
                            $rskmax = count($dom[$table_el]['rowspans']);
                            while ($rsk < $rskmax) {
                                $trwsp = $dom[$table_el]['rowspans'][$rsk];
                                $rsstartx = $trwsp['startx'];
                                $rsendx = $trwsp['endx'];
                                // account for margin changes
                                if ($trwsp['startpage'] < $this->page) {
                                    if (($this->rtl) AND ($this->pagedim[$this->page]['orm'] != $this->pagedim[$trwsp['startpage']]['orm'])) {
                                        $dl = ($this->pagedim[$this->page]['orm'] - $this->pagedim[$trwsp['startpage']]['orm']);
                                        $rsstartx -= $dl;
                                        $rsendx -= $dl;
                                    } elseif ((!$this->rtl) AND ($this->pagedim[$this->page]['olm'] != $this->pagedim[$trwsp['startpage']]['olm'])) {
                                        $dl = ($this->pagedim[$this->page]['olm'] - $this->pagedim[$trwsp['startpage']]['olm']);
                                        $rsstartx += $dl;
                                        $rsendx += $dl;
                                    }
                                }
                                if (($trwsp['rowspan'] > 0)
                                    AND ($rsstartx > ($this->x - $cellspacing['H'] - $current_cell_padding['L'] - $this->feps))
                                    AND ($rsstartx < ($this->x + $cellspacing['H'] + $current_cell_padding['R'] + $this->feps))
                                    AND (($trwsp['starty'] < ($this->y - $this->feps)) OR ($trwsp['startpage'] < $this->page) OR ($trwsp['startcolumn'] < $this->current_column))) {
                                    // set the starting X position of the current cell
                                    $this->x = $rsendx + $cellspacingx;
                                    // increment column indicator
                                    $colid += $trwsp['colspan'];
                                    if (($trwsp['rowspan'] == 1)
                                        AND (isset($dom[$trid]['endy']))
                                        AND (isset($dom[$trid]['endpage']))
                                        AND (isset($dom[$trid]['endcolumn']))
                                        AND ($trwsp['endpage'] == $dom[$trid]['endpage'])
                                        AND ($trwsp['endcolumn'] == $dom[$trid]['endcolumn'])) {
                                        // set ending Y position for row
                                        $dom[$table_el]['rowspans'][$rsk]['endy'] = max($dom[$trid]['endy'], $trwsp['endy']);
                                        $dom[$trid]['endy'] = $dom[$table_el]['rowspans'][$rsk]['endy'];
                                    }
                                    $rsk = 0;
                                } else {
                                    ++$rsk;
                                }
                            }
                        }
                        if (isset($dom[$parentid]['width'])) {
                            // user specified width
                            $cellw = $this->getHTMLUnitToUnits($dom[$parentid]['width'], $table_columns_width, 'px');
                            $tmpcw = ($cellw / $colspan);
                            for ($i = 0; $i < $colspan; ++$i) {
                                $table_colwidths[($colid + $i)] = $tmpcw;
                            }
                        } else {
                            // inherit column width
                            $cellw = 0;
                            for ($i = 0; $i < $colspan; ++$i) {
                                $cellw += (isset($table_colwidths[($colid + $i)]) ? $table_colwidths[($colid + $i)] : 0);
                            }
                        }
                        $cellw += (($colspan - 1) * $cellspacing['H']);
                        // increment column indicator
                        $colid += $colspan;
                        // add rowspan information to table element
                        if ($rowspan > 1) {
                            $trsid = array_push($dom[$table_el]['rowspans'], array('trid' => $trid, 'rowspan' => $rowspan, 'mrowspan' => $rowspan, 'colspan' => $colspan, 'startpage' => $this->page, 'startcolumn' => $this->current_column, 'startx' => $this->x, 'starty' => $this->y));
                        }
                        $cellid = array_push($dom[$trid]['cellpos'], array('startx' => $this->x));
                        if ($rowspan > 1) {
                            $dom[$trid]['cellpos'][($cellid - 1)]['rowspanid'] = ($trsid - 1);
                        }
                        // push background colors
                        if (isset($dom[$parentid]['bgcolor']) AND ($dom[$parentid]['bgcolor'] !== false)) {
                            $dom[$trid]['cellpos'][($cellid - 1)]['bgcolor'] = $dom[$parentid]['bgcolor'];
                        }
                        // store border info
                        if (isset($tdborder) AND !empty($tdborder)) {
                            $dom[$trid]['cellpos'][($cellid - 1)]['border'] = $tdborder;
                        }
                        $prevLastH = $this->lasth;
                        // store some info for multicolumn mode
                        if ($this->rtl) {
                            $this->colxshift['x'] = $this->w - $this->x - $this->rMargin;
                        } else {
                            $this->colxshift['x'] = $this->x - $this->lMargin;
                        }
                        $this->colxshift['s'] = $cellspacing;
                        $this->colxshift['p'] = $current_cell_padding;
                        // ****** write the cell content ******
                        $this->MultiCell($cellw, $cellh, $cell_content, false, $lalign, false, 2, '', '', true, 0, true, true, 0, 'T', false);
                        // restore some values
                        $this->colxshift = array('x' => 0, 's' => array('H' => 0, 'V' => 0), 'p' => array('L' => 0, 'T' => 0, 'R' => 0, 'B' => 0));
                        $this->lasth = $prevLastH;
                        $this->cell_padding = $old_cell_padding;
                        $dom[$trid]['cellpos'][($cellid - 1)]['endx'] = $this->x;
                        // update the end of row position
                        if ($rowspan <= 1) {
                            if (isset($dom[$trid]['endy'])) {
                                if (($this->page == $dom[$trid]['endpage']) AND ($this->current_column == $dom[$trid]['endcolumn'])) {
                                    $dom[$trid]['endy'] = max($this->y, $dom[$trid]['endy']);
                                } elseif (($this->page > $dom[$trid]['endpage']) OR ($this->current_column > $dom[$trid]['endcolumn'])) {
                                    $dom[$trid]['endy'] = $this->y;
                                }
                            } else {
                                $dom[$trid]['endy'] = $this->y;
                            }
                            if (isset($dom[$trid]['endpage'])) {
                                $dom[$trid]['endpage'] = max($this->page, $dom[$trid]['endpage']);
                            } else {
                                $dom[$trid]['endpage'] = $this->page;
                            }
                            if (isset($dom[$trid]['endcolumn'])) {
                                $dom[$trid]['endcolumn'] = max($this->current_column, $dom[$trid]['endcolumn']);
                            } else {
                                $dom[$trid]['endcolumn'] = $this->current_column;
                            }
                        } else {
                            // account for row-spanned cells
                            $dom[$table_el]['rowspans'][($trsid - 1)]['endx'] = $this->x;
                            $dom[$table_el]['rowspans'][($trsid - 1)]['endy'] = $this->y;
                            $dom[$table_el]['rowspans'][($trsid - 1)]['endpage'] = $this->page;
                            $dom[$table_el]['rowspans'][($trsid - 1)]['endcolumn'] = $this->current_column;
                        }
                        if (isset($dom[$table_el]['rowspans'])) {
                            // update endy and endpage on rowspanned cells
                            foreach ($dom[$table_el]['rowspans'] as $k => $trwsp) {
                                if ($trwsp['rowspan'] > 0) {
                                    if (isset($dom[$trid]['endpage'])) {
                                        if (($trwsp['endpage'] == $dom[$trid]['endpage']) AND ($trwsp['endcolumn'] == $dom[$trid]['endcolumn'])) {
                                            $dom[$table_el]['rowspans'][$k]['endy'] = max($dom[$trid]['endy'], $trwsp['endy']);
                                        } elseif (($trwsp['endpage'] < $dom[$trid]['endpage']) OR ($trwsp['endcolumn'] < $dom[$trid]['endcolumn'])) {
                                            $dom[$table_el]['rowspans'][$k]['endy'] = $dom[$trid]['endy'];
                                            $dom[$table_el]['rowspans'][$k]['endpage'] = $dom[$trid]['endpage'];
                                            $dom[$table_el]['rowspans'][$k]['endcolumn'] = $dom[$trid]['endcolumn'];
                                        } else {
                                            $dom[$trid]['endy'] = $this->pagedim[$dom[$trid]['endpage']]['hk'] - $this->pagedim[$dom[$trid]['endpage']]['bm'];
                                        }
                                    }
                                }
                            }
                        }
                        $this->x += ($cellspacingx / 2);
                    } else {
                        // opening tag (or self-closing tag)
                        if (!isset($opentagpos)) {
                            if ($this->inxobj) {
                                // we are inside an XObject template
                                $opentagpos = strlen($this->xobjects[$this->xobjid]['outdata']);
                            } elseif (!$this->InFooter) {
                                if (isset($this->footerlen[$this->page])) {
                                    $this->footerpos[$this->page] = $this->pagelen[$this->page] - $this->footerlen[$this->page];
                                } else {
                                    $this->footerpos[$this->page] = $this->pagelen[$this->page];
                                }
                                $opentagpos = $this->footerpos[$this->page];
                            }
                        }
                        $dom = $this->openHTMLTagHandler($dom, $key, $cell);
                    }
                } else { // closing tag
                    $prev_numpages = $this->numpages;
                    $old_bordermrk = $this->bordermrk[$this->page];
                    $dom = $this->closeHTMLTagHandler($dom, $key, $cell, $maxbottomliney);
                    if ($this->bordermrk[$this->page] > $old_bordermrk) {
                        $startlinepos += ($this->bordermrk[$this->page] - $old_bordermrk);
                    }
                    if ($prev_numpages > $this->numpages) {
                        $startlinepage = $this->page;
                    }
                }
            } elseif (strlen($dom[$key]['value']) > 0) {
                // print list-item
                if (!TCPDF_STATIC::empty_string($this->lispacer) AND ($this->lispacer != '^')) {
                    $this->SetFont($pfontname, $pfontstyle, $pfontsize);
                    $this->resetLastH();
                    $minstartliney = $this->y;
                    $maxbottomliney = ($startliney + $this->getCellHeight($this->FontSize));
                    if (is_numeric($pfontsize) AND ($pfontsize > 0)) {
                        $this->putHtmlListBullet($this->listnum, $this->lispacer, $pfontsize);
                    }
                    $this->SetFont($curfontname, $curfontstyle, $curfontsize);
                    $this->resetLastH();
                    if (is_numeric($pfontsize) AND ($pfontsize > 0) AND is_numeric($curfontsize) AND ($curfontsize > 0) AND ($pfontsize != $curfontsize)) {
                        $pfontascent = $this->getFontAscent($pfontname, $pfontstyle, $pfontsize);
                        $pfontdescent = $this->getFontDescent($pfontname, $pfontstyle, $pfontsize);
                        $this->y += ($this->getCellHeight(($pfontsize - $curfontsize) / $this->k) + $pfontascent - $curfontascent - $pfontdescent + $curfontdescent) / 2;
                        $minstartliney = min($this->y, $minstartliney);
                        $maxbottomliney = max(($this->y + $this->getCellHeight($pfontsize / $this->k)), $maxbottomliney);
                    }
                }
                // text
                $this->htmlvspace = 0;
                if ((!$this->premode) AND $this->isRTLTextDir()) {
                    // reverse spaces order
                    $lsp = ''; // left spaces
                    $rsp = ''; // right spaces
                    if (preg_match('/^('.$this->re_space['p'].'+)/'.$this->re_space['m'], $dom[$key]['value'], $matches)) {
                        $lsp = $matches[1];
                    }
                    if (preg_match('/('.$this->re_space['p'].'+)$/'.$this->re_space['m'], $dom[$key]['value'], $matches)) {
                        $rsp = $matches[1];
                    }
                    $dom[$key]['value'] = $rsp.$this->stringTrim($dom[$key]['value']).$lsp;
                }
                if ($newline) {
                    if (!$this->premode) {
                        $prelen = strlen($dom[$key]['value']);
                        if ($this->isRTLTextDir()) {
                            // right trim except non-breaking space
                            $dom[$key]['value'] = $this->stringRightTrim($dom[$key]['value']);
                        } else {
                            // left trim except non-breaking space
                            $dom[$key]['value'] = $this->stringLeftTrim($dom[$key]['value']);
                        }
                        $postlen = strlen($dom[$key]['value']);
                        if (($postlen == 0) AND ($prelen > 0)) {
                            $dom[$key]['trimmed_space'] = true;
                        }
                    }
                    $newline = false;
                    $firstblock = true;
                } else {
                    $firstblock = false;
                    // replace empty multiple spaces string with a single space
                    $dom[$key]['value'] = preg_replace('/^'.$this->re_space['p'].'+$/'.$this->re_space['m'], chr(32), $dom[$key]['value']);
                }
                $strrest = '';
                if ($this->rtl) {
                    $this->x -= $this->textindent;
                } else {
                    $this->x += $this->textindent;
                }
                if (!isset($dom[$key]['trimmed_space']) OR !$dom[$key]['trimmed_space']) {
                    $strlinelen = $this->GetStringWidth($dom[$key]['value']);
                    if (!empty($this->HREF) AND (isset($this->HREF['url']))) {
                        // HTML <a> Link
                        $hrefcolor = '';
                        if (isset($dom[($dom[$key]['parent'])]['fgcolor']) AND ($dom[($dom[$key]['parent'])]['fgcolor'] !== false)) {
                            $hrefcolor = $dom[($dom[$key]['parent'])]['fgcolor'];
                        }
                        $hrefstyle = -1;
                        if (isset($dom[($dom[$key]['parent'])]['fontstyle']) AND ($dom[($dom[$key]['parent'])]['fontstyle'] !== false)) {
                            $hrefstyle = $dom[($dom[$key]['parent'])]['fontstyle'];
                        }
                        $strrest = $this->addHtmlLink($this->HREF['url'], $dom[$key]['value'], $wfill, true, $hrefcolor, $hrefstyle, true);
                    } else {
                        $wadj = 0; // space to leave for block continuity
                        if ($this->rtl) {
                            $cwa = ($this->x - $this->lMargin);
                        } else {
                            $cwa = ($this->w - $this->rMargin - $this->x);
                        }
                        if (($strlinelen < $cwa) AND (isset($dom[($key + 1)])) AND ($dom[($key + 1)]['tag']) AND (!$dom[($key + 1)]['block'])) {
                            // check the next text blocks for continuity
                            $nkey = ($key + 1);
                            $write_block = true;
                            $same_textdir = true;
                            $tmp_fontname = $this->FontFamily;
                            $tmp_fontstyle = $this->FontStyle;
                            $tmp_fontsize = $this->FontSizePt;
                            while ($write_block AND isset($dom[$nkey])) {
                                if ($dom[$nkey]['tag']) {
                                    if ($dom[$nkey]['block']) {
                                        // end of block
                                        $write_block = false;
                                    }
                                    $tmp_fontname = isset($dom[$nkey]['fontname']) ? $dom[$nkey]['fontname'] : $this->FontFamily;
                                    $tmp_fontstyle = isset($dom[$nkey]['fontstyle']) ? $dom[$nkey]['fontstyle'] : $this->FontStyle;
                                    $tmp_fontsize = isset($dom[$nkey]['fontsize']) ? $dom[$nkey]['fontsize'] : $this->FontSizePt;
                                    $same_textdir = ($dom[$nkey]['dir'] == $dom[$key]['dir']);
                                } else {
                                    $nextstr = TCPDF_STATIC::pregSplit('/'.$this->re_space['p'].'+/', $this->re_space['m'], $dom[$nkey]['value']);
                                    if (isset($nextstr[0]) AND $same_textdir) {
                                        $wadj += $this->GetStringWidth($nextstr[0], $tmp_fontname, $tmp_fontstyle, $tmp_fontsize);
                                        if (isset($nextstr[1])) {
                                            $write_block = false;
                                        }
                                    }
                                }
                                ++$nkey;
                            }
                        }
                        if (($wadj > 0) AND (($strlinelen + $wadj) >= $cwa)) {
                            $wadj = 0;
                            $nextstr = TCPDF_STATIC::pregSplit('/'.$this->re_space['p'].'/', $this->re_space['m'], $dom[$key]['value']);
                            $numblks = count($nextstr);
                            if ($numblks > 1) {
                                // try to split on blank spaces
                                $wadj = ($cwa - $strlinelen + $this->GetStringWidth($nextstr[($numblks - 1)]));
                            } else {
                                // set the entire block on new line
                                $wadj = $this->GetStringWidth($nextstr[0]);
                            }
                        }
                        // check for reversed text direction
                        if (($wadj > 0) AND (($this->rtl AND ($this->tmprtl === 'L')) OR (!$this->rtl AND ($this->tmprtl === 'R')))) {
                            // LTR text on RTL direction or RTL text on LTR direction
                            $reverse_dir = true;
                            $this->rtl = !$this->rtl;
                            $revshift = ($strlinelen + $wadj + 0.000001); // add little quantity for rounding problems
                            if ($this->rtl) {
                                $this->x += $revshift;
                            } else {
                                $this->x -= $revshift;
                            }
                            $xws = $this->x;
                        }
                        // ****** write only until the end of the line and get the rest ******
                        $strrest = $this->Write($this->lasth, $dom[$key]['value'], '', $wfill, '', false, 0, true, $firstblock, 0, $wadj);
                        // restore default direction
                        if ($reverse_dir AND ($wadj == 0)) {
                            $this->x = $xws;
                            $this->rtl = !$this->rtl;
                            $reverse_dir = false;
                        }
                    }
                }
                $this->textindent = 0;
                if (strlen($strrest) > 0) {
                    // store the remaining string on the previous $key position
                    $this->newline = true;
                    if ($strrest == $dom[$key]['value']) {
                        // used to avoid infinite loop
                        ++$loop;
                    } else {
                        $loop = 0;
                    }
                    $dom[$key]['value'] = $strrest;
                    if ($cell) {
                        if ($this->rtl) {
                            $this->x -= $this->cell_padding['R'];
                        } else {
                            $this->x += $this->cell_padding['L'];
                        }
                    }
                    if ($loop < 3) {
                        --$key;
                    }
                } else {
                    $loop = 0;
                    // add the positive font spacing of the last character (if any)
                     if ($this->font_spacing > 0) {
                        if ($this->rtl) {
                            $this->x -= $this->font_spacing;
                        } else {
                            $this->x += $this->font_spacing;
                        }
                    }
                }
            }
            ++$key;
            if (isset($dom[$key]['tag']) AND $dom[$key]['tag'] AND (!isset($dom[$key]['opening']) OR !$dom[$key]['opening']) AND isset($dom[($dom[$key]['parent'])]['attribute']['nobr']) AND ($dom[($dom[$key]['parent'])]['attribute']['nobr'] == 'true')) {
                // check if we are on a new page or on a new column
                if ((!$undo) AND (($this->y < $this->start_transaction_y) OR (($dom[$key]['value'] == 'tr') AND ($dom[($dom[$key]['parent'])]['endy'] < $this->start_transaction_y)))) {
                    // we are on a new page or on a new column and the total object height is less than the available vertical space.
                    // restore previous object
                    $this->rollbackTransaction(true);
                    // restore previous values
                    foreach ($this_method_vars as $vkey => $vval) {
                        $$vkey = $vval;
                    }
                    if (!empty($dom[$key]['thead'])) {
                        $this->inthead = true;
                    }
                    // add a page (or trig AcceptPageBreak() for multicolumn mode)
                    $pre_y = $this->y;
                    if ((!$this->checkPageBreak($this->PageBreakTrigger + 1)) AND ($this->y < $pre_y)) {
                        $startliney = $this->y;
                    }
                    $undo = true; // avoid infinite loop
                } else {
                    $undo = false;
                }
            }
        } // end for each $key
        // align the last line
        if (isset($startlinex)) {
            $yshift = ($minstartliney - $startliney);
            if (($yshift > 0) OR ($this->page > $startlinepage)) {
                $yshift = 0;
            }
            $t_x = 0;
            // the last line must be shifted to be aligned as requested
            $linew = abs($this->endlinex - $startlinex);
            if ($this->inxobj) {
                // we are inside an XObject template
                $pstart = substr($this->xobjects[$this->xobjid]['outdata'], 0, $startlinepos);
                if (isset($opentagpos)) {
                    $midpos = $opentagpos;
                } else {
                    $midpos = 0;
                }
                if ($midpos > 0) {
                    $pmid = substr($this->xobjects[$this->xobjid]['outdata'], $startlinepos, ($midpos - $startlinepos));
                    $pend = substr($this->xobjects[$this->xobjid]['outdata'], $midpos);
                } else {
                    $pmid = substr($this->xobjects[$this->xobjid]['outdata'], $startlinepos);
                    $pend = '';
                }
            } else {
                $pstart = substr($this->getPageBuffer($startlinepage), 0, $startlinepos);
                if (isset($opentagpos) AND isset($this->footerlen[$startlinepage]) AND (!$this->InFooter)) {
                    $this->footerpos[$startlinepage] = $this->pagelen[$startlinepage] - $this->footerlen[$startlinepage];
                    $midpos = min($opentagpos, $this->footerpos[$startlinepage]);
                } elseif (isset($opentagpos)) {
                    $midpos = $opentagpos;
                } elseif (isset($this->footerlen[$startlinepage]) AND (!$this->InFooter)) {
                    $this->footerpos[$startlinepage] = $this->pagelen[$startlinepage] - $this->footerlen[$startlinepage];
                    $midpos = $this->footerpos[$startlinepage];
                } else {
                    $midpos = 0;
                }
                if ($midpos > 0) {
                    $pmid = substr($this->getPageBuffer($startlinepage), $startlinepos, ($midpos - $startlinepos));
                    $pend = substr($this->getPageBuffer($startlinepage), $midpos);
                } else {
                    $pmid = substr($this->getPageBuffer($startlinepage), $startlinepos);
                    $pend = '';
                }
            }
            if ((isset($plalign) AND ((($plalign == 'C') OR (($plalign == 'R') AND (!$this->rtl)) OR (($plalign == 'L') AND ($this->rtl)))))) {
                // calculate shifting amount
                $tw = $w;
                if ($this->lMargin != $prevlMargin) {
                    $tw += ($prevlMargin - $this->lMargin);
                }
                if ($this->rMargin != $prevrMargin) {
                    $tw += ($prevrMargin - $this->rMargin);
                }
                $one_space_width = $this->GetStringWidth(chr(32));
                $no = 0; // number of spaces on a line contained on a single block
                if ($this->isRTLTextDir()) { // RTL
                    // remove left space if exist
                    $pos1 = TCPDF_STATIC::revstrpos($pmid, '[(');
                    if ($pos1 > 0) {
                        $pos1 = intval($pos1);
                        if ($this->isUnicodeFont()) {
                            $pos2 = intval(TCPDF_STATIC::revstrpos($pmid, '[('.chr(0).chr(32)));
                            $spacelen = 2;
                        } else {
                            $pos2 = intval(TCPDF_STATIC::revstrpos($pmid, '[('.chr(32)));
                            $spacelen = 1;
                        }
                        if ($pos1 == $pos2) {
                            $pmid = substr($pmid, 0, ($pos1 + 2)).substr($pmid, ($pos1 + 2 + $spacelen));
                            if (substr($pmid, $pos1, 4) == '[()]') {
                                $linew -= $one_space_width;
                            } elseif ($pos1 == strpos($pmid, '[(')) {
                                $no = 1;
                            }
                        }
                    }
                } else { // LTR
                    // remove right space if exist
                    $pos1 = TCPDF_STATIC::revstrpos($pmid, ')]');
                    if ($pos1 > 0) {
                        $pos1 = intval($pos1);
                        if ($this->isUnicodeFont()) {
                            $pos2 = intval(TCPDF_STATIC::revstrpos($pmid, chr(0).chr(32).')]')) + 2;
                            $spacelen = 2;
                        } else {
                            $pos2 = intval(TCPDF_STATIC::revstrpos($pmid, chr(32).')]')) + 1;
                            $spacelen = 1;
                        }
                        if ($pos1 == $pos2) {
                            $pmid = substr($pmid, 0, ($pos1 - $spacelen)).substr($pmid, $pos1);
                            $linew -= $one_space_width;
                        }
                    }
                }
                $mdiff = ($tw - $linew);
                if ($plalign == 'C') {
                    if ($this->rtl) {
                        $t_x = -($mdiff / 2);
                    } else {
                        $t_x = ($mdiff / 2);
                    }
                } elseif ($plalign == 'R') {
                    // right alignment on LTR document
                    $t_x = $mdiff;
                } elseif ($plalign == 'L') {
                    // left alignment on RTL document
                    $t_x = -$mdiff;
                }
            } // end if startlinex
            if (($t_x != 0) OR ($yshift < 0)) {
                // shift the line
                $trx = sprintf('1 0 0 1 %F %F cm', ($t_x * $this->k), ($yshift * $this->k));
                $pstart .= "\nq\n".$trx."\n".$pmid."\nQ\n";
                $endlinepos = strlen($pstart);
                if ($this->inxobj) {
                    // we are inside an XObject template
                    $this->xobjects[$this->xobjid]['outdata'] = $pstart.$pend;
                    foreach ($this->xobjects[$this->xobjid]['annotations'] as $pak => $pac) {
                        if ($pak >= $pask) {
                            $this->xobjects[$this->xobjid]['annotations'][$pak]['x'] += $t_x;
                            $this->xobjects[$this->xobjid]['annotations'][$pak]['y'] -= $yshift;
                        }
                    }
                } else {
                    $this->setPageBuffer($startlinepage, $pstart.$pend);
                    // shift the annotations and links
                    if (isset($this->PageAnnots[$this->page])) {
                        foreach ($this->PageAnnots[$this->page] as $pak => $pac) {
                            if ($pak >= $pask) {
                                $this->PageAnnots[$this->page][$pak]['x'] += $t_x;
                                $this->PageAnnots[$this->page][$pak]['y'] -= $yshift;
                            }
                        }
                    }
                }
                $this->y -= $yshift;
                $yshift = 0;
            }
        }
        // restore previous values
        $this->setGraphicVars($gvars);
        if ($this->num_columns > 1) {
            $this->selectColumn();
        } elseif ($this->page > $prevPage) {
            $this->lMargin = $this->pagedim[$this->page]['olm'];
            $this->rMargin = $this->pagedim[$this->page]['orm'];
        }
        // restore previous list state
        $this->cell_height_ratio = $prev_cell_height_ratio;
        $this->listnum = $prev_listnum;
        $this->listordered = $prev_listordered;
        $this->listcount = $prev_listcount;
        $this->lispacer = $prev_lispacer;
        if ($ln AND (!($cell AND ($dom[$key-1]['value'] == 'table')))) {
            $this->Ln($this->lasth);
            if (($this->y < $maxbottomliney) AND ($startlinepage == $this->page)) {
                $this->y = $maxbottomliney;
            }
        }
        unset($dom);
    }

    /**
     * Process opening tags.
     * @param $dom (array) html dom array
     * @param $key (int) current element id
     * @param $cell (boolean) if true add the default left (or right if RTL) padding to each new line (default false).
     * @return $dom array
     * @protected
     */
    protected function openHTMLTagHandler($dom, $key, $cell) {
        $tag = $dom[$key];
        $parent = $dom[($dom[$key]['parent'])];
        $firsttag = ($key == 1);
        // check for text direction attribute
        if (isset($tag['dir'])) {
            $this->setTempRTL($tag['dir']);
        } else {
            $this->tmprtl = false;
        }
        if ($tag['block']) {
            $hbz = 0; // distance from y to line bottom
            $hb = 0; // vertical space between block tags
            // calculate vertical space for block tags
            if (isset($this->tagvspaces[$tag['value']][0]['h']) AND ($this->tagvspaces[$tag['value']][0]['h'] >= 0)) {
                $cur_h = $this->tagvspaces[$tag['value']][0]['h'];
            } elseif (isset($tag['fontsize'])) {
                $cur_h = $this->getCellHeight($tag['fontsize'] / $this->k);
            } else {
                $cur_h = $this->getCellHeight($this->FontSize);
            }
            if (isset($this->tagvspaces[$tag['value']][0]['n'])) {
                $on = $this->tagvspaces[$tag['value']][0]['n'];
            } elseif (preg_match('/[h][0-9]/', $tag['value']) > 0) {
                $on = 0.6;
            } else {
                $on = 1;
            }
            if ((!isset($this->tagvspaces[$tag['value']])) AND (in_array($tag['value'], array('div', 'dt', 'dd', 'li', 'br', 'hr')))) {
                $hb = 0;
            } else {
                $hb = ($on * $cur_h);
            }
            if (($this->htmlvspace <= 0) AND ($on > 0)) {
                if (isset($parent['fontsize'])) {
                    $hbz = (($parent['fontsize'] / $this->k) * $this->cell_height_ratio);
                } else {
                    $hbz = $this->getCellHeight($this->FontSize);
                }
            }
            if (isset($dom[($key - 1)]) AND ($dom[($key - 1)]['value'] == 'table')) {
                // fix vertical space after table
                $hbz = 0;
            }
            // closing vertical space
            $hbc = 0;
            if (isset($this->tagvspaces[$tag['value']][1]['h']) AND ($this->tagvspaces[$tag['value']][1]['h'] >= 0)) {
                $pre_h = $this->tagvspaces[$tag['value']][1]['h'];
            } elseif (isset($parent['fontsize'])) {
                $pre_h = $this->getCellHeight($parent['fontsize'] / $this->k);
            } else {
                $pre_h = $this->getCellHeight($this->FontSize);
            }
            if (isset($this->tagvspaces[$tag['value']][1]['n'])) {
                $cn = $this->tagvspaces[$tag['value']][1]['n'];
            } elseif (preg_match('/[h][0-9]/', $tag['value']) > 0) {
                $cn = 0.6;
            } else {
                $cn = 1;
            }
            if (isset($this->tagvspaces[$tag['value']][1])) {
                $hbc = ($cn * $pre_h);
            }
        }
        // Opening tag
        switch($tag['value']) {
            case 'table': {
                $cp = 0;
                $cs = 0;
                $dom[$key]['rowspans'] = array();
                if (!isset($dom[$key]['attribute']['nested']) OR ($dom[$key]['attribute']['nested'] != 'true')) {
                    $this->htmlvspace = 0;
                    // set table header
                    if (!TCPDF_STATIC::empty_string($dom[$key]['thead'])) {
                        // set table header
                        $this->thead = $dom[$key]['thead'];
                        if (!isset($this->theadMargins) OR (empty($this->theadMargins))) {
                            $this->theadMargins = array();
                            $this->theadMargins['cell_padding'] = $this->cell_padding;
                            $this->theadMargins['lmargin'] = $this->lMargin;
                            $this->theadMargins['rmargin'] = $this->rMargin;
                            $this->theadMargins['page'] = $this->page;
                            $this->theadMargins['cell'] = $cell;
                            $this->theadMargins['gvars'] = $this->getGraphicVars();
                        }
                    }
                }
                // store current margins and page
                $dom[$key]['old_cell_padding'] = $this->cell_padding;
                if (isset($tag['attribute']['cellpadding'])) {
                    $pad = $this->getHTMLUnitToUnits($tag['attribute']['cellpadding'], 1, 'px');
                    $this->SetCellPadding($pad);
                } elseif (isset($tag['padding'])) {
                    $this->cell_padding = $tag['padding'];
                }
                if (isset($tag['attribute']['cellspacing'])) {
                    $cs = $this->getHTMLUnitToUnits($tag['attribute']['cellspacing'], 1, 'px');
                } elseif (isset($tag['border-spacing'])) {
                    $cs = $tag['border-spacing']['V'];
                }
                $prev_y = $this->y;
                if ($this->checkPageBreak(((2 * $cp) + (2 * $cs) + $this->lasth), '', false) OR ($this->y < $prev_y)) {
                    $this->inthead = true;
                    // add a page (or trig AcceptPageBreak() for multicolumn mode)
                    $this->checkPageBreak($this->PageBreakTrigger + 1);
                }
                break;
            }
            case 'tr': {
                // array of columns positions
                $dom[$key]['cellpos'] = array();
                break;
            }
            case 'hr': {
                if ((isset($tag['height'])) AND ($tag['height'] != '')) {
                    $hrHeight = $this->getHTMLUnitToUnits($tag['height'], 1, 'px');
                } else {
                    $hrHeight = $this->GetLineWidth();
                }
                $this->addHTMLVertSpace($hbz, max($hb, ($hrHeight / 2)), $cell, $firsttag);
                $x = $this->GetX();
                $y = $this->GetY();
                $wtmp = $this->w - $this->lMargin - $this->rMargin;
                if ($cell) {
                    $wtmp -= ($this->cell_padding['L'] + $this->cell_padding['R']);
                }
                if ((isset($tag['width'])) AND ($tag['width'] != '')) {
                    $hrWidth = $this->getHTMLUnitToUnits($tag['width'], $wtmp, 'px');
                } else {
                    $hrWidth = $wtmp;
                }
                $prevlinewidth = $this->GetLineWidth();
                $this->SetLineWidth($hrHeight);
                $this->Line($x, $y, $x + $hrWidth, $y);
                $this->SetLineWidth($prevlinewidth);
                $this->addHTMLVertSpace(max($hbc, ($hrHeight / 2)), 0, $cell, !isset($dom[($key + 1)]));
                break;
            }
            case 'a': {
                if (array_key_exists('href', $tag['attribute'])) {
                    $this->HREF['url'] = $tag['attribute']['href'];
                }
                break;
            }
            case 'img': {
                if (!empty($tag['attribute']['src'])) {
                    if ($tag['attribute']['src'][0] === '@') {
                        // data stream
                        $tag['attribute']['src'] = '@'.base64_decode(substr($tag['attribute']['src'], 1));
                        $type = '';
                    } else {
                        // get image type
                        $type = TCPDF_IMAGES::getImageFileType($tag['attribute']['src']);
                    }
                    if (!isset($tag['width'])) {
                        $tag['width'] = 0;
                    }
                    if (!isset($tag['height'])) {
                        $tag['height'] = 0;
                    }
                    //if (!isset($tag['attribute']['align'])) {
                        // the only alignment supported is "bottom"
                        // further development is required for other modes.
                        $tag['attribute']['align'] = 'bottom';
                    //}
                    switch($tag['attribute']['align']) {
                        case 'top': {
                            $align = 'T';
                            break;
                        }
                        case 'middle': {
                            $align = 'M';
                            break;
                        }
                        case 'bottom': {
                            $align = 'B';
                            break;
                        }
                        default: {
                            $align = 'B';
                            break;
                        }
                    }
                    $prevy = $this->y;
                    $xpos = $this->x;
                    $imglink = '';
                    if (isset($this->HREF['url']) AND !TCPDF_STATIC::empty_string($this->HREF['url'])) {
                        $imglink = $this->HREF['url'];
                        if ($imglink[0] == '#') {
                            // convert url to internal link
                            $lnkdata = explode(',', $imglink);
                            if (isset($lnkdata[0])) {
                                $page = intval(substr($lnkdata[0], 1));
                                if (empty($page) OR ($page <= 0)) {
                                    $page = $this->page;
                                }
                                if (isset($lnkdata[1]) AND (strlen($lnkdata[1]) > 0)) {
                                    $lnky = floatval($lnkdata[1]);
                                } else {
                                    $lnky = 0;
                                }
                                $imglink = $this->AddLink();
                                $this->SetLink($imglink, $lnky, $page);
                            }
                        }
                    }
                    $border = 0;
                    if (isset($tag['border']) AND !empty($tag['border'])) {
                        // currently only support 1 (frame) or a combination of 'LTRB'
                        $border = $tag['border'];
                    }
                    $iw = '';
                    if (isset($tag['width'])) {
                        $iw = $this->getHTMLUnitToUnits($tag['width'], ($tag['fontsize'] / $this->k), 'px', false);
                    }
                    $ih = '';
                    if (isset($tag['height'])) {
                        $ih = $this->getHTMLUnitToUnits($tag['height'], ($tag['fontsize'] / $this->k), 'px', false);
                    }
                    if (($type == 'eps') OR ($type == 'ai')) {
                        $this->ImageEps($tag['attribute']['src'], $xpos, $this->y, $iw, $ih, $imglink, true, $align, '', $border, true);
                    } elseif ($type == 'svg') {
                        $this->ImageSVG($tag['attribute']['src'], $xpos, $this->y, $iw, $ih, $imglink, $align, '', $border, true);
                    } else {
                        $this->Image($tag['attribute']['src'], $xpos, $this->y, $iw, $ih, '', $imglink, $align, false, 300, '', false, false, $border, false, false, true);
                    }
                    switch($align) {
                        case 'T': {
                            $this->y = $prevy;
                            break;
                        }
                        case 'M': {
                            $this->y = (($this->img_rb_y + $prevy - ($this->getCellHeight($tag['fontsize'] / $this->k))) / 2);
                            break;
                        }
                        case 'B': {
                            $this->y = $this->img_rb_y - ($this->getCellHeight($tag['fontsize'] / $this->k) - ($this->getFontDescent($tag['fontname'], $tag['fontstyle'], $tag['fontsize']) * $this->cell_height_ratio));
                            break;
                        }
                    }
                }
                break;
            }
            case 'dl': {
                ++$this->listnum;
                if ($this->listnum == 1) {
                    $this->addHTMLVertSpace($hbz, $hb, $cell, $firsttag);
                } else {
                    $this->addHTMLVertSpace(0, 0, $cell, $firsttag);
                }
                break;
            }
            case 'dt': {
                $this->addHTMLVertSpace($hbz, $hb, $cell, $firsttag);
                break;
            }
            case 'dd': {
                if ($this->rtl) {
                    $this->rMargin += $this->listindent;
                } else {
                    $this->lMargin += $this->listindent;
                }
                ++$this->listindentlevel;
                $this->addHTMLVertSpace($hbz, $hb, $cell, $firsttag);
                break;
            }
            case 'ul':
            case 'ol': {
                ++$this->listnum;
                if ($tag['value'] == 'ol') {
                    $this->listordered[$this->listnum] = true;
                } else {
                    $this->listordered[$this->listnum] = false;
                }
                if (isset($tag['attribute']['start'])) {
                    $this->listcount[$this->listnum] = intval($tag['attribute']['start']) - 1;
                } else {
                    $this->listcount[$this->listnum] = 0;
                }
                if ($this->rtl) {
                    $this->rMargin += $this->listindent;
                    $this->x -= $this->listindent;
                } else {
                    $this->lMargin += $this->listindent;
                    $this->x += $this->listindent;
                }
                ++$this->listindentlevel;
                if ($this->listnum == 1) {
                    if ($key > 1) {
                        $this->addHTMLVertSpace($hbz, $hb, $cell, $firsttag);
                    }
                } else {
                    $this->addHTMLVertSpace(0, 0, $cell, $firsttag);
                }
                break;
            }
            case 'li': {
                if ($key > 2) {
                    $this->addHTMLVertSpace($hbz, $hb, $cell, $firsttag);
                }
                if ($this->listordered[$this->listnum]) {
                    // ordered item
                    if (isset($parent['attribute']['type']) AND !TCPDF_STATIC::empty_string($parent['attribute']['type'])) {
                        $this->lispacer = $parent['attribute']['type'];
                    } elseif (isset($parent['listtype']) AND !TCPDF_STATIC::empty_string($parent['listtype'])) {
                        $this->lispacer = $parent['listtype'];
                    } elseif (isset($this->lisymbol) AND !TCPDF_STATIC::empty_string($this->lisymbol)) {
                        $this->lispacer = $this->lisymbol;
                    } else {
                        $this->lispacer = '#';
                    }
                    ++$this->listcount[$this->listnum];
                    if (isset($tag['attribute']['value'])) {
                        $this->listcount[$this->listnum] = intval($tag['attribute']['value']);
                    }
                } else {
                    // unordered item
                    if (isset($parent['attribute']['type']) AND !TCPDF_STATIC::empty_string($parent['attribute']['type'])) {
                        $this->lispacer = $parent['attribute']['type'];
                    } elseif (isset($parent['listtype']) AND !TCPDF_STATIC::empty_string($parent['listtype'])) {
                        $this->lispacer = $parent['listtype'];
                    } elseif (isset($this->lisymbol) AND !TCPDF_STATIC::empty_string($this->lisymbol)) {
                        $this->lispacer = $this->lisymbol;
                    } else {
                        $this->lispacer = '!';
                    }
                }
                break;
            }
            case 'blockquote': {
                if ($this->rtl) {
                    $this->rMargin += $this->listindent;
                } else {
                    $this->lMargin += $this->listindent;
                }
                ++$this->listindentlevel;
                $this->addHTMLVertSpace($hbz, $hb, $cell, $firsttag);
                break;
            }
            case 'br': {
                $this->addHTMLVertSpace($hbz, $hb, $cell, $firsttag);
                break;
            }
            case 'div': {
                $this->addHTMLVertSpace($hbz, $hb, $cell, $firsttag);
                break;
            }
            case 'p': {
                $this->addHTMLVertSpace($hbz, $hb, $cell, $firsttag);
                break;
            }
            case 'pre': {
                $this->addHTMLVertSpace($hbz, $hb, $cell, $firsttag);
                $this->premode = true;
                break;
            }
            case 'sup': {
                $this->SetXY($this->GetX(), $this->GetY() - ((0.7 * $this->FontSizePt) / $this->k));
                break;
            }
            case 'sub': {
                $this->SetXY($this->GetX(), $this->GetY() + ((0.3 * $this->FontSizePt) / $this->k));
                break;
            }
            case 'h1':
            case 'h2':
            case 'h3':
            case 'h4':
            case 'h5':
            case 'h6': {
                $this->addHTMLVertSpace($hbz, $hb, $cell, $firsttag);
                break;
            }
            // Form fields (since 4.8.000 - 2009-09-07)
            case 'form': {
                if (isset($tag['attribute']['action'])) {
                    $this->form_action = $tag['attribute']['action'];
                } else {
                    $this->Error('Please explicitly set action attribute path!');
                }
                if (isset($tag['attribute']['enctype'])) {
                    $this->form_enctype = $tag['attribute']['enctype'];
                } else {
                    $this->form_enctype = 'application/x-www-form-urlencoded';
                }
                if (isset($tag['attribute']['method'])) {
                    $this->form_mode = $tag['attribute']['method'];
                } else {
                    $this->form_mode = 'post';
                }
                break;
            }
            case 'input': {
                if (isset($tag['attribute']['name']) AND !TCPDF_STATIC::empty_string($tag['attribute']['name'])) {
                    $name = $tag['attribute']['name'];
                } else {
                    break;
                }
                $prop = array();
                $opt = array();
                if (isset($tag['attribute']['readonly']) AND !TCPDF_STATIC::empty_string($tag['attribute']['readonly'])) {
                    $prop['readonly'] = true;
                }
                if (isset($tag['attribute']['value']) AND !TCPDF_STATIC::empty_string($tag['attribute']['value'])) {
                    $value = $tag['attribute']['value'];
                }
                if (isset($tag['attribute']['maxlength']) AND !TCPDF_STATIC::empty_string($tag['attribute']['maxlength'])) {
                    $opt['maxlen'] = intval($tag['attribute']['maxlength']);
                }
                $h = $this->getCellHeight($this->FontSize);
                if (isset($tag['attribute']['size']) AND !TCPDF_STATIC::empty_string($tag['attribute']['size'])) {
                    $w = intval($tag['attribute']['size']) * $this->GetStringWidth(chr(32)) * 2;
                } else {
                    $w = $h;
                }
                if (isset($tag['attribute']['checked']) AND (($tag['attribute']['checked'] == 'checked') OR ($tag['attribute']['checked'] == 'true'))) {
                    $checked = true;
                } else {
                    $checked = false;
                }
                if (isset($tag['align'])) {
                    switch ($tag['align']) {
                        case 'C': {
                            $opt['q'] = 1;
                            break;
                        }
                        case 'R': {
                            $opt['q'] = 2;
                            break;
                        }
                        case 'L':
                        default: {
                            break;
                        }
                    }
                }
                switch ($tag['attribute']['type']) {
                    case 'text': {
                        if (isset($value)) {
                            $opt['v'] = $value;
                        }
                        $this->TextField($name, $w, $h, $prop, $opt, '', '', false);
                        break;
                    }
                    case 'password': {
                        if (isset($value)) {
                            $opt['v'] = $value;
                        }
                        $prop['password'] = 'true';
                        $this->TextField($name, $w, $h, $prop, $opt, '', '', false);
                        break;
                    }
                    case 'checkbox': {
                        if (!isset($value)) {
                            break;
                        }
                        $this->CheckBox($name, $w, $checked, $prop, $opt, $value, '', '', false);
                        break;
                    }
                    case 'radio': {
                        if (!isset($value)) {
                            break;
                        }
                        $this->RadioButton($name, $w, $prop, $opt, $value, $checked, '', '', false);
                        break;
                    }
                    case 'submit': {
                        if (!isset($value)) {
                            $value = 'submit';
                        }
                        $w = $this->GetStringWidth($value) * 1.5;
                        $h *= 1.6;
                        $prop = array('lineWidth'=>1, 'borderStyle'=>'beveled', 'fillColor'=>array(196, 196, 196), 'strokeColor'=>array(255, 255, 255));
                        $action = array();
                        $action['S'] = 'SubmitForm';
                        $action['F'] = $this->form_action;
                        if ($this->form_enctype != 'FDF') {
                            $action['Flags'] = array('ExportFormat');
                        }
                        if ($this->form_mode == 'get') {
                            $action['Flags'] = array('GetMethod');
                        }
                        $this->Button($name, $w, $h, $value, $action, $prop, $opt, '', '', false);
                        break;
                    }
                    case 'reset': {
                        if (!isset($value)) {
                            $value = 'reset';
                        }
                        $w = $this->GetStringWidth($value) * 1.5;
                        $h *= 1.6;
                        $prop = array('lineWidth'=>1, 'borderStyle'=>'beveled', 'fillColor'=>array(196, 196, 196), 'strokeColor'=>array(255, 255, 255));
                        $this->Button($name, $w, $h, $value, array('S'=>'ResetForm'), $prop, $opt, '', '', false);
                        break;
                    }
                    case 'file': {
                        $prop['fileSelect'] = 'true';
                        $this->TextField($name, $w, $h, $prop, $opt, '', '', false);
                        if (!isset($value)) {
                            $value = '*';
                        }
                        $w = $this->GetStringWidth($value) * 2;
                        $h *= 1.2;
                        $prop = array('lineWidth'=>1, 'borderStyle'=>'beveled', 'fillColor'=>array(196, 196, 196), 'strokeColor'=>array(255, 255, 255));
                        $jsaction = 'var f=this.getField(\''.$name.'\'); f.browseForFileToSubmit();';
                        $this->Button('FB_'.$name, $w, $h, $value, $jsaction, $prop, $opt, '', '', false);
                        break;
                    }
                    case 'hidden': {
                        if (isset($value)) {
                            $opt['v'] = $value;
                        }
                        $opt['f'] = array('invisible', 'hidden');
                        $this->TextField($name, 0, 0, $prop, $opt, '', '', false);
                        break;
                    }
                    case 'image': {
                        // THIS TYPE MUST BE FIXED
                        if (isset($tag['attribute']['src']) AND !TCPDF_STATIC::empty_string($tag['attribute']['src'])) {
                            $img = $tag['attribute']['src'];
                        } else {
                            break;
                        }
                        $value = 'img';
                        //$opt['mk'] = array('i'=>$img, 'tp'=>1, 'if'=>array('sw'=>'A', 's'=>'A', 'fb'=>false));
                        if (isset($tag['attribute']['onclick']) AND !empty($tag['attribute']['onclick'])) {
                            $jsaction = $tag['attribute']['onclick'];
                        } else {
                            $jsaction = '';
                        }
                        $this->Button($name, $w, $h, $value, $jsaction, $prop, $opt, '', '', false);
                        break;
                    }
                    case 'button': {
                        if (!isset($value)) {
                            $value = ' ';
                        }
                        $w = $this->GetStringWidth($value) * 1.5;
                        $h *= 1.6;
                        $prop = array('lineWidth'=>1, 'borderStyle'=>'beveled', 'fillColor'=>array(196, 196, 196), 'strokeColor'=>array(255, 255, 255));
                        if (isset($tag['attribute']['onclick']) AND !empty($tag['attribute']['onclick'])) {
                            $jsaction = $tag['attribute']['onclick'];
                        } else {
                            $jsaction = '';
                        }
                        $this->Button($name, $w, $h, $value, $jsaction, $prop, $opt, '', '', false);
                        break;
                    }
                }
                break;
            }
            case 'textarea': {
                $prop = array();
                $opt = array();
                if (isset($tag['attribute']['readonly']) AND !TCPDF_STATIC::empty_string($tag['attribute']['readonly'])) {
                    $prop['readonly'] = true;
                }
                if (isset($tag['attribute']['name']) AND !TCPDF_STATIC::empty_string($tag['attribute']['name'])) {
                    $name = $tag['attribute']['name'];
                } else {
                    break;
                }
                if (isset($tag['attribute']['value']) AND !TCPDF_STATIC::empty_string($tag['attribute']['value'])) {
                    $opt['v'] = $tag['attribute']['value'];
                }
                if (isset($tag['attribute']['cols']) AND !TCPDF_STATIC::empty_string($tag['attribute']['cols'])) {
                    $w = intval($tag['attribute']['cols']) * $this->GetStringWidth(chr(32)) * 2;
                } else {
                    $w = 40;
                }
                if (isset($tag['attribute']['rows']) AND !TCPDF_STATIC::empty_string($tag['attribute']['rows'])) {
                    $h = intval($tag['attribute']['rows']) * $this->getCellHeight($this->FontSize);
                } else {
                    $h = 10;
                }
                $prop['multiline'] = 'true';
                $this->TextField($name, $w, $h, $prop, $opt, '', '', false);
                break;
            }
            case 'select': {
                $h = $this->getCellHeight($this->FontSize);
                if (isset($tag['attribute']['size']) AND !TCPDF_STATIC::empty_string($tag['attribute']['size'])) {
                    $h *= ($tag['attribute']['size'] + 1);
                }
                $prop = array();
                $opt = array();
                if (isset($tag['attribute']['name']) AND !TCPDF_STATIC::empty_string($tag['attribute']['name'])) {
                    $name = $tag['attribute']['name'];
                } else {
                    break;
                }
                $w = 0;
                if (isset($tag['attribute']['opt']) AND !TCPDF_STATIC::empty_string($tag['attribute']['opt'])) {
                    $options = explode('#!NwL!#', $tag['attribute']['opt']);
                    $values = array();
                    foreach ($options as $val) {
                        if (strpos($val, '#!TaB!#') !== false) {
                            $opts = explode('#!TaB!#', $val);
                            $values[] = $opts;
                            $w = max($w, $this->GetStringWidth($opts[1]));
                        } else {
                            $values[] = $val;
                            $w = max($w, $this->GetStringWidth($val));
                        }
                    }
                } else {
                    break;
                }
                $w *= 2;
                if (isset($tag['attribute']['multiple']) AND ($tag['attribute']['multiple']='multiple')) {
                    $prop['multipleSelection'] = 'true';
                    $this->ListBox($name, $w, $h, $values, $prop, $opt, '', '', false);
                } else {
                    $this->ComboBox($name, $w, $h, $values, $prop, $opt, '', '', false);
                }
                break;
            }
            case 'tcpdf': {
                if (defined('K_TCPDF_CALLS_IN_HTML') AND (K_TCPDF_CALLS_IN_HTML === true)) {
                    // Special tag used to call TCPDF methods
                    if (isset($tag['attribute']['method'])) {
                        $tcpdf_method = $tag['attribute']['method'];
                        if (method_exists($this, $tcpdf_method)) {
                            if (isset($tag['attribute']['params']) AND (!empty($tag['attribute']['params']))) {
                                $params = $this->unserializeTCPDFtagParameters($tag['attribute']['params']);
                                call_user_func_array(array($this, $tcpdf_method), $params);
                            } else {
                                $this->$tcpdf_method();
                            }
                            $this->newline = true;
                        }
                    }
                }
                break;
            }
            default: {
                break;
            }
        }
        // define tags that support borders and background colors
        $bordertags = array('blockquote','br','dd','dl','div','dt','h1','h2','h3','h4','h5','h6','hr','li','ol','p','pre','ul','tcpdf','table');
        if (in_array($tag['value'], $bordertags)) {
            // set border
            $dom[$key]['borderposition'] = $this->getBorderStartPosition();
        }
        if ($dom[$key]['self'] AND isset($dom[$key]['attribute']['pagebreakafter'])) {
            $pba = $dom[$key]['attribute']['pagebreakafter'];
            // check for pagebreak
            if (($pba == 'true') OR ($pba == 'left') OR ($pba == 'right')) {
                // add a page (or trig AcceptPageBreak() for multicolumn mode)
                $this->checkPageBreak($this->PageBreakTrigger + 1);
            }
            if ((($pba == 'left') AND (((!$this->rtl) AND (($this->page % 2) == 0)) OR (($this->rtl) AND (($this->page % 2) != 0))))
                OR (($pba == 'right') AND (((!$this->rtl) AND (($this->page % 2) != 0)) OR (($this->rtl) AND (($this->page % 2) == 0))))) {
                // add a page (or trig AcceptPageBreak() for multicolumn mode)
                $this->checkPageBreak($this->PageBreakTrigger + 1);
            }
        }
        return $dom;
    }

    /**
     * Process closing tags.
     * @param $dom (array) html dom array
     * @param $key (int) current element id
     * @param $cell (boolean) if true add the default left (or right if RTL) padding to each new line (default false).
     * @param $maxbottomliney (int) maximum y value of current line
     * @return $dom array
     * @protected
     */
    protected function closeHTMLTagHandler($dom, $key, $cell, $maxbottomliney=0) {
        $tag = $dom[$key];
        $parent = $dom[($dom[$key]['parent'])];
        $lasttag = ((!isset($dom[($key + 1)])) OR ((!isset($dom[($key + 2)])) AND ($dom[($key + 1)]['value'] == 'marker')));
        $in_table_head = false;
        // maximum x position (used to draw borders)
        if ($this->rtl) {
            $xmax = $this->w;
        } else {
            $xmax = 0;
        }
        if ($tag['block']) {
            $hbz = 0; // distance from y to line bottom
            $hb = 0; // vertical space between block tags
            // calculate vertical space for block tags
            if (isset($this->tagvspaces[$tag['value']][1]['h']) AND ($this->tagvspaces[$tag['value']][1]['h'] >= 0)) {
                $pre_h = $this->tagvspaces[$tag['value']][1]['h'];
            } elseif (isset($parent['fontsize'])) {
                $pre_h = $this->getCellHeight($parent['fontsize'] / $this->k);
            } else {
                $pre_h = $this->getCellHeight($this->FontSize);
            }
            if (isset($this->tagvspaces[$tag['value']][1]['n'])) {
                $cn = $this->tagvspaces[$tag['value']][1]['n'];
            } elseif (preg_match('/[h][0-9]/', $tag['value']) > 0) {
                $cn = 0.6;
            } else {
                $cn = 1;
            }
            if ((!isset($this->tagvspaces[$tag['value']])) AND ($tag['value'] == 'div')) {
                $hb = 0;
            } else {
                $hb = ($cn * $pre_h);
            }
            if ($maxbottomliney > $this->PageBreakTrigger) {
                $hbz = $this->getCellHeight($this->FontSize);
            } elseif ($this->y < $maxbottomliney) {
                $hbz = ($maxbottomliney - $this->y);
            }
        }
        // Closing tag
        switch($tag['value']) {
            case 'tr': {
                $table_el = $dom[($dom[$key]['parent'])]['parent'];
                if (!isset($parent['endy'])) {
                    $dom[($dom[$key]['parent'])]['endy'] = $this->y;
                    $parent['endy'] = $this->y;
                }
                if (!isset($parent['endpage'])) {
                    $dom[($dom[$key]['parent'])]['endpage'] = $this->page;
                    $parent['endpage'] = $this->page;
                }
                if (!isset($parent['endcolumn'])) {
                    $dom[($dom[$key]['parent'])]['endcolumn'] = $this->current_column;
                    $parent['endcolumn'] = $this->current_column;
                }
                // update row-spanned cells
                if (isset($dom[$table_el]['rowspans'])) {
                    foreach ($dom[$table_el]['rowspans'] as $k => $trwsp) {
                        $dom[$table_el]['rowspans'][$k]['rowspan'] -= 1;
                        if ($dom[$table_el]['rowspans'][$k]['rowspan'] == 0) {
                            if (($dom[$table_el]['rowspans'][$k]['endpage'] == $parent['endpage']) AND ($dom[$table_el]['rowspans'][$k]['endcolumn'] == $parent['endcolumn'])) {
                                $dom[($dom[$key]['parent'])]['endy'] = max($dom[$table_el]['rowspans'][$k]['endy'], $parent['endy']);
                            } elseif (($dom[$table_el]['rowspans'][$k]['endpage'] > $parent['endpage']) OR ($dom[$table_el]['rowspans'][$k]['endcolumn'] > $parent['endcolumn'])) {
                                $dom[($dom[$key]['parent'])]['endy'] = $dom[$table_el]['rowspans'][$k]['endy'];
                                $dom[($dom[$key]['parent'])]['endpage'] = $dom[$table_el]['rowspans'][$k]['endpage'];
                                $dom[($dom[$key]['parent'])]['endcolumn'] = $dom[$table_el]['rowspans'][$k]['endcolumn'];
                            }
                        }
                    }
                    // report new endy and endpage to the rowspanned cells
                    foreach ($dom[$table_el]['rowspans'] as $k => $trwsp) {
                        if ($dom[$table_el]['rowspans'][$k]['rowspan'] == 0) {
                            $dom[$table_el]['rowspans'][$k]['endpage'] = max($dom[$table_el]['rowspans'][$k]['endpage'], $dom[($dom[$key]['parent'])]['endpage']);
                            $dom[($dom[$key]['parent'])]['endpage'] = $dom[$table_el]['rowspans'][$k]['endpage'];
                            $dom[$table_el]['rowspans'][$k]['endcolumn'] = max($dom[$table_el]['rowspans'][$k]['endcolumn'], $dom[($dom[$key]['parent'])]['endcolumn']);
                            $dom[($dom[$key]['parent'])]['endcolumn'] = $dom[$table_el]['rowspans'][$k]['endcolumn'];
                            $dom[$table_el]['rowspans'][$k]['endy'] = max($dom[$table_el]['rowspans'][$k]['endy'], $dom[($dom[$key]['parent'])]['endy']);
                            $dom[($dom[$key]['parent'])]['endy'] = $dom[$table_el]['rowspans'][$k]['endy'];
                        }
                    }
                    // update remaining rowspanned cells
                    foreach ($dom[$table_el]['rowspans'] as $k => $trwsp) {
                        if ($dom[$table_el]['rowspans'][$k]['rowspan'] == 0) {
                            $dom[$table_el]['rowspans'][$k]['endpage'] = $dom[($dom[$key]['parent'])]['endpage'];
                            $dom[$table_el]['rowspans'][$k]['endcolumn'] = $dom[($dom[$key]['parent'])]['endcolumn'];
                            $dom[$table_el]['rowspans'][$k]['endy'] = $dom[($dom[$key]['parent'])]['endy'];
                        }
                    }
                }
                $prev_page = $this->page;
                $this->setPage($dom[($dom[$key]['parent'])]['endpage']);
                if ($this->num_columns > 1) {
                    if (($prev_page < $this->page)
                        AND ((($this->current_column == 0) AND ($dom[($dom[$key]['parent'])]['endcolumn'] == ($this->num_columns - 1)))
                            OR ($this->current_column == $dom[($dom[$key]['parent'])]['endcolumn']))) {
                        // page jump
                        $this->selectColumn(0);
                        $dom[($dom[$key]['parent'])]['endcolumn'] = 0;
                        $dom[($dom[$key]['parent'])]['endy'] = $this->y;
                    } else {
                        $this->selectColumn($dom[($dom[$key]['parent'])]['endcolumn']);
                        $this->y = $dom[($dom[$key]['parent'])]['endy'];
                    }
                } else {
                    $this->y = $dom[($dom[$key]['parent'])]['endy'];
                }
                if (isset($dom[$table_el]['attribute']['cellspacing'])) {
                    $this->y += $this->getHTMLUnitToUnits($dom[$table_el]['attribute']['cellspacing'], 1, 'px');
                } elseif (isset($dom[$table_el]['border-spacing'])) {
                    $this->y += $dom[$table_el]['border-spacing']['V'];
                }
                $this->Ln(0, $cell);
                if ($this->current_column == $parent['startcolumn']) {
                    $this->x = $parent['startx'];
                }
                // account for booklet mode
                if ($this->page > $parent['startpage']) {
                    if (($this->rtl) AND ($this->pagedim[$this->page]['orm'] != $this->pagedim[$parent['startpage']]['orm'])) {
                        $this->x -= ($this->pagedim[$this->page]['orm'] - $this->pagedim[$parent['startpage']]['orm']);
                    } elseif ((!$this->rtl) AND ($this->pagedim[$this->page]['olm'] != $this->pagedim[$parent['startpage']]['olm'])) {
                        $this->x += ($this->pagedim[$this->page]['olm'] - $this->pagedim[$parent['startpage']]['olm']);
                    }
                }
                break;
            }
            case 'tablehead':
                // closing tag used for the thead part
                $in_table_head = true;
                $this->inthead = false;
            case 'table': {
                $table_el = $parent;
                // set default border
                if (isset($table_el['attribute']['border']) AND ($table_el['attribute']['border'] > 0)) {
                    // set default border
                    $border = array('LTRB' => array('width' => $this->getCSSBorderWidth($table_el['attribute']['border']), 'cap'=>'square', 'join'=>'miter', 'dash'=> 0, 'color'=>array(0,0,0)));
                } else {
                    $border = 0;
                }
                $default_border = $border;
                // fix bottom line alignment of last line before page break
                foreach ($dom[($dom[$key]['parent'])]['trids'] as $j => $trkey) {
                    // update row-spanned cells
                    if (isset($dom[($dom[$key]['parent'])]['rowspans'])) {
                        foreach ($dom[($dom[$key]['parent'])]['rowspans'] as $k => $trwsp) {
                            if (isset($prevtrkey) AND ($trwsp['trid'] == $prevtrkey) AND ($trwsp['mrowspan'] > 0)) {
                                $dom[($dom[$key]['parent'])]['rowspans'][$k]['trid'] = $trkey;
                            }
                            if ($dom[($dom[$key]['parent'])]['rowspans'][$k]['trid'] == $trkey) {
                                $dom[($dom[$key]['parent'])]['rowspans'][$k]['mrowspan'] -= 1;
                            }
                        }
                    }
                    if (isset($prevtrkey) AND ($dom[$trkey]['startpage'] > $dom[$prevtrkey]['endpage'])) {
                        $pgendy = $this->pagedim[$dom[$prevtrkey]['endpage']]['hk'] - $this->pagedim[$dom[$prevtrkey]['endpage']]['bm'];
                        $dom[$prevtrkey]['endy'] = $pgendy;
                        // update row-spanned cells
                        if (isset($dom[($dom[$key]['parent'])]['rowspans'])) {
                            foreach ($dom[($dom[$key]['parent'])]['rowspans'] as $k => $trwsp) {
                                if (($trwsp['trid'] == $prevtrkey) AND ($trwsp['mrowspan'] >= 0) AND ($trwsp['endpage'] == $dom[$prevtrkey]['endpage'])) {
                                    $dom[($dom[$key]['parent'])]['rowspans'][$k]['endy'] = $pgendy;
                                    $dom[($dom[$key]['parent'])]['rowspans'][$k]['mrowspan'] = -1;
                                }
                            }
                        }
                    }
                    $prevtrkey = $trkey;
                    $table_el = $dom[($dom[$key]['parent'])];
                }
                // for each row
                if (count($table_el['trids']) > 0) {
                    unset($xmax);
                }
                foreach ($table_el['trids'] as $j => $trkey) {
                    $parent = $dom[$trkey];
                    if (!isset($xmax)) {
                        $xmax = $parent['cellpos'][(count($parent['cellpos']) - 1)]['endx'];
                    }
                    // for each cell on the row
                    foreach ($parent['cellpos'] as $k => $cellpos) {
                        if (isset($cellpos['rowspanid']) AND ($cellpos['rowspanid'] >= 0)) {
                            $cellpos['startx'] = $table_el['rowspans'][($cellpos['rowspanid'])]['startx'];
                            $cellpos['endx'] = $table_el['rowspans'][($cellpos['rowspanid'])]['endx'];
                            $endy = $table_el['rowspans'][($cellpos['rowspanid'])]['endy'];
                            $startpage = $table_el['rowspans'][($cellpos['rowspanid'])]['startpage'];
                            $endpage = $table_el['rowspans'][($cellpos['rowspanid'])]['endpage'];
                            $startcolumn = $table_el['rowspans'][($cellpos['rowspanid'])]['startcolumn'];
                            $endcolumn = $table_el['rowspans'][($cellpos['rowspanid'])]['endcolumn'];
                        } else {
                            $endy = $parent['endy'];
                            $startpage = $parent['startpage'];
                            $endpage = $parent['endpage'];
                            $startcolumn = $parent['startcolumn'];
                            $endcolumn = $parent['endcolumn'];
                        }
                        if ($this->num_columns == 0) {
                            $this->num_columns = 1;
                        }
                        if (isset($cellpos['border'])) {
                            $border = $cellpos['border'];
                        }
                        if (isset($cellpos['bgcolor']) AND ($cellpos['bgcolor']) !== false) {
                            $this->SetFillColorArray($cellpos['bgcolor']);
                            $fill = true;
                        } else {
                            $fill = false;
                        }
                        $x = $cellpos['startx'];
                        $y = $parent['starty'];
                        $starty = $y;
                        $w = abs($cellpos['endx'] - $cellpos['startx']);
                        // get border modes
                        $border_start = TCPDF_STATIC::getBorderMode($border, $position='start', $this->opencell);
                        $border_end = TCPDF_STATIC::getBorderMode($border, $position='end', $this->opencell);
                        $border_middle = TCPDF_STATIC::getBorderMode($border, $position='middle', $this->opencell);
                        // design borders around HTML cells.
                        for ($page = $startpage; $page <= $endpage; ++$page) { // for each page
                            $ccode = '';
                            $this->setPage($page);
                            if ($this->num_columns < 2) {
                                // single-column mode
                                $this->x = $x;
                                $this->y = $this->tMargin;
                            }
                            // account for margin changes
                            if ($page > $startpage) {
                                if (($this->rtl) AND ($this->pagedim[$page]['orm'] != $this->pagedim[$startpage]['orm'])) {
                                    $this->x -= ($this->pagedim[$page]['orm'] - $this->pagedim[$startpage]['orm']);
                                } elseif ((!$this->rtl) AND ($this->pagedim[$page]['olm'] != $this->pagedim[$startpage]['olm'])) {
                                    $this->x += ($this->pagedim[$page]['olm'] - $this->pagedim[$startpage]['olm']);
                                }
                            }
                            if ($startpage == $endpage) { // single page
                                $deltacol = 0;
                                $deltath = 0;
                                for ($column = $startcolumn; $column <= $endcolumn; ++$column) { // for each column
                                    $this->selectColumn($column);
                                    if ($startcolumn == $endcolumn) { // single column
                                        $cborder = $border;
                                        $h = $endy - $parent['starty'];
                                        $this->y = $y;
                                        $this->x = $x;
                                    } elseif ($column == $startcolumn) { // first column
                                        $cborder = $border_start;
                                        $this->y = $starty;
                                        $this->x = $x;
                                        $h = $this->h - $this->y - $this->bMargin;
                                        if ($this->rtl) {
                                            $deltacol = $this->x + $this->rMargin - $this->w;
                                        } else {
                                            $deltacol = $this->x - $this->lMargin;
                                        }
                                    } elseif ($column == $endcolumn) { // end column
                                        $cborder = $border_end;
                                        if (isset($this->columns[$column]['th']['\''.$page.'\''])) {
                                            $this->y = $this->columns[$column]['th']['\''.$page.'\''];
                                        }
                                        $this->x += $deltacol;
                                        $h = $endy - $this->y;
                                    } else { // middle column
                                        $cborder = $border_middle;
                                        if (isset($this->columns[$column]['th']['\''.$page.'\''])) {
                                            $this->y = $this->columns[$column]['th']['\''.$page.'\''];
                                        }
                                        $this->x += $deltacol;
                                        $h = $this->h - $this->y - $this->bMargin;
                                    }
                                    $ccode .= $this->getCellCode($w, $h, '', $cborder, 1, '', $fill, '', 0, true)."\n";
                                } // end for each column
                            } elseif ($page == $startpage) { // first page
                                $deltacol = 0;
                                $deltath = 0;
                                for ($column = $startcolumn; $column < $this->num_columns; ++$column) { // for each column
                                    $this->selectColumn($column);
                                    if ($column == $startcolumn) { // first column
                                        $cborder = $border_start;
                                        $this->y = $starty;
                                        $this->x = $x;
                                        $h = $this->h - $this->y - $this->bMargin;
                                        if ($this->rtl) {
                                            $deltacol = $this->x + $this->rMargin - $this->w;
                                        } else {
                                            $deltacol = $this->x - $this->lMargin;
                                        }
                                    } else { // middle column
                                        $cborder = $border_middle;
                                        if (isset($this->columns[$column]['th']['\''.$page.'\''])) {
                                            $this->y = $this->columns[$column]['th']['\''.$page.'\''];
                                        }
                                        $this->x += $deltacol;
                                        $h = $this->h - $this->y - $this->bMargin;
                                    }
                                    $ccode .= $this->getCellCode($w, $h, '', $cborder, 1, '', $fill, '', 0, true)."\n";
                                } // end for each column
                            } elseif ($page == $endpage) { // last page
                                $deltacol = 0;
                                $deltath = 0;
                                for ($column = 0; $column <= $endcolumn; ++$column) { // for each column
                                    $this->selectColumn($column);
                                    if ($column == $endcolumn) { // end column
                                        $cborder = $border_end;
                                        if (isset($this->columns[$column]['th']['\''.$page.'\''])) {
                                            $this->y = $this->columns[$column]['th']['\''.$page.'\''];
                                        }
                                        $this->x += $deltacol;
                                        $h = $endy - $this->y;
                                    } else { // middle column
                                        $cborder = $border_middle;
                                        if (isset($this->columns[$column]['th']['\''.$page.'\''])) {
                                            $this->y = $this->columns[$column]['th']['\''.$page.'\''];
                                        }
                                        $this->x += $deltacol;
                                        $h = $this->h - $this->y - $this->bMargin;
                                    }
                                    $ccode .= $this->getCellCode($w, $h, '', $cborder, 1, '', $fill, '', 0, true)."\n";
                                } // end for each column
                            } else { // middle page
                                $deltacol = 0;
                                $deltath = 0;
                                for ($column = 0; $column < $this->num_columns; ++$column) { // for each column
                                    $this->selectColumn($column);
                                    $cborder = $border_middle;
                                    if (isset($this->columns[$column]['th']['\''.$page.'\''])) {
                                        $this->y = $this->columns[$column]['th']['\''.$page.'\''];
                                    }
                                    $this->x += $deltacol;
                                    $h = $this->h - $this->y - $this->bMargin;
                                    $ccode .= $this->getCellCode($w, $h, '', $cborder, 1, '', $fill, '', 0, true)."\n";
                                } // end for each column
                            }
                            if (!empty($cborder) OR !empty($fill)) {
                                $offsetlen = strlen($ccode);
                                // draw border and fill
                                if ($this->inxobj) {
                                    // we are inside an XObject template
                                    if (end($this->xobjects[$this->xobjid]['transfmrk']) !== false) {
                                        $pagemarkkey = key($this->xobjects[$this->xobjid]['transfmrk']);
                                        $pagemark = $this->xobjects[$this->xobjid]['transfmrk'][$pagemarkkey];
                                        $this->xobjects[$this->xobjid]['transfmrk'][$pagemarkkey] += $offsetlen;
                                    } else {
                                        $pagemark = $this->xobjects[$this->xobjid]['intmrk'];
                                        $this->xobjects[$this->xobjid]['intmrk'] += $offsetlen;
                                    }
                                    $pagebuff = $this->xobjects[$this->xobjid]['outdata'];
                                    $pstart = substr($pagebuff, 0, $pagemark);
                                    $pend = substr($pagebuff, $pagemark);
                                    $this->xobjects[$this->xobjid]['outdata'] = $pstart.$ccode.$pend;
                                } else {
                                    // draw border and fill
                                    if (end($this->transfmrk[$this->page]) !== false) {
                                        $pagemarkkey = key($this->transfmrk[$this->page]);
                                        $pagemark = $this->transfmrk[$this->page][$pagemarkkey];
                                    } elseif ($this->InFooter) {
                                        $pagemark = $this->footerpos[$this->page];
                                    } else {
                                        $pagemark = $this->intmrk[$this->page];
                                    }
                                    $pagebuff = $this->getPageBuffer($this->page);
                                    $pstart = substr($pagebuff, 0, $pagemark);
                                    $pend = substr($pagebuff, $pagemark);
                                    $this->setPageBuffer($this->page, $pstart.$ccode.$pend);
                                }
                            }
                        } // end for each page
                        // restore default border
                        $border = $default_border;
                    } // end for each cell on the row
                    if (isset($table_el['attribute']['cellspacing'])) {
                        $this->y += $this->getHTMLUnitToUnits($table_el['attribute']['cellspacing'], 1, 'px');
                    } elseif (isset($table_el['border-spacing'])) {
                        $this->y += $table_el['border-spacing']['V'];
                    }
                    $this->Ln(0, $cell);
                    $this->x = $parent['startx'];
                    if ($endpage > $startpage) {
                        if (($this->rtl) AND ($this->pagedim[$endpage]['orm'] != $this->pagedim[$startpage]['orm'])) {
                            $this->x += ($this->pagedim[$endpage]['orm'] - $this->pagedim[$startpage]['orm']);
                        } elseif ((!$this->rtl) AND ($this->pagedim[$endpage]['olm'] != $this->pagedim[$startpage]['olm'])) {
                            $this->x += ($this->pagedim[$endpage]['olm'] - $this->pagedim[$startpage]['olm']);
                        }
                    }
                }
                if (!$in_table_head) { // we are not inside a thead section
                    $this->cell_padding = $table_el['old_cell_padding'];
                    // reset row height
                    $this->resetLastH();
                    if (($this->page == ($this->numpages - 1)) AND ($this->pageopen[$this->numpages])) {
                        $plendiff = ($this->pagelen[$this->numpages] - $this->emptypagemrk[$this->numpages]);
                        if (($plendiff > 0) AND ($plendiff < 60)) {
                            $pagediff = substr($this->getPageBuffer($this->numpages), $this->emptypagemrk[$this->numpages], $plendiff);
                            if (substr($pagediff, 0, 5) == 'BT /F') {
                                // the difference is only a font setting
                                $plendiff = 0;
                            }
                        }
                        if ($plendiff == 0) {
                            // remove last blank page
                            $this->deletePage($this->numpages);
                        }
                    }
                    if (isset($this->theadMargins['top'])) {
                        // restore top margin
                        $this->tMargin = $this->theadMargins['top'];
                    }
                    if (!isset($table_el['attribute']['nested']) OR ($table_el['attribute']['nested'] != 'true')) {
                        // reset main table header
                        $this->thead = '';
                        $this->theadMargins = array();
                        $this->pagedim[$this->page]['tm'] = $this->tMargin;
                    }
                }
                $parent = $table_el;
                break;
            }
            case 'a': {
                $this->HREF = '';
                break;
            }
            case 'sup': {
                $this->SetXY($this->GetX(), $this->GetY() + ((0.7 * $parent['fontsize']) / $this->k));
                break;
            }
            case 'sub': {
                $this->SetXY($this->GetX(), $this->GetY() - ((0.3 * $parent['fontsize']) / $this->k));
                break;
            }
            case 'div': {
                $this->addHTMLVertSpace($hbz, $hb, $cell, false, $lasttag);
                break;
            }
            case 'blockquote': {
                if ($this->rtl) {
                    $this->rMargin -= $this->listindent;
                } else {
                    $this->lMargin -= $this->listindent;
                }
                --$this->listindentlevel;
                $this->addHTMLVertSpace($hbz, $hb, $cell, false, $lasttag);
                break;
            }
            case 'p': {
                $this->addHTMLVertSpace($hbz, $hb, $cell, false, $lasttag);
                break;
            }
            case 'pre': {
                $this->addHTMLVertSpace($hbz, $hb, $cell, false, $lasttag);
                $this->premode = false;
                break;
            }
            case 'dl': {
                --$this->listnum;
                if ($this->listnum <= 0) {
                    $this->listnum = 0;
                    $this->addHTMLVertSpace($hbz, $hb, $cell, false, $lasttag);
                } else {
                    $this->addHTMLVertSpace(0, 0, $cell, false, $lasttag);
                }
                $this->resetLastH();
                break;
            }
            case 'dt': {
                $this->lispacer = '';
                $this->addHTMLVertSpace(0, 0, $cell, false, $lasttag);
                break;
            }
            case 'dd': {
                $this->lispacer = '';
                if ($this->rtl) {
                    $this->rMargin -= $this->listindent;
                } else {
                    $this->lMargin -= $this->listindent;
                }
                --$this->listindentlevel;
                $this->addHTMLVertSpace(0, 0, $cell, false, $lasttag);
                break;
            }
            case 'ul':
            case 'ol': {
                --$this->listnum;
                $this->lispacer = '';
                if ($this->rtl) {
                    $this->rMargin -= $this->listindent;
                } else {
                    $this->lMargin -= $this->listindent;
                }
                --$this->listindentlevel;
                if ($this->listnum <= 0) {
                    $this->listnum = 0;
                    $this->addHTMLVertSpace($hbz, $hb, $cell, false, $lasttag);
                } else {
                    $this->addHTMLVertSpace(0, 0, $cell, false, $lasttag);
                }
                $this->resetLastH();
                break;
            }
            case 'li': {
                $this->lispacer = '';
                $this->addHTMLVertSpace(0, 0, $cell, false, $lasttag);
                break;
            }
            case 'h1':
            case 'h2':
            case 'h3':
            case 'h4':
            case 'h5':
            case 'h6': {
                $this->addHTMLVertSpace($hbz, $hb, $cell, false, $lasttag);
                break;
            }
            // Form fields (since 4.8.000 - 2009-09-07)
            case 'form': {
                $this->form_action = '';
                $this->form_enctype = 'application/x-www-form-urlencoded';
                break;
            }
            default : {
                break;
            }
        }
        // draw border and background (if any)
        $this->drawHTMLTagBorder($parent, $xmax);
        if (isset($dom[($dom[$key]['parent'])]['attribute']['pagebreakafter'])) {
            $pba = $dom[($dom[$key]['parent'])]['attribute']['pagebreakafter'];
            // check for pagebreak
            if (($pba == 'true') OR ($pba == 'left') OR ($pba == 'right')) {
                // add a page (or trig AcceptPageBreak() for multicolumn mode)
                $this->checkPageBreak($this->PageBreakTrigger + 1);
            }
            if ((($pba == 'left') AND (((!$this->rtl) AND (($this->page % 2) == 0)) OR (($this->rtl) AND (($this->page % 2) != 0))))
                OR (($pba == 'right') AND (((!$this->rtl) AND (($this->page % 2) != 0)) OR (($this->rtl) AND (($this->page % 2) == 0))))) {
                // add a page (or trig AcceptPageBreak() for multicolumn mode)
                $this->checkPageBreak($this->PageBreakTrigger + 1);
            }
        }
        $this->tmprtl = false;
        return $dom;
    }

    /**
     * Add vertical spaces if needed.
     * @param $hbz (string) Distance between current y and line bottom.
     * @param $hb (string) The height of the break.
     * @param $cell (boolean) if true add the default left (or right if RTL) padding to each new line (default false).
     * @param $firsttag (boolean) set to true when the tag is the first.
     * @param $lasttag (boolean) set to true when the tag is the last.
     * @protected
     */
    protected function addHTMLVertSpace($hbz=0, $hb=0, $cell=false, $firsttag=false, $lasttag=false) {
        if ($firsttag) {
            $this->Ln(0, $cell);
            $this->htmlvspace = 0;
            return;
        }
        if ($lasttag) {
            $this->Ln($hbz, $cell);
            $this->htmlvspace = 0;
            return;
        }
        if ($hb < $this->htmlvspace) {
            $hd = 0;
        } else {
            $hd = $hb - $this->htmlvspace;
            $this->htmlvspace = $hb;
        }
        $this->Ln(($hbz + $hd), $cell);
    }

    /**
     * Return the starting coordinates to draw an html border
     * @return array containing top-left border coordinates
     * @protected
     * @since 5.7.000 (2010-08-03)
     */
    protected function getBorderStartPosition() {
        if ($this->rtl) {
            $xmax = $this->lMargin;
        } else {
            $xmax = $this->w - $this->rMargin;
        }
        return array('page' => $this->page, 'column' => $this->current_column, 'x' => $this->x, 'y' => $this->y, 'xmax' => $xmax);
    }

    /**
     * Draw an HTML block border and fill
     * @param $tag (array) array of tag properties.
     * @param $xmax (int) end X coordinate for border.
     * @protected
     * @since 5.7.000 (2010-08-03)
     */
    protected function drawHTMLTagBorder($tag, $xmax) {
        if (!isset($tag['borderposition'])) {
            // nothing to draw
            return;
        }
        $prev_x = $this->x;
        $prev_y = $this->y;
        $prev_lasth = $this->lasth;
        $border = 0;
        $fill = false;
        $this->lasth = 0;
        if (isset($tag['border']) AND !empty($tag['border'])) {
            // get border style
            $border = $tag['border'];
            if (!TCPDF_STATIC::empty_string($this->thead) AND (!$this->inthead)) {
                // border for table header
                $border = TCPDF_STATIC::getBorderMode($border, $position='middle', $this->opencell);
            }
        }
        if (isset($tag['bgcolor']) AND ($tag['bgcolor'] !== false)) {
            // get background color
            $old_bgcolor = $this->bgcolor;
            $this->SetFillColorArray($tag['bgcolor']);
            $fill = true;
        }
        if (!$border AND !$fill) {
            // nothing to draw
            return;
        }
        if (isset($tag['attribute']['cellspacing'])) {
            $clsp = $this->getHTMLUnitToUnits($tag['attribute']['cellspacing'], 1, 'px');
            $cellspacing = array('H' => $clsp, 'V' => $clsp);
        } elseif (isset($tag['border-spacing'])) {
            $cellspacing = $tag['border-spacing'];
        } else {
            $cellspacing = array('H' => 0, 'V' => 0);
        }
        if (($tag['value'] != 'table') AND (is_array($border)) AND (!empty($border))) {
            // draw the border externally respect the sqare edge.
            $border['mode'] = 'ext';
        }
        if ($this->rtl) {
            if ($xmax >= $tag['borderposition']['x']) {
                $xmax = $tag['borderposition']['xmax'];
            }
            $w = ($tag['borderposition']['x'] - $xmax);
        } else {
            if ($xmax <= $tag['borderposition']['x']) {
                $xmax = $tag['borderposition']['xmax'];
            }
            $w = ($xmax - $tag['borderposition']['x']);
        }
        if ($w <= 0) {
            return;
        }
        $w += $cellspacing['H'];
        $startpage = $tag['borderposition']['page'];
        $startcolumn = $tag['borderposition']['column'];
        $x = $tag['borderposition']['x'];
        $y = $tag['borderposition']['y'];
        $endpage = $this->page;
        $starty = $tag['borderposition']['y'] - $cellspacing['V'];
        $currentY = $this->y;
        $this->x = $x;
        // get latest column
        $endcolumn = $this->current_column;
        if ($this->num_columns == 0) {
            $this->num_columns = 1;
        }
        // get border modes
        $border_start = TCPDF_STATIC::getBorderMode($border, $position='start', $this->opencell);
        $border_end = TCPDF_STATIC::getBorderMode($border, $position='end', $this->opencell);
        $border_middle = TCPDF_STATIC::getBorderMode($border, $position='middle', $this->opencell);
        // temporary disable page regions
        $temp_page_regions = $this->page_regions;
        $this->page_regions = array();
        // design borders around HTML cells.
        for ($page = $startpage; $page <= $endpage; ++$page) { // for each page
            $ccode = '';
            $this->setPage($page);
            if ($this->num_columns < 2) {
                // single-column mode
                $this->x = $x;
                $this->y = $this->tMargin;
            }
            // account for margin changes
            if ($page > $startpage) {
                if (($this->rtl) AND ($this->pagedim[$page]['orm'] != $this->pagedim[$startpage]['orm'])) {
                    $this->x -= ($this->pagedim[$page]['orm'] - $this->pagedim[$startpage]['orm']);
                } elseif ((!$this->rtl) AND ($this->pagedim[$page]['olm'] != $this->pagedim[$startpage]['olm'])) {
                    $this->x += ($this->pagedim[$page]['olm'] - $this->pagedim[$startpage]['olm']);
                }
            }
            if ($startpage == $endpage) {
                // single page
                for ($column = $startcolumn; $column <= $endcolumn; ++$column) { // for each column
                    $this->selectColumn($column);
                    if ($startcolumn == $endcolumn) { // single column
                        $cborder = $border;
                        $h = ($currentY - $y) + $cellspacing['V'];
                        $this->y = $starty;
                    } elseif ($column == $startcolumn) { // first column
                        $cborder = $border_start;
                        $this->y = $starty;
                        $h = $this->h - $this->y - $this->bMargin;
                    } elseif ($column == $endcolumn) { // end column
                        $cborder = $border_end;
                        $h = $currentY - $this->y;
                    } else { // middle column
                        $cborder = $border_middle;
                        $h = $this->h - $this->y - $this->bMargin;
                    }
                    $ccode .= $this->getCellCode($w, $h, '', $cborder, 1, '', $fill, '', 0, true)."\n";
                } // end for each column
            } elseif ($page == $startpage) { // first page
                for ($column = $startcolumn; $column < $this->num_columns; ++$column) { // for each column
                    $this->selectColumn($column);
                    if ($column == $startcolumn) { // first column
                        $cborder = $border_start;
                        $this->y = $starty;
                        $h = $this->h - $this->y - $this->bMargin;
                    } else { // middle column
                        $cborder = $border_middle;
                        $h = $this->h - $this->y - $this->bMargin;
                    }
                    $ccode .= $this->getCellCode($w, $h, '', $cborder, 1, '', $fill, '', 0, true)."\n";
                } // end for each column
            } elseif ($page == $endpage) { // last page
                for ($column = 0; $column <= $endcolumn; ++$column) { // for each column
                    $this->selectColumn($column);
                    if ($column == $endcolumn) {
                        // end column
                        $cborder = $border_end;
                        $h = $currentY - $this->y;
                    } else {
                        // middle column
                        $cborder = $border_middle;
                        $h = $this->h - $this->y - $this->bMargin;
                    }
                    $ccode .= $this->getCellCode($w, $h, '', $cborder, 1, '', $fill, '', 0, true)."\n";
                } // end for each column
            } else { // middle page
                for ($column = 0; $column < $this->num_columns; ++$column) { // for each column
                    $this->selectColumn($column);
                    $cborder = $border_middle;
                    $h = $this->h - $this->y - $this->bMargin;
                    $ccode .= $this->getCellCode($w, $h, '', $cborder, 1, '', $fill, '', 0, true)."\n";
                } // end for each column
            }
            if ($cborder OR $fill) {
                $offsetlen = strlen($ccode);
                // draw border and fill
                if ($this->inxobj) {
                    // we are inside an XObject template
                    if (end($this->xobjects[$this->xobjid]['transfmrk']) !== false) {
                        $pagemarkkey = key($this->xobjects[$this->xobjid]['transfmrk']);
                        $pagemark = $this->xobjects[$this->xobjid]['transfmrk'][$pagemarkkey];
                        $this->xobjects[$this->xobjid]['transfmrk'][$pagemarkkey] += $offsetlen;
                    } else {
                        $pagemark = $this->xobjects[$this->xobjid]['intmrk'];
                        $this->xobjects[$this->xobjid]['intmrk'] += $offsetlen;
                    }
                    $pagebuff = $this->xobjects[$this->xobjid]['outdata'];
                    $pstart = substr($pagebuff, 0, $pagemark);
                    $pend = substr($pagebuff, $pagemark);
                    $this->xobjects[$this->xobjid]['outdata'] = $pstart.$ccode.$pend;
                } else {
                    if (end($this->transfmrk[$this->page]) !== false) {
                        $pagemarkkey = key($this->transfmrk[$this->page]);
                        $pagemark = $this->transfmrk[$this->page][$pagemarkkey];
                    } elseif ($this->InFooter) {
                        $pagemark = $this->footerpos[$this->page];
                    } else {
                        $pagemark = $this->intmrk[$this->page];
                    }
                    $pagebuff = $this->getPageBuffer($this->page);
                    $pstart = substr($pagebuff, 0, $pagemark);
                    $pend = substr($pagebuff, $pagemark);
                    $this->setPageBuffer($this->page, $pstart.$ccode.$pend);
                    $this->bordermrk[$this->page] += $offsetlen;
                    $this->cntmrk[$this->page] += $offsetlen;
                }
            }
        } // end for each page
        // restore page regions
        $this->page_regions = $temp_page_regions;
        if (isset($old_bgcolor)) {
            // restore background color
            $this->SetFillColorArray($old_bgcolor);
        }
        // restore pointer position
        $this->x = $prev_x;
        $this->y = $prev_y;
        $this->lasth = $prev_lasth;
    }

    /**
     * Set the default bullet to be used as LI bullet symbol
     * @param $symbol (string) character or string to be used (legal values are: '' = automatic, '!' = auto bullet, '#' = auto numbering, 'disc', 'disc', 'circle', 'square', '1', 'decimal', 'decimal-leading-zero', 'i', 'lower-roman', 'I', 'upper-roman', 'a', 'lower-alpha', 'lower-latin', 'A', 'upper-alpha', 'upper-latin', 'lower-greek', 'img|type|width|height|image.ext')
     * @public
     * @since 4.0.028 (2008-09-26)
     */
    public function setLIsymbol($symbol='!') {
        // check for custom image symbol
        if (substr($symbol, 0, 4) == 'img|') {
            $this->lisymbol = $symbol;
            return;
        }
        $symbol = strtolower($symbol);
        $valid_symbols = array('!', '#', 'disc', 'circle', 'square', '1', 'decimal', 'decimal-leading-zero', 'i', 'lower-roman', 'I', 'upper-roman', 'a', 'lower-alpha', 'lower-latin', 'A', 'upper-alpha', 'upper-latin', 'lower-greek');
        if (in_array($symbol, $valid_symbols)) {
            $this->lisymbol = $symbol;
        } else {
            $this->lisymbol = '';
        }
    }

    /**
     * Set the booklet mode for double-sided pages.
     * @param $booklet (boolean) true set the booklet mode on, false otherwise.
     * @param $inner (float) Inner page margin.
     * @param $outer (float) Outer page margin.
     * @public
     * @since 4.2.000 (2008-10-29)
     */
    public function SetBooklet($booklet=true, $inner=-1, $outer=-1) {
        $this->booklet = $booklet;
        if ($inner >= 0) {
            $this->lMargin = $inner;
        }
        if ($outer >= 0) {
            $this->rMargin = $outer;
        }
    }

    /**
     * Swap the left and right margins.
     * @param $reverse (boolean) if true swap left and right margins.
     * @protected
     * @since 4.2.000 (2008-10-29)
     */
    protected function swapMargins($reverse=true) {
        if ($reverse) {
            // swap left and right margins
            $mtemp = $this->original_lMargin;
            $this->original_lMargin = $this->original_rMargin;
            $this->original_rMargin = $mtemp;
            $deltam = $this->original_lMargin - $this->original_rMargin;
            $this->lMargin += $deltam;
            $this->rMargin -= $deltam;
        }
    }

    /**
     * Set the vertical spaces for HTML tags.
     * The array must have the following structure (example):
     * $tagvs = array('h1' => array(0 => array('h' => '', 'n' => 2), 1 => array('h' => 1.3, 'n' => 1)));
     * The first array level contains the tag names,
     * the second level contains 0 for opening tags or 1 for closing tags,
     * the third level contains the vertical space unit (h) and the number spaces to add (n).
     * If the h parameter is not specified, default values are used.
     * @param $tagvs (array) array of tags and relative vertical spaces.
     * @public
     * @since 4.2.001 (2008-10-30)
     */
    public function setHtmlVSpace($tagvs) {
        $this->tagvspaces = $tagvs;
    }

    /**
     * Set custom width for list indentation.
     * @param $width (float) width of the indentation. Use negative value to disable it.
     * @public
     * @since 4.2.007 (2008-11-12)
     */
    public function setListIndentWidth($width) {
        return $this->customlistindent = floatval($width);
    }

    /**
     * Set the top/bottom cell sides to be open or closed when the cell cross the page.
     * @param $isopen (boolean) if true keeps the top/bottom border open for the cell sides that cross the page.
     * @public
     * @since 4.2.010 (2008-11-14)
     */
    public function setOpenCell($isopen) {
        $this->opencell = $isopen;
    }

    /**
     * Set the color and font style for HTML links.
     * @param $color (array) RGB array of colors
     * @param $fontstyle (string) additional font styles to add
     * @public
     * @since 4.4.003 (2008-12-09)
     */
    public function setHtmlLinksStyle($color=array(0,0,255), $fontstyle='U') {
        $this->htmlLinkColorArray = $color;
        $this->htmlLinkFontStyle = $fontstyle;
    }

    /**
     * Convert HTML string containing value and unit of measure to user's units or points.
     * @param $htmlval (string) String containing values and unit.
     * @param $refsize (string) Reference value in points.
     * @param $defaultunit (string) Default unit (can be one of the following: %, em, ex, px, in, mm, pc, pt).
     * @param $points (boolean) If true returns points, otherwise returns value in user's units.
     * @return float value in user's unit or point if $points=true
     * @public
     * @since 4.4.004 (2008-12-10)
     */
    public function getHTMLUnitToUnits($htmlval, $refsize=1, $defaultunit='px', $points=false) {
        $supportedunits = array('%', 'em', 'ex', 'px', 'in', 'cm', 'mm', 'pc', 'pt');
        $retval = 0;
        $value = 0;
        $unit = 'px';
        if ($points) {
            $k = 1;
        } else {
            $k = $this->k;
        }
        if (in_array($defaultunit, $supportedunits)) {
            $unit = $defaultunit;
        }
        if (is_numeric($htmlval)) {
            $value = floatval($htmlval);
        } elseif (preg_match('/([0-9\.\-\+]+)/', $htmlval, $mnum)) {
            $value = floatval($mnum[1]);
            if (preg_match('/([a-z%]+)/', $htmlval, $munit)) {
                if (in_array($munit[1], $supportedunits)) {
                    $unit = $munit[1];
                }
            }
        }
        switch ($unit) {
            // percentage
            case '%': {
                $retval = (($value * $refsize) / 100);
                break;
            }
            // relative-size
            case 'em': {
                $retval = ($value * $refsize);
                break;
            }
            // height of lower case 'x' (about half the font-size)
            case 'ex': {
                $retval = ($value * ($refsize / 2));
                break;
            }
            // absolute-size
            case 'in': {
                $retval = (($value * $this->dpi) / $k);
                break;
            }
            // centimeters
            case 'cm': {
                $retval = (($value / 2.54 * $this->dpi) / $k);
                break;
            }
            // millimeters
            case 'mm': {
                $retval = (($value / 25.4 * $this->dpi) / $k);
                break;
            }
            // one pica is 12 points
            case 'pc': {
                $retval = (($value * 12) / $k);
                break;
            }
            // points
            case 'pt': {
                $retval = ($value / $k);
                break;
            }
            // pixels
            case 'px': {
                $retval = $this->pixelsToUnits($value);
                if ($points) {
                    $retval *= $this->k;
                }
                break;
            }
        }
        return $retval;
    }

    /**
     * Output an HTML list bullet or ordered item symbol
     * @param $listdepth (int) list nesting level
     * @param $listtype (string) type of list
     * @param $size (float) current font size
     * @protected
     * @since 4.4.004 (2008-12-10)
     */
    protected function putHtmlListBullet($listdepth, $listtype='', $size=10) {
        if ($this->state != 2) {
            return;
        }
        $size /= $this->k;
        $fill = '';
        $bgcolor = $this->bgcolor;
        $color = $this->fgcolor;
        $strokecolor = $this->strokecolor;
        $width = 0;
        $textitem = '';
        $tmpx = $this->x;
        $lspace = $this->GetStringWidth('  ');
        if ($listtype == '^') {
            // special symbol used for avoid justification of rect bullet
            $this->lispacer = '';
            return;
        } elseif ($listtype == '!') {
            // set default list type for unordered list
            $deftypes = array('disc', 'circle', 'square');
            $listtype = $deftypes[($listdepth - 1) % 3];
        } elseif ($listtype == '#') {
            // set default list type for ordered list
            $listtype = 'decimal';
        } elseif (substr($listtype, 0, 4) == 'img|') {
            // custom image type ('img|type|width|height|image.ext')
            $img = explode('|', $listtype);
            $listtype = 'img';
        }
        switch ($listtype) {
            // unordered types
            case 'none': {
                break;
            }
            case 'disc': {
                $r = $size / 6;
                $lspace += (2 * $r);
                if ($this->rtl) {
                    $this->x += $lspace;
                } else {
                    $this->x -= $lspace;
                }
                $this->Circle(($this->x + $r), ($this->y + ($this->lasth / 2)), $r, 0, 360, 'F', array(), $color, 8);
                break;
            }
            case 'circle': {
                $r = $size / 6;
                $lspace += (2 * $r);
                if ($this->rtl) {
                    $this->x += $lspace;
                } else {
                    $this->x -= $lspace;
                }
                $prev_line_style = $this->linestyleWidth.' '.$this->linestyleCap.' '.$this->linestyleJoin.' '.$this->linestyleDash.' '.$this->DrawColor;
                $new_line_style = array('width' => ($r / 3), 'cap' => 'butt', 'join' => 'miter', 'dash' => 0, 'phase' => 0, 'color'=>$color);
                $this->Circle(($this->x + $r), ($this->y + ($this->lasth / 2)), ($r * (1 - (1/6))), 0, 360, 'D', $new_line_style, array(), 8);
                $this->_out($prev_line_style); // restore line settings
                break;
            }
            case 'square': {
                $l = $size / 3;
                $lspace += $l;
                if ($this->rtl) {;
                    $this->x += $lspace;
                } else {
                    $this->x -= $lspace;
                }
                $this->Rect($this->x, ($this->y + (($this->lasth - $l) / 2)), $l, $l, 'F', array(), $color);
                break;
            }
            case 'img': {
                // 1=>type, 2=>width, 3=>height, 4=>image.ext
                $lspace += $img[2];
                if ($this->rtl) {;
                    $this->x += $lspace;
                } else {
                    $this->x -= $lspace;
                }
                $imgtype = strtolower($img[1]);
                $prev_y = $this->y;
                switch ($imgtype) {
                    case 'svg': {
                        $this->ImageSVG($img[4], $this->x, ($this->y + (($this->lasth - $img[3]) / 2)), $img[2], $img[3], '', 'T', '', 0, false);
                        break;
                    }
                    case 'ai':
                    case 'eps': {
                        $this->ImageEps($img[4], $this->x, ($this->y + (($this->lasth - $img[3]) / 2)), $img[2], $img[3], '', true, 'T', '', 0, false);
                        break;
                    }
                    default: {
                        $this->Image($img[4], $this->x, ($this->y + (($this->lasth - $img[3]) / 2)), $img[2], $img[3], $img[1], '', 'T', false, 300, '', false, false, 0, false, false, false);
                        break;
                    }
                }
                $this->y = $prev_y;
                break;
            }
            // ordered types
            // $this->listcount[$this->listnum];
            // $textitem
            case '1':
            case 'decimal': {
                $textitem = $this->listcount[$this->listnum];
                break;
            }
            case 'decimal-leading-zero': {
                $textitem = sprintf('%02d', $this->listcount[$this->listnum]);
                break;
            }
            case 'i':
            case 'lower-roman': {
                $textitem = strtolower(TCPDF_STATIC::intToRoman($this->listcount[$this->listnum]));
                break;
            }
            case 'I':
            case 'upper-roman': {
                $textitem = TCPDF_STATIC::intToRoman($this->listcount[$this->listnum]);
                break;
            }
            case 'a':
            case 'lower-alpha':
            case 'lower-latin': {
                $textitem = chr(97 + $this->listcount[$this->listnum] - 1);
                break;
            }
            case 'A':
            case 'upper-alpha':
            case 'upper-latin': {
                $textitem = chr(65 + $this->listcount[$this->listnum] - 1);
                break;
            }
            case 'lower-greek': {
                $textitem = TCPDF_FONTS::unichr((945 + $this->listcount[$this->listnum] - 1), $this->isunicode);
                break;
            }
            /*
            // Types to be implemented (special handling)
            case 'hebrew': {
                break;
            }
            case 'armenian': {
                break;
            }
            case 'georgian': {
                break;
            }
            case 'cjk-ideographic': {
                break;
            }
            case 'hiragana': {
                break;
            }
            case 'katakana': {
                break;
            }
            case 'hiragana-iroha': {
                break;
            }
            case 'katakana-iroha': {
                break;
            }
            */
            default: {
                $textitem = $this->listcount[$this->listnum];
            }
        }
        if (!TCPDF_STATIC::empty_string($textitem)) {
            // Check whether we need a new page or new column
            $prev_y = $this->y;
            $h = $this->getCellHeight($this->FontSize);
            if ($this->checkPageBreak($h) OR ($this->y < $prev_y)) {
                $tmpx = $this->x;
            }
            // print ordered item
            if ($this->rtl) {
                $textitem = '.'.$textitem;
            } else {
                $textitem = $textitem.'.';
            }
            $lspace += $this->GetStringWidth($textitem);
            if ($this->rtl) {
                $this->x += $lspace;
            } else {
                $this->x -= $lspace;
            }
            $this->Write($this->lasth, $textitem, '', false, '', false, 0, false);
        }
        $this->x = $tmpx;
        $this->lispacer = '^';
        // restore colors
        $this->SetFillColorArray($bgcolor);
        $this->SetDrawColorArray($strokecolor);
        $this->SettextColorArray($color);
    }

    /**
     * Returns current graphic variables as array.
     * @return array of graphic variables
     * @protected
     * @since 4.2.010 (2008-11-14)
     */
    protected function getGraphicVars() {
        $grapvars = array(
            'FontFamily' => $this->FontFamily,
            'FontStyle' => $this->FontStyle,
            'FontSizePt' => $this->FontSizePt,
            'rMargin' => $this->rMargin,
            'lMargin' => $this->lMargin,
            'cell_padding' => $this->cell_padding,
            'cell_margin' => $this->cell_margin,
            'LineWidth' => $this->LineWidth,
            'linestyleWidth' => $this->linestyleWidth,
            'linestyleCap' => $this->linestyleCap,
            'linestyleJoin' => $this->linestyleJoin,
            'linestyleDash' => $this->linestyleDash,
            'textrendermode' => $this->textrendermode,
            'textstrokewidth' => $this->textstrokewidth,
            'DrawColor' => $this->DrawColor,
            'FillColor' => $this->FillColor,
            'TextColor' => $this->TextColor,
            'ColorFlag' => $this->ColorFlag,
            'bgcolor' => $this->bgcolor,
            'fgcolor' => $this->fgcolor,
            'htmlvspace' => $this->htmlvspace,
            'listindent' => $this->listindent,
            'listindentlevel' => $this->listindentlevel,
            'listnum' => $this->listnum,
            'listordered' => $this->listordered,
            'listcount' => $this->listcount,
            'lispacer' => $this->lispacer,
            'cell_height_ratio' => $this->cell_height_ratio,
            'font_stretching' => $this->font_stretching,
            'font_spacing' => $this->font_spacing,
            'alpha' => $this->alpha,
            // extended
            'lasth' => $this->lasth,
            'tMargin' => $this->tMargin,
            'bMargin' => $this->bMargin,
            'AutoPageBreak' => $this->AutoPageBreak,
            'PageBreakTrigger' => $this->PageBreakTrigger,
            'x' => $this->x,
            'y' => $this->y,
            'w' => $this->w,
            'h' => $this->h,
            'wPt' => $this->wPt,
            'hPt' => $this->hPt,
            'fwPt' => $this->fwPt,
            'fhPt' => $this->fhPt,
            'page' => $this->page,
            'current_column' => $this->current_column,
            'num_columns' => $this->num_columns
            );
        return $grapvars;
    }

    /**
     * Set graphic variables.
     * @param $gvars (array) array of graphic variablesto restore
     * @param $extended (boolean) if true restore extended graphic variables
     * @protected
     * @since 4.2.010 (2008-11-14)
     */
    protected function setGraphicVars($gvars, $extended=false) {
        if ($this->state != 2) {
             return;
        }
        $this->FontFamily = $gvars['FontFamily'];
        $this->FontStyle = $gvars['FontStyle'];
        $this->FontSizePt = $gvars['FontSizePt'];
        $this->rMargin = $gvars['rMargin'];
        $this->lMargin = $gvars['lMargin'];
        $this->cell_padding = $gvars['cell_padding'];
        $this->cell_margin = $gvars['cell_margin'];
        $this->LineWidth = $gvars['LineWidth'];
        $this->linestyleWidth = $gvars['linestyleWidth'];
        $this->linestyleCap = $gvars['linestyleCap'];
        $this->linestyleJoin = $gvars['linestyleJoin'];
        $this->linestyleDash = $gvars['linestyleDash'];
        $this->textrendermode = $gvars['textrendermode'];
        $this->textstrokewidth = $gvars['textstrokewidth'];
        $this->DrawColor = $gvars['DrawColor'];
        $this->FillColor = $gvars['FillColor'];
        $this->TextColor = $gvars['TextColor'];
        $this->ColorFlag = $gvars['ColorFlag'];
        $this->bgcolor = $gvars['bgcolor'];
        $this->fgcolor = $gvars['fgcolor'];
        $this->htmlvspace = $gvars['htmlvspace'];
        $this->listindent = $gvars['listindent'];
        $this->listindentlevel = $gvars['listindentlevel'];
        $this->listnum = $gvars['listnum'];
        $this->listordered = $gvars['listordered'];
        $this->listcount = $gvars['listcount'];
        $this->lispacer = $gvars['lispacer'];
        $this->cell_height_ratio = $gvars['cell_height_ratio'];
        $this->font_stretching = $gvars['font_stretching'];
        $this->font_spacing = $gvars['font_spacing'];
        $this->alpha = $gvars['alpha'];
        if ($extended) {
            // restore extended values
            $this->lasth = $gvars['lasth'];
            $this->tMargin = $gvars['tMargin'];
            $this->bMargin = $gvars['bMargin'];
            $this->AutoPageBreak = $gvars['AutoPageBreak'];
            $this->PageBreakTrigger = $gvars['PageBreakTrigger'];
            $this->x = $gvars['x'];
            $this->y = $gvars['y'];
            $this->w = $gvars['w'];
            $this->h = $gvars['h'];
            $this->wPt = $gvars['wPt'];
            $this->hPt = $gvars['hPt'];
            $this->fwPt = $gvars['fwPt'];
            $this->fhPt = $gvars['fhPt'];
            $this->page = $gvars['page'];
            $this->current_column = $gvars['current_column'];
            $this->num_columns = $gvars['num_columns'];
        }
        $this->_out(''.$this->linestyleWidth.' '.$this->linestyleCap.' '.$this->linestyleJoin.' '.$this->linestyleDash.' '.$this->DrawColor.' '.$this->FillColor.'');
        if (!TCPDF_STATIC::empty_string($this->FontFamily)) {
            $this->SetFont($this->FontFamily, $this->FontStyle, $this->FontSizePt);
        }
    }

    /**
     * Outputs the "save graphics state" operator 'q'
     * @protected
     */
    protected function _outSaveGraphicsState() {
        $this->_out('q');
    }

    /**
     * Outputs the "restore graphics state" operator 'Q'
     * @protected
     */
    protected function _outRestoreGraphicsState() {
        $this->_out('Q');
    }

    /**
     * Set buffer content (always append data).
     * @param $data (string) data
     * @protected
     * @since 4.5.000 (2009-01-02)
     */
    protected function setBuffer($data) {
        $this->bufferlen += strlen($data);
        $this->buffer .= $data;
    }

    /**
     * Replace the buffer content
     * @param $data (string) data
     * @protected
     * @since 5.5.000 (2010-06-22)
     */
    protected function replaceBuffer($data) {
        $this->bufferlen = strlen($data);
        $this->buffer = $data;
    }

    /**
     * Get buffer content.
     * @return string buffer content
     * @protected
     * @since 4.5.000 (2009-01-02)
     */
    protected function getBuffer() {
        return $this->buffer;
    }

    /**
     * Set page buffer content.
     * @param $page (int) page number
     * @param $data (string) page data
     * @param $append (boolean) if true append data, false replace.
     * @protected
     * @since 4.5.000 (2008-12-31)
     */
    protected function setPageBuffer($page, $data, $append=false) {
        if ($append) {
            $this->pages[$page] .= $data;
        } else {
            $this->pages[$page] = $data;
        }
        if ($append AND isset($this->pagelen[$page])) {
            $this->pagelen[$page] += strlen($data);
        } else {
            $this->pagelen[$page] = strlen($data);
        }
    }

    /**
     * Get page buffer content.
     * @param $page (int) page number
     * @return string page buffer content or false in case of error
     * @protected
     * @since 4.5.000 (2008-12-31)
     */
    protected function getPageBuffer($page) {
        if (isset($this->pages[$page])) {
            return $this->pages[$page];
        }
        return false;
    }

    /**
     * Set image buffer content.
     * @param $image (string) image key
     * @param $data (array) image data
     * @return int image index number
     * @protected
     * @since 4.5.000 (2008-12-31)
     */
    protected function setImageBuffer($image, $data) {
        if (($data['i'] = array_search($image, $this->imagekeys)) === FALSE) {
            $this->imagekeys[$this->numimages] = $image;
            $data['i'] = $this->numimages;
            ++$this->numimages;
        }
        $this->images[$image] = $data;
        return $data['i'];
    }

    /**
     * Set image buffer content for a specified sub-key.
     * @param $image (string) image key
     * @param $key (string) image sub-key
     * @param $data (array) image data
     * @protected
     * @since 4.5.000 (2008-12-31)
     */
    protected function setImageSubBuffer($image, $key, $data) {
        if (!isset($this->images[$image])) {
            $this->setImageBuffer($image, array());
        }
        $this->images[$image][$key] = $data;
    }

    /**
     * Get image buffer content.
     * @param $image (string) image key
     * @return string image buffer content or false in case of error
     * @protected
     * @since 4.5.000 (2008-12-31)
     */
    protected function getImageBuffer($image) {
        if (isset($this->images[$image])) {
            return $this->images[$image];
        }
        return false;
    }

    /**
     * Set font buffer content.
     * @param $font (string) font key
     * @param $data (array) font data
     * @protected
     * @since 4.5.000 (2009-01-02)
     */
    protected function setFontBuffer($font, $data) {
        $this->fonts[$font] = $data;
        if (!in_array($font, $this->fontkeys)) {
            $this->fontkeys[] = $font;
            // store object ID for current font
            ++$this->n;
            $this->font_obj_ids[$font] = $this->n;
            $this->setFontSubBuffer($font, 'n', $this->n);
        }
    }

    /**
     * Set font buffer content.
     * @param $font (string) font key
     * @param $key (string) font sub-key
     * @param $data (array) font data
     * @protected
     * @since 4.5.000 (2009-01-02)
     */
    protected function setFontSubBuffer($font, $key, $data) {
        if (!isset($this->fonts[$font])) {
            $this->setFontBuffer($font, array());
        }
        $this->fonts[$font][$key] = $data;
    }

    /**
     * Get font buffer content.
     * @param $font (string) font key
     * @return string font buffer content or false in case of error
     * @protected
     * @since 4.5.000 (2009-01-02)
     */
    protected function getFontBuffer($font) {
        if (isset($this->fonts[$font])) {
            return $this->fonts[$font];
        }
        return false;
    }

    /**
     * Move a page to a previous position.
     * @param $frompage (int) number of the source page
     * @param $topage (int) number of the destination page (must be less than $frompage)
     * @return true in case of success, false in case of error.
     * @public
     * @since 4.5.000 (2009-01-02)
     */
    public function movePage($frompage, $topage) {
        if (($frompage > $this->numpages) OR ($frompage <= $topage)) {
            return false;
        }
        if ($frompage == $this->page) {
            // close the page before moving it
            $this->endPage();
        }
        // move all page-related states
        $tmppage = $this->getPageBuffer($frompage);
        $tmppagedim = $this->pagedim[$frompage];
        $tmppagelen = $this->pagelen[$frompage];
        $tmpintmrk = $this->intmrk[$frompage];
        $tmpbordermrk = $this->bordermrk[$frompage];
        $tmpcntmrk = $this->cntmrk[$frompage];
        $tmppageobjects = $this->pageobjects[$frompage];
        if (isset($this->footerpos[$frompage])) {
            $tmpfooterpos = $this->footerpos[$frompage];
        }
        if (isset($this->footerlen[$frompage])) {
            $tmpfooterlen = $this->footerlen[$frompage];
        }
        if (isset($this->transfmrk[$frompage])) {
            $tmptransfmrk = $this->transfmrk[$frompage];
        }
        if (isset($this->PageAnnots[$frompage])) {
            $tmpannots = $this->PageAnnots[$frompage];
        }
        if (isset($this->newpagegroup) AND !empty($this->newpagegroup)) {
            for ($i = $frompage; $i > $topage; --$i) {
                if (isset($this->newpagegroup[$i]) AND (($i + $this->pagegroups[$this->newpagegroup[$i]]) > $frompage)) {
                    --$this->pagegroups[$this->newpagegroup[$i]];
                    break;
                }
            }
            for ($i = $topage; $i > 0; --$i) {
                if (isset($this->newpagegroup[$i]) AND (($i + $this->pagegroups[$this->newpagegroup[$i]]) > $topage)) {
                    ++$this->pagegroups[$this->newpagegroup[$i]];
                    break;
                }
            }
        }
        for ($i = $frompage; $i > $topage; --$i) {
            $j = $i - 1;
            // shift pages down
            $this->setPageBuffer($i, $this->getPageBuffer($j));
            $this->pagedim[$i] = $this->pagedim[$j];
            $this->pagelen[$i] = $this->pagelen[$j];
            $this->intmrk[$i] = $this->intmrk[$j];
            $this->bordermrk[$i] = $this->bordermrk[$j];
            $this->cntmrk[$i] = $this->cntmrk[$j];
            $this->pageobjects[$i] = $this->pageobjects[$j];
            if (isset($this->footerpos[$j])) {
                $this->footerpos[$i] = $this->footerpos[$j];
            } elseif (isset($this->footerpos[$i])) {
                unset($this->footerpos[$i]);
            }
            if (isset($this->footerlen[$j])) {
                $this->footerlen[$i] = $this->footerlen[$j];
            } elseif (isset($this->footerlen[$i])) {
                unset($this->footerlen[$i]);
            }
            if (isset($this->transfmrk[$j])) {
                $this->transfmrk[$i] = $this->transfmrk[$j];
            } elseif (isset($this->transfmrk[$i])) {
                unset($this->transfmrk[$i]);
            }
            if (isset($this->PageAnnots[$j])) {
                $this->PageAnnots[$i] = $this->PageAnnots[$j];
            } elseif (isset($this->PageAnnots[$i])) {
                unset($this->PageAnnots[$i]);
            }
            if (isset($this->newpagegroup[$j])) {
                $this->newpagegroup[$i] = $this->newpagegroup[$j];
                unset($this->newpagegroup[$j]);
            }
            if ($this->currpagegroup == $j) {
                $this->currpagegroup = $i;
            }
        }
        $this->setPageBuffer($topage, $tmppage);
        $this->pagedim[$topage] = $tmppagedim;
        $this->pagelen[$topage] = $tmppagelen;
        $this->intmrk[$topage] = $tmpintmrk;
        $this->bordermrk[$topage] = $tmpbordermrk;
        $this->cntmrk[$topage] = $tmpcntmrk;
        $this->pageobjects[$topage] = $tmppageobjects;
        if (isset($tmpfooterpos)) {
            $this->footerpos[$topage] = $tmpfooterpos;
        } elseif (isset($this->footerpos[$topage])) {
            unset($this->footerpos[$topage]);
        }
        if (isset($tmpfooterlen)) {
            $this->footerlen[$topage] = $tmpfooterlen;
        } elseif (isset($this->footerlen[$topage])) {
            unset($this->footerlen[$topage]);
        }
        if (isset($tmptransfmrk)) {
            $this->transfmrk[$topage] = $tmptransfmrk;
        } elseif (isset($this->transfmrk[$topage])) {
            unset($this->transfmrk[$topage]);
        }
        if (isset($tmpannots)) {
            $this->PageAnnots[$topage] = $tmpannots;
        } elseif (isset($this->PageAnnots[$topage])) {
            unset($this->PageAnnots[$topage]);
        }
        // adjust outlines
        $tmpoutlines = $this->outlines;
        foreach ($tmpoutlines as $key => $outline) {
            if (!$outline['f']) {
                if (($outline['p'] >= $topage) AND ($outline['p'] < $frompage)) {
                    $this->outlines[$key]['p'] = ($outline['p'] + 1);
                } elseif ($outline['p'] == $frompage) {
                    $this->outlines[$key]['p'] = $topage;
                }
            }
        }
        // adjust dests
        $tmpdests = $this->dests;
        foreach ($tmpdests as $key => $dest) {
            if (!$dest['f']) {
                if (($dest['p'] >= $topage) AND ($dest['p'] < $frompage)) {
                    $this->dests[$key]['p'] = ($dest['p'] + 1);
                } elseif ($dest['p'] == $frompage) {
                    $this->dests[$key]['p'] = $topage;
                }
            }
        }
        // adjust links
        $tmplinks = $this->links;
        foreach ($tmplinks as $key => $link) {
            if (!$link['f']) {
                if (($link['p'] >= $topage) AND ($link['p'] < $frompage)) {
                    $this->links[$key]['p'] = ($link['p'] + 1);
                } elseif ($link['p'] == $frompage) {
                    $this->links[$key]['p'] = $topage;
                }
            }
        }
        // adjust javascript
        $jfrompage = $frompage;
        $jtopage = $topage;
        if (preg_match_all('/this\.addField\(\'([^\']*)\',\'([^\']*)\',([0-9]+)/', $this->javascript, $pamatch) > 0) {
            foreach($pamatch[0] as $pk => $pmatch) {
                $pagenum = intval($pamatch[3][$pk]) + 1;
                if (($pagenum >= $jtopage) AND ($pagenum < $jfrompage)) {
                    $newpage = ($pagenum + 1);
                } elseif ($pagenum == $jfrompage) {
                    $newpage = $jtopage;
                } else {
                    $newpage = $pagenum;
                }
                --$newpage;
                $newjs = "this.addField(\'".$pamatch[1][$pk]."\',\'".$pamatch[2][$pk]."\',".$newpage;
                $this->javascript = str_replace($pmatch, $newjs, $this->javascript);
            }
            unset($pamatch);
        }
        // return to last page
        $this->lastPage(true);
        return true;
    }

    /**
     * Remove the specified page.
     * @param $page (int) page to remove
     * @return true in case of success, false in case of error.
     * @public
     * @since 4.6.004 (2009-04-23)
     */
    public function deletePage($page) {
        if (($page < 1) OR ($page > $this->numpages)) {
            return false;
        }
        // delete current page
        unset($this->pages[$page]);
        unset($this->pagedim[$page]);
        unset($this->pagelen[$page]);
        unset($this->intmrk[$page]);
        unset($this->bordermrk[$page]);
        unset($this->cntmrk[$page]);
        foreach ($this->pageobjects[$page] as $oid) {
            if (isset($this->offsets[$oid])){
                unset($this->offsets[$oid]);
            }
        }
        unset($this->pageobjects[$page]);
        if (isset($this->footerpos[$page])) {
            unset($this->footerpos[$page]);
        }
        if (isset($this->footerlen[$page])) {
            unset($this->footerlen[$page]);
        }
        if (isset($this->transfmrk[$page])) {
            unset($this->transfmrk[$page]);
        }
        if (isset($this->PageAnnots[$page])) {
            unset($this->PageAnnots[$page]);
        }
        if (isset($this->newpagegroup) AND !empty($this->newpagegroup)) {
            for ($i = $page; $i > 0; --$i) {
                if (isset($this->newpagegroup[$i]) AND (($i + $this->pagegroups[$this->newpagegroup[$i]]) > $page)) {
                    --$this->pagegroups[$this->newpagegroup[$i]];
                    break;
                }
            }
        }
        if (isset($this->pageopen[$page])) {
            unset($this->pageopen[$page]);
        }
        if ($page < $this->numpages) {
            // update remaining pages
            for ($i = $page; $i < $this->numpages; ++$i) {
                $j = $i + 1;
                // shift pages
                $this->setPageBuffer($i, $this->getPageBuffer($j));
                $this->pagedim[$i] = $this->pagedim[$j];
                $this->pagelen[$i] = $this->pagelen[$j];
                $this->intmrk[$i] = $this->intmrk[$j];
                $this->bordermrk[$i] = $this->bordermrk[$j];
                $this->cntmrk[$i] = $this->cntmrk[$j];
                $this->pageobjects[$i] = $this->pageobjects[$j];
                if (isset($this->footerpos[$j])) {
                    $this->footerpos[$i] = $this->footerpos[$j];
                } elseif (isset($this->footerpos[$i])) {
                    unset($this->footerpos[$i]);
                }
                if (isset($this->footerlen[$j])) {
                    $this->footerlen[$i] = $this->footerlen[$j];
                } elseif (isset($this->footerlen[$i])) {
                    unset($this->footerlen[$i]);
                }
                if (isset($this->transfmrk[$j])) {
                    $this->transfmrk[$i] = $this->transfmrk[$j];
                } elseif (isset($this->transfmrk[$i])) {
                    unset($this->transfmrk[$i]);
                }
                if (isset($this->PageAnnots[$j])) {
                    $this->PageAnnots[$i] = $this->PageAnnots[$j];
                } elseif (isset($this->PageAnnots[$i])) {
                    unset($this->PageAnnots[$i]);
                }
                if (isset($this->newpagegroup[$j])) {
                    $this->newpagegroup[$i] = $this->newpagegroup[$j];
                    unset($this->newpagegroup[$j]);
                }
                if ($this->currpagegroup == $j) {
                    $this->currpagegroup = $i;
                }
                if (isset($this->pageopen[$j])) {
                    $this->pageopen[$i] = $this->pageopen[$j];
                } elseif (isset($this->pageopen[$i])) {
                    unset($this->pageopen[$i]);
                }
            }
            // remove last page
            unset($this->pages[$this->numpages]);
            unset($this->pagedim[$this->numpages]);
            unset($this->pagelen[$this->numpages]);
            unset($this->intmrk[$this->numpages]);
            unset($this->bordermrk[$this->numpages]);
            unset($this->cntmrk[$this->numpages]);
            foreach ($this->pageobjects[$this->numpages] as $oid) {
                if (isset($this->offsets[$oid])){
                    unset($this->offsets[$oid]);
                }
            }
            unset($this->pageobjects[$this->numpages]);
            if (isset($this->footerpos[$this->numpages])) {
                unset($this->footerpos[$this->numpages]);
            }
            if (isset($this->footerlen[$this->numpages])) {
                unset($this->footerlen[$this->numpages]);
            }
            if (isset($this->transfmrk[$this->numpages])) {
                unset($this->transfmrk[$this->numpages]);
            }
            if (isset($this->PageAnnots[$this->numpages])) {
                unset($this->PageAnnots[$this->numpages]);
            }
            if (isset($this->newpagegroup[$this->numpages])) {
                unset($this->newpagegroup[$this->numpages]);
            }
            if ($this->currpagegroup == $this->numpages) {
                $this->currpagegroup = ($this->numpages - 1);
            }
            if (isset($this->pagegroups[$this->numpages])) {
                unset($this->pagegroups[$this->numpages]);
            }
            if (isset($this->pageopen[$this->numpages])) {
                unset($this->pageopen[$this->numpages]);
            }
        }
        --$this->numpages;
        $this->page = $this->numpages;
        // adjust outlines
        $tmpoutlines = $this->outlines;
        foreach ($tmpoutlines as $key => $outline) {
            if (!$outline['f']) {
                if ($outline['p'] > $page) {
                    $this->outlines[$key]['p'] = $outline['p'] - 1;
                } elseif ($outline['p'] == $page) {
                    unset($this->outlines[$key]);
                }
            }
        }
        // adjust dests
        $tmpdests = $this->dests;
        foreach ($tmpdests as $key => $dest) {
            if (!$dest['f']) {
                if ($dest['p'] > $page) {
                    $this->dests[$key]['p'] = $dest['p'] - 1;
                } elseif ($dest['p'] == $page) {
                    unset($this->dests[$key]);
                }
            }
        }
        // adjust links
        $tmplinks = $this->links;
        foreach ($tmplinks as $key => $link) {
            if (!$link['f']) {
                if ($link['p'] > $page) {
                    $this->links[$key]['p'] = $link['p'] - 1;
                } elseif ($link['p'] == $page) {
                    unset($this->links[$key]);
                }
            }
        }
        // adjust javascript
        $jpage = $page;
        if (preg_match_all('/this\.addField\(\'([^\']*)\',\'([^\']*)\',([0-9]+)/', $this->javascript, $pamatch) > 0) {
            foreach($pamatch[0] as $pk => $pmatch) {
                $pagenum = intval($pamatch[3][$pk]) + 1;
                if ($pagenum >= $jpage) {
                    $newpage = ($pagenum - 1);
                } elseif ($pagenum == $jpage) {
                    $newpage = 1;
                } else {
                    $newpage = $pagenum;
                }
                --$newpage;
                $newjs = "this.addField(\'".$pamatch[1][$pk]."\',\'".$pamatch[2][$pk]."\',".$newpage;
                $this->javascript = str_replace($pmatch, $newjs, $this->javascript);
            }
            unset($pamatch);
        }
        // return to last page
        if ($this->numpages > 0) {
            $this->lastPage(true);
        }
        return true;
    }

    /**
     * Clone the specified page to a new page.
     * @param $page (int) number of page to copy (0 = current page)
     * @return true in case of success, false in case of error.
     * @public
     * @since 4.9.015 (2010-04-20)
     */
    public function copyPage($page=0) {
        if ($page == 0) {
            // default value
            $page = $this->page;
        }
        if (($page < 1) OR ($page > $this->numpages)) {
            return false;
        }
        // close the last page
        $this->endPage();
        // copy all page-related states
        ++$this->numpages;
        $this->page = $this->numpages;
        $this->setPageBuffer($this->page, $this->getPageBuffer($page));
        $this->pagedim[$this->page] = $this->pagedim[$page];
        $this->pagelen[$this->page] = $this->pagelen[$page];
        $this->intmrk[$this->page] = $this->intmrk[$page];
        $this->bordermrk[$this->page] = $this->bordermrk[$page];
        $this->cntmrk[$this->page] = $this->cntmrk[$page];
        $this->pageobjects[$this->page] = $this->pageobjects[$page];
        $this->pageopen[$this->page] = false;
        if (isset($this->footerpos[$page])) {
            $this->footerpos[$this->page] = $this->footerpos[$page];
        }
        if (isset($this->footerlen[$page])) {
            $this->footerlen[$this->page] = $this->footerlen[$page];
        }
        if (isset($this->transfmrk[$page])) {
            $this->transfmrk[$this->page] = $this->transfmrk[$page];
        }
        if (isset($this->PageAnnots[$page])) {
            $this->PageAnnots[$this->page] = $this->PageAnnots[$page];
        }
        if (isset($this->newpagegroup[$page])) {
            // start a new group
            $this->newpagegroup[$this->page] = sizeof($this->newpagegroup) + 1;
            $this->currpagegroup = $this->newpagegroup[$this->page];
            $this->pagegroups[$this->currpagegroup] = 1;
        } elseif (isset($this->currpagegroup) AND ($this->currpagegroup > 0)) {
            ++$this->pagegroups[$this->currpagegroup];
        }
        // copy outlines
        $tmpoutlines = $this->outlines;
        foreach ($tmpoutlines as $key => $outline) {
            if ($outline['p'] == $page) {
                $this->outlines[] = array('t' => $outline['t'], 'l' => $outline['l'], 'x' => $outline['x'], 'y' => $outline['y'], 'p' => $this->page, 'f' => $outline['f'], 's' => $outline['s'], 'c' => $outline['c']);
            }
        }
        // copy links
        $tmplinks = $this->links;
        foreach ($tmplinks as $key => $link) {
            if ($link['p'] == $page) {
                $this->links[] = array('p' => $this->page, 'y' => $link['y'], 'f' => $link['f']);
            }
        }
        // return to last page
        $this->lastPage(true);
        return true;
    }

    /**
     * Output a Table of Content Index (TOC).
     * This method must be called after all Bookmarks were set.
     * Before calling this method you have to open the page using the addTOCPage() method.
     * After calling this method you have to call endTOCPage() to close the TOC page.
     * You can override this method to achieve different styles.
     * @param $page (int) page number where this TOC should be inserted (leave empty for current page).
     * @param $numbersfont (string) set the font for page numbers (please use monospaced font for better alignment).
     * @param $filler (string) string used to fill the space between text and page number.
     * @param $toc_name (string) name to use for TOC bookmark.
     * @param $style (string) Font style for title: B = Bold, I = Italic, BI = Bold + Italic.
     * @param $color (array) RGB color array for bookmark title (values from 0 to 255).
     * @public
     * @author Nicola Asuni
     * @since 4.5.000 (2009-01-02)
     * @see addTOCPage(), endTOCPage(), addHTMLTOC()
     */
    public function addTOC($page='', $numbersfont='', $filler='.', $toc_name='TOC', $style='', $color=array(0,0,0)) {
        $fontsize = $this->FontSizePt;
        $fontfamily = $this->FontFamily;
        $fontstyle = $this->FontStyle;
        $w = $this->w - $this->lMargin - $this->rMargin;
        $spacer = $this->GetStringWidth(chr(32)) * 4;
        $lmargin = $this->lMargin;
        $rmargin = $this->rMargin;
        $x_start = $this->GetX();
        $page_first = $this->page;
        $current_page = $this->page;
        $page_fill_start = false;
        $page_fill_end = false;
        $current_column = $this->current_column;
        if (TCPDF_STATIC::empty_string($numbersfont)) {
            $numbersfont = $this->default_monospaced_font;
        }
        if (TCPDF_STATIC::empty_string($filler)) {
            $filler = ' ';
        }
        if (TCPDF_STATIC::empty_string($page)) {
            $gap = ' ';
        } else {
            $gap = '';
            if ($page < 1) {
                $page = 1;
            }
        }
        $this->SetFont($numbersfont, $fontstyle, $fontsize);
        $numwidth = $this->GetStringWidth('00000');
        $maxpage = 0; //used for pages on attached documents
        foreach ($this->outlines as $key => $outline) {
            // check for extra pages (used for attachments)
            if (($this->page > $page_first) AND ($outline['p'] >= $this->numpages)) {
                $outline['p'] += ($this->page - $page_first);
            }
            if ($this->rtl) {
                $aligntext = 'R';
                $alignnum = 'L';
            } else {
                $aligntext = 'L';
                $alignnum = 'R';
            }
            if ($outline['l'] == 0) {
                $this->SetFont($fontfamily, $outline['s'].'B', $fontsize);
            } else {
                $this->SetFont($fontfamily, $outline['s'], $fontsize - $outline['l']);
            }
            $this->SetTextColorArray($outline['c']);
            // check for page break
            $this->checkPageBreak(2 * $this->getCellHeight($this->FontSize));
            // set margins and X position
            if (($this->page == $current_page) AND ($this->current_column == $current_column)) {
                $this->lMargin = $lmargin;
                $this->rMargin = $rmargin;
            } else {
                if ($this->current_column != $current_column) {
                    if ($this->rtl) {
                        $x_start = $this->w - $this->columns[$this->current_column]['x'];
                    } else {
                        $x_start = $this->columns[$this->current_column]['x'];
                    }
                }
                $lmargin = $this->lMargin;
                $rmargin = $this->rMargin;
                $current_page = $this->page;
                $current_column = $this->current_column;
            }
            $this->SetX($x_start);
            $indent = ($spacer * $outline['l']);
            if ($this->rtl) {
                $this->x -= $indent;
                $this->rMargin = $this->w - $this->x;
            } else {
                $this->x += $indent;
                $this->lMargin = $this->x;
            }
            $link = $this->AddLink();
            $this->SetLink($link, $outline['y'], $outline['p']);
            // write the text
            if ($this->rtl) {
                $txt = ' '.$outline['t'];
            } else {
                $txt = $outline['t'].' ';
            }
            $this->Write(0, $txt, $link, false, $aligntext, false, 0, false, false, 0, $numwidth, '');
            if ($this->rtl) {
                $tw = $this->x - $this->lMargin;
            } else {
                $tw = $this->w - $this->rMargin - $this->x;
            }
            $this->SetFont($numbersfont, $fontstyle, $fontsize);
            if (TCPDF_STATIC::empty_string($page)) {
                $pagenum = $outline['p'];
            } else {
                // placemark to be replaced with the correct number
                $pagenum = '{#'.($outline['p']).'}';
                if ($this->isUnicodeFont()) {
                    $pagenum = '{'.$pagenum.'}';
                }
                $maxpage = max($maxpage, $outline['p']);
            }
            $fw = ($tw - $this->GetStringWidth($pagenum.$filler));
            $wfiller = $this->GetStringWidth($filler);
            if ($wfiller > 0) {
                $numfills = floor($fw / $wfiller);
            } else {
                $numfills = 0;
            }
            if ($numfills > 0) {
                $rowfill = str_repeat($filler, $numfills);
            } else {
                $rowfill = '';
            }
            if ($this->rtl) {
                $pagenum = $pagenum.$gap.$rowfill;
            } else {
                $pagenum = $rowfill.$gap.$pagenum;
            }
            // write the number
            $this->Cell($tw, 0, $pagenum, 0, 1, $alignnum, 0, $link, 0);
        }
        $page_last = $this->getPage();
        $numpages = ($page_last - $page_first + 1);
        // account for booklet mode
        if ($this->booklet) {
            // check if a blank page is required before TOC
            $page_fill_start = ((($page_first % 2) == 0) XOR (($page % 2) == 0));
            $page_fill_end = (!((($numpages % 2) == 0) XOR ($page_fill_start)));
            if ($page_fill_start) {
                // add a page at the end (to be moved before TOC)
                $this->addPage();
                ++$page_last;
                ++$numpages;
            }
            if ($page_fill_end) {
                // add a page at the end
                $this->addPage();
                ++$page_last;
                ++$numpages;
            }
        }
        $maxpage = max($maxpage, $page_last);
        if (!TCPDF_STATIC::empty_string($page)) {
            for ($p = $page_first; $p <= $page_last; ++$p) {
                // get page data
                $temppage = $this->getPageBuffer($p);
                for ($n = 1; $n <= $maxpage; ++$n) {
                    // update page numbers
                    $a = '{#'.$n.'}';
                    // get page number aliases
                    $pnalias = $this->getInternalPageNumberAliases($a);
                    // calculate replacement number
                    if (($n >= $page) AND ($n <= $this->numpages)) {
                        $np = $n + $numpages;
                    } else {
                        $np = $n;
                    }
                    $na = TCPDF_STATIC::formatTOCPageNumber(($this->starting_page_number + $np - 1));
                    $nu = TCPDF_FONTS::UTF8ToUTF16BE($na, false, $this->isunicode, $this->CurrentFont);
                    // replace aliases with numbers
                    foreach ($pnalias['u'] as $u) {
                        $sfill = str_repeat($filler, max(0, (strlen($u) - strlen($nu.' '))));
                        if ($this->rtl) {
                            $nr = $nu.TCPDF_FONTS::UTF8ToUTF16BE(' '.$sfill, false, $this->isunicode, $this->CurrentFont);
                        } else {
                            $nr = TCPDF_FONTS::UTF8ToUTF16BE($sfill.' ', false, $this->isunicode, $this->CurrentFont).$nu;
                        }
                        $temppage = str_replace($u, $nr, $temppage);
                    }
                    foreach ($pnalias['a'] as $a) {
                        $sfill = str_repeat($filler, max(0, (strlen($a) - strlen($na.' '))));
                        if ($this->rtl) {
                            $nr = $na.' '.$sfill;
                        } else {
                            $nr = $sfill.' '.$na;
                        }
                        $temppage = str_replace($a, $nr, $temppage);
                    }
                }
                // save changes
                $this->setPageBuffer($p, $temppage);
            }
            // move pages
            $this->Bookmark($toc_name, 0, 0, $page_first, $style, $color);
            if ($page_fill_start) {
                $this->movePage($page_last, $page_first);
            }
            for ($i = 0; $i < $numpages; ++$i) {
                $this->movePage($page_last, $page);
            }
        }
    }

    /**
     * Output a Table Of Content Index (TOC) using HTML templates.
     * This method must be called after all Bookmarks were set.
     * Before calling this method you have to open the page using the addTOCPage() method.
     * After calling this method you have to call endTOCPage() to close the TOC page.
     * @param $page (int) page number where this TOC should be inserted (leave empty for current page).
     * @param $toc_name (string) name to use for TOC bookmark.
     * @param $templates (array) array of html templates. Use: "#TOC_DESCRIPTION#" for bookmark title, "#TOC_PAGE_NUMBER#" for page number.
     * @param $correct_align (boolean) if true correct the number alignment (numbers must be in monospaced font like courier and right aligned on LTR, or left aligned on RTL)
     * @param $style (string) Font style for title: B = Bold, I = Italic, BI = Bold + Italic.
     * @param $color (array) RGB color array for title (values from 0 to 255).
     * @public
     * @author Nicola Asuni
     * @since 5.0.001 (2010-05-06)
     * @see addTOCPage(), endTOCPage(), addTOC()
     */
    public function addHTMLTOC($page='', $toc_name='TOC', $templates=array(), $correct_align=true, $style='', $color=array(0,0,0)) {
        $filler = ' ';
        $prev_htmlLinkColorArray = $this->htmlLinkColorArray;
        $prev_htmlLinkFontStyle = $this->htmlLinkFontStyle;
        // set new style for link
        $this->htmlLinkColorArray = array();
        $this->htmlLinkFontStyle = '';
        $page_first = $this->getPage();
        $page_fill_start = false;
        $page_fill_end = false;
        // get the font type used for numbers in each template
        $current_font = $this->FontFamily;
        foreach ($templates as $level => $html) {
            $dom = $this->getHtmlDomArray($html);
            foreach ($dom as $key => $value) {
                if ($value['value'] == '#TOC_PAGE_NUMBER#') {
                    $this->SetFont($dom[($key - 1)]['fontname']);
                    $templates['F'.$level] = $this->isUnicodeFont();
                }
            }
        }
        $this->SetFont($current_font);
        $maxpage = 0; //used for pages on attached documents
        foreach ($this->outlines as $key => $outline) {
            // get HTML template
            $row = $templates[$outline['l']];
            if (TCPDF_STATIC::empty_string($page)) {
                $pagenum = $outline['p'];
            } else {
                // placemark to be replaced with the correct number
                $pagenum = '{#'.($outline['p']).'}';
                if ($templates['F'.$outline['l']]) {
                    $pagenum = '{'.$pagenum.'}';
                }
                $maxpage = max($maxpage, $outline['p']);
            }
            // replace templates with current values
            $row = str_replace('#TOC_DESCRIPTION#', $outline['t'], $row);
            $row = str_replace('#TOC_PAGE_NUMBER#', $pagenum, $row);
            // add link to page
            $row = '<a href="#'.$outline['p'].','.$outline['y'].'">'.$row.'</a>';
            // write bookmark entry
            $this->writeHTML($row, false, false, true, false, '');
        }
        // restore link styles
        $this->htmlLinkColorArray = $prev_htmlLinkColorArray;
        $this->htmlLinkFontStyle = $prev_htmlLinkFontStyle;
        // move TOC page and replace numbers
        $page_last = $this->getPage();
        $numpages = ($page_last - $page_first + 1);
        // account for booklet mode
        if ($this->booklet) {
            // check if a blank page is required before TOC
            $page_fill_start = ((($page_first % 2) == 0) XOR (($page % 2) == 0));
            $page_fill_end = (!((($numpages % 2) == 0) XOR ($page_fill_start)));
            if ($page_fill_start) {
                // add a page at the end (to be moved before TOC)
                $this->addPage();
                ++$page_last;
                ++$numpages;
            }
            if ($page_fill_end) {
                // add a page at the end
                $this->addPage();
                ++$page_last;
                ++$numpages;
            }
        }
        $maxpage = max($maxpage, $page_last);
        if (!TCPDF_STATIC::empty_string($page)) {
            for ($p = $page_first; $p <= $page_last; ++$p) {
                // get page data
                $temppage = $this->getPageBuffer($p);
                for ($n = 1; $n <= $maxpage; ++$n) {
                    // update page numbers
                    $a = '{#'.$n.'}';
                    // get page number aliases
                    $pnalias = $this->getInternalPageNumberAliases($a);
                    // calculate replacement number
                    if ($n >= $page) {
                        $np = $n + $numpages;
                    } else {
                        $np = $n;
                    }
                    $na = TCPDF_STATIC::formatTOCPageNumber(($this->starting_page_number + $np - 1));
                    $nu = TCPDF_FONTS::UTF8ToUTF16BE($na, false, $this->isunicode, $this->CurrentFont);
                    // replace aliases with numbers
                    foreach ($pnalias['u'] as $u) {
                        if ($correct_align) {
                            $sfill = str_repeat($filler, (strlen($u) - strlen($nu.' ')));
                            if ($this->rtl) {
                                $nr = $nu.TCPDF_FONTS::UTF8ToUTF16BE(' '.$sfill, false, $this->isunicode, $this->CurrentFont);
                            } else {
                                $nr = TCPDF_FONTS::UTF8ToUTF16BE($sfill.' ', false, $this->isunicode, $this->CurrentFont).$nu;
                            }
                        } else {
                            $nr = $nu;
                        }
                        $temppage = str_replace($u, $nr, $temppage);
                    }
                    foreach ($pnalias['a'] as $a) {
                        if ($correct_align) {
                            $sfill = str_repeat($filler, (strlen($a) - strlen($na.' ')));
                            if ($this->rtl) {
                                $nr = $na.' '.$sfill;
                            } else {
                                $nr = $sfill.' '.$na;
                            }
                        } else {
                            $nr = $na;
                        }
                        $temppage = str_replace($a, $nr, $temppage);
                    }
                }
                // save changes
                $this->setPageBuffer($p, $temppage);
            }
            // move pages
            $this->Bookmark($toc_name, 0, 0, $page_first, $style, $color);
            if ($page_fill_start) {
                $this->movePage($page_last, $page_first);
            }
            for ($i = 0; $i < $numpages; ++$i) {
                $this->movePage($page_last, $page);
            }
        }
    }

    /**
     * Stores a copy of the current TCPDF object used for undo operation.
     * @public
     * @since 4.5.029 (2009-03-19)
     */
    public function startTransaction() {
        if (isset($this->objcopy)) {
            // remove previous copy
            $this->commitTransaction();
        }
        // record current page number and Y position
        $this->start_transaction_page = $this->page;
        $this->start_transaction_y = $this->y;
        // clone current object
        $this->objcopy = TCPDF_STATIC::objclone($this);
    }

    /**
     * Delete the copy of the current TCPDF object used for undo operation.
     * @public
     * @since 4.5.029 (2009-03-19)
     */
    public function commitTransaction() {
        if (isset($this->objcopy)) {
            $this->objcopy->_destroy(true, true);
            unset($this->objcopy);
        }
    }

    /**
     * This method allows to undo the latest transaction by returning the latest saved TCPDF object with startTransaction().
     * @param $self (boolean) if true restores current class object to previous state without the need of reassignment via the returned value.
     * @return TCPDF object.
     * @public
     * @since 4.5.029 (2009-03-19)
     */
    public function rollbackTransaction($self=false) {
        if (isset($this->objcopy)) {
            $this->_destroy(true, true);
            if ($self) {
                $objvars = get_object_vars($this->objcopy);
                foreach ($objvars as $key => $value) {
                    $this->$key = $value;
                }
            }
            return $this->objcopy;
        }
        return $this;
    }

    // --- MULTI COLUMNS METHODS -----------------------

    /**
     * Set multiple columns of the same size
     * @param $numcols (int) number of columns (set to zero to disable columns mode)
     * @param $width (int) column width
     * @param $y (int) column starting Y position (leave empty for current Y position)
     * @public
     * @since 4.9.001 (2010-03-28)
     */
    public function setEqualColumns($numcols=0, $width=0, $y='') {
        $this->columns = array();
        if ($numcols < 2) {
            $numcols = 0;
            $this->columns = array();
        } else {
            // maximum column width
            $maxwidth = ($this->w - $this->original_lMargin - $this->original_rMargin) / $numcols;
            if (($width == 0) OR ($width > $maxwidth)) {
                $width = $maxwidth;
            }
            if (TCPDF_STATIC::empty_string($y)) {
                $y = $this->y;
            }
            // space between columns
            $space = (($this->w - $this->original_lMargin - $this->original_rMargin - ($numcols * $width)) / ($numcols - 1));
            // fill the columns array (with, space, starting Y position)
            for ($i = 0; $i < $numcols; ++$i) {
                $this->columns[$i] = array('w' => $width, 's' => $space, 'y' => $y);
            }
        }
        $this->num_columns = $numcols;
        $this->current_column = 0;
        $this->column_start_page = $this->page;
        $this->selectColumn(0);
    }

    /**
     * Remove columns and reset page margins.
     * @public
     * @since 5.9.072 (2011-04-26)
     */
    public function resetColumns() {
        $this->lMargin = $this->original_lMargin;
        $this->rMargin = $this->original_rMargin;
        $this->setEqualColumns();
    }

    /**
     * Set columns array.
     * Each column is represented by an array of arrays with the following keys: (w = width, s = space between columns, y = column top position).
     * @param $columns (array)
     * @public
     * @since 4.9.001 (2010-03-28)
     */
    public function setColumnsArray($columns) {
        $this->columns = $columns;
        $this->num_columns = count($columns);
        $this->current_column = 0;
        $this->column_start_page = $this->page;
        $this->selectColumn(0);
    }

    /**
     * Set position at a given column
     * @param $col (int) column number (from 0 to getNumberOfColumns()-1); empty string = current column.
     * @public
     * @since 4.9.001 (2010-03-28)
     */
    public function selectColumn($col='') {
        if (is_string($col)) {
            $col = $this->current_column;
        } elseif ($col >= $this->num_columns) {
            $col = 0;
        }
        $xshift = array('x' => 0, 's' => array('H' => 0, 'V' => 0), 'p' => array('L' => 0, 'T' => 0, 'R' => 0, 'B' => 0));
        $enable_thead = false;
        if ($this->num_columns > 1) {
            if ($col != $this->current_column) {
                // move Y pointer at the top of the column
                if ($this->column_start_page == $this->page) {
                    $this->y = $this->columns[$col]['y'];
                } else {
                    $this->y = $this->tMargin;
                }
                // Avoid to write table headers more than once
                if (($this->page > $this->maxselcol['page']) OR (($this->page == $this->maxselcol['page']) AND ($col > $this->maxselcol['column']))) {
                    $enable_thead = true;
                    $this->maxselcol['page'] = $this->page;
                    $this->maxselcol['column'] = $col;
                }
            }
            $xshift = $this->colxshift;
            // set X position of the current column by case
            $listindent = ($this->listindentlevel * $this->listindent);
            // calculate column X position
            $colpos = 0;
            for ($i = 0; $i < $col; ++$i) {
                $colpos += ($this->columns[$i]['w'] + $this->columns[$i]['s']);
            }
            if ($this->rtl) {
                $x = $this->w - $this->original_rMargin - $colpos;
                $this->rMargin = ($this->w - $x + $listindent);
                $this->lMargin = ($x - $this->columns[$col]['w']);
                $this->x = $x - $listindent;
            } else {
                $x = $this->original_lMargin + $colpos;
                $this->lMargin = ($x + $listindent);
                $this->rMargin = ($this->w - $x - $this->columns[$col]['w']);
                $this->x = $x + $listindent;
            }
            $this->columns[$col]['x'] = $x;
        }
        $this->current_column = $col;
        // fix for HTML mode
        $this->newline = true;
        // print HTML table header (if any)
        if ((!TCPDF_STATIC::empty_string($this->thead)) AND (!$this->inthead)) {
            if ($enable_thead) {
                // print table header
                $this->writeHTML($this->thead, false, false, false, false, '');
                $this->y += $xshift['s']['V'];
                // store end of header position
                if (!isset($this->columns[$col]['th'])) {
                    $this->columns[$col]['th'] = array();
                }
                $this->columns[$col]['th']['\''.$this->page.'\''] = $this->y;
                $this->lasth = 0;
            } elseif (isset($this->columns[$col]['th']['\''.$this->page.'\''])) {
                $this->y = $this->columns[$col]['th']['\''.$this->page.'\''];
            }
        }
        // account for an html table cell over multiple columns
        if ($this->rtl) {
            $this->rMargin += $xshift['x'];
            $this->x -= ($xshift['x'] + $xshift['p']['R']);
        } else {
            $this->lMargin += $xshift['x'];
            $this->x += $xshift['x'] + $xshift['p']['L'];
        }
    }

    /**
     * Return the current column number
     * @return int current column number
     * @public
     * @since 5.5.011 (2010-07-08)
     */
    public function getColumn() {
        return $this->current_column;
    }

    /**
     * Return the current number of columns.
     * @return int number of columns
     * @public
     * @since 5.8.018 (2010-08-25)
     */
    public function getNumberOfColumns() {
        return $this->num_columns;
    }

    /**
     * Set Text rendering mode.
     * @param $stroke (int) outline size in user units (0 = disable).
     * @param $fill (boolean) if true fills the text (default).
     * @param $clip (boolean) if true activate clipping mode
     * @public
     * @since 4.9.008 (2009-04-02)
     */
    public function setTextRenderingMode($stroke=0, $fill=true, $clip=false) {
        // Ref.: PDF 32000-1:2008 - 9.3.6 Text Rendering Mode
        // convert text rendering parameters
        if ($stroke < 0) {
            $stroke = 0;
        }
        if ($fill === true) {
            if ($stroke > 0) {
                if ($clip === true) {
                    // Fill, then stroke text and add to path for clipping
                    $textrendermode = 6;
                } else {
                    // Fill, then stroke text
                    $textrendermode = 2;
                }
                $textstrokewidth = $stroke;
            } else {
                if ($clip === true) {
                    // Fill text and add to path for clipping
                    $textrendermode = 4;
                } else {
                    // Fill text
                    $textrendermode = 0;
                }
            }
        } else {
            if ($stroke > 0) {
                if ($clip === true) {
                    // Stroke text and add to path for clipping
                    $textrendermode = 5;
                } else {
                    // Stroke text
                    $textrendermode = 1;
                }
                $textstrokewidth = $stroke;
            } else {
                if ($clip === true) {
                    // Add text to path for clipping
                    $textrendermode = 7;
                } else {
                    // Neither fill nor stroke text (invisible)
                    $textrendermode = 3;
                }
            }
        }
        $this->textrendermode = $textrendermode;
        $this->textstrokewidth = $stroke;
    }

    /**
     * Set parameters for drop shadow effect for text.
     * @param $params (array) Array of parameters: enabled (boolean) set to true to enable shadow; depth_w (float) shadow width in user units; depth_h (float) shadow height in user units; color (array) shadow color or false to use the stroke color; opacity (float) Alpha value: real value from 0 (transparent) to 1 (opaque); blend_mode (string) blend mode, one of the following: Normal, Multiply, Screen, Overlay, Darken, Lighten, ColorDodge, ColorBurn, HardLight, SoftLight, Difference, Exclusion, Hue, Saturation, Color, Luminosity.
     * @since 5.9.174 (2012-07-25)
     * @public
    */
    public function setTextShadow($params=array('enabled'=>false, 'depth_w'=>0, 'depth_h'=>0, 'color'=>false, 'opacity'=>1, 'blend_mode'=>'Normal')) {
        if (isset($params['enabled'])) {
            $this->txtshadow['enabled'] = $params['enabled']?true:false;
        } else {
            $this->txtshadow['enabled'] = false;
        }
        if (isset($params['depth_w'])) {
            $this->txtshadow['depth_w'] = floatval($params['depth_w']);
        } else {
            $this->txtshadow['depth_w'] = 0;
        }
        if (isset($params['depth_h'])) {
            $this->txtshadow['depth_h'] = floatval($params['depth_h']);
        } else {
            $this->txtshadow['depth_h'] = 0;
        }
        if (isset($params['color']) AND ($params['color'] !== false) AND is_array($params['color'])) {
            $this->txtshadow['color'] = $params['color'];
        } else {
            $this->txtshadow['color'] = $this->strokecolor;
        }
        if (isset($params['opacity'])) {
            $this->txtshadow['opacity'] = min(1, max(0, floatval($params['opacity'])));
        } else {
            $this->txtshadow['opacity'] = 1;
        }
        if (isset($params['blend_mode']) AND in_array($params['blend_mode'], array('Normal', 'Multiply', 'Screen', 'Overlay', 'Darken', 'Lighten', 'ColorDodge', 'ColorBurn', 'HardLight', 'SoftLight', 'Difference', 'Exclusion', 'Hue', 'Saturation', 'Color', 'Luminosity'))) {
            $this->txtshadow['blend_mode'] = $params['blend_mode'];
        } else {
            $this->txtshadow['blend_mode'] = 'Normal';
        }
        if ((($this->txtshadow['depth_w'] == 0) AND ($this->txtshadow['depth_h'] == 0)) OR ($this->txtshadow['opacity'] == 0)) {
            $this->txtshadow['enabled'] = false;
        }
    }

    /**
     * Return the text shadow parameters array.
     * @return Array of parameters.
     * @since 5.9.174 (2012-07-25)
     * @public
     */
    public function getTextShadow() {
        return $this->txtshadow;
    }

    /**
     * Returns an array of chars containing soft hyphens.
     * @param $word (array) array of chars
     * @param $patterns (array) Array of hypenation patterns.
     * @param $dictionary (array) Array of words to be returned without applying the hyphenation algorithm.
     * @param $leftmin (int) Minimum number of character to leave on the left of the word without applying the hyphens.
     * @param $rightmin (int) Minimum number of character to leave on the right of the word without applying the hyphens.
     * @param $charmin (int) Minimum word length to apply the hyphenation algorithm.
     * @param $charmax (int) Maximum length of broken piece of word.
     * @return array text with soft hyphens
     * @author Nicola Asuni
     * @since 4.9.012 (2010-04-12)
     * @protected
     */
    protected function hyphenateWord($word, $patterns, $dictionary=array(), $leftmin=1, $rightmin=2, $charmin=1, $charmax=8) {
        $hyphenword = array(); // hyphens positions
        $numchars = count($word);
        if ($numchars <= $charmin) {
            return $word;
        }
        $word_string = TCPDF_FONTS::UTF8ArrSubString($word, '', '', $this->isunicode);
        // some words will be returned as-is
        $pattern = '/^([a-zA-Z0-9_\.\-]+)@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.)|(([a-zA-Z0-9\-]+\.)+))([a-zA-Z]{2,4}|[0-9]{1,3})(\]?)$/';
        if (preg_match($pattern, $word_string) > 0) {
            // email
            return $word;
        }
        $pattern = '/(([a-zA-Z0-9\-]+\.)?)((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.)|(([a-zA-Z0-9\-]+\.)+))([a-zA-Z]{2,4}|[0-9]{1,3})(\]?)$/';
        if (preg_match($pattern, $word_string) > 0) {
            // URL
            return $word;
        }
        if (isset($dictionary[$word_string])) {
            return TCPDF_FONTS::UTF8StringToArray($dictionary[$word_string], $this->isunicode, $this->CurrentFont);
        }
        // surround word with '_' characters
        $tmpword = array_merge(array(46), $word, array(46));
        $tmpnumchars = $numchars + 2;
        $maxpos = $tmpnumchars - 1;
        for ($pos = 0; $pos < $maxpos; ++$pos) {
            $imax = min(($tmpnumchars - $pos), $charmax);
            for ($i = 1; $i <= $imax; ++$i) {
                $subword = strtolower(TCPDF_FONTS::UTF8ArrSubString($tmpword, $pos, ($pos + $i), $this->isunicode));
                if (isset($patterns[$subword])) {
                    $pattern = TCPDF_FONTS::UTF8StringToArray($patterns[$subword], $this->isunicode, $this->CurrentFont);
                    $pattern_length = count($pattern);
                    $digits = 1;
                    for ($j = 0; $j < $pattern_length; ++$j) {
                        // check if $pattern[$j] is a number = hyphenation level (only numbers from 1 to 5 are valid)
                        if (($pattern[$j] >= 48) AND ($pattern[$j] <= 57)) {
                            if ($j == 0) {
                                $zero = $pos - 1;
                            } else {
                                $zero = $pos + $j - $digits;
                            }
                            // get hyphenation level
                            $level = ($pattern[$j] - 48);
                            // if two levels from two different patterns match at the same point, the higher one is selected.
                            if (!isset($hyphenword[$zero]) OR ($hyphenword[$zero] < $level)) {
                                $hyphenword[$zero] = $level;
                            }
                            ++$digits;
                        }
                    }
                }
            }
        }
        $inserted = 0;
        $maxpos = $numchars - $rightmin;
        for ($i = $leftmin; $i <= $maxpos; ++$i) {
            // only odd levels indicate allowed hyphenation points
            if (isset($hyphenword[$i]) AND (($hyphenword[$i] % 2) != 0)) {
                // 173 = soft hyphen character
                array_splice($word, $i + $inserted, 0, 173);
                ++$inserted;
            }
        }
        return $word;
    }

    /**
     * Returns text with soft hyphens.
     * @param $text (string) text to process
     * @param $patterns (mixed) Array of hypenation patterns or a TEX file containing hypenation patterns. TEX patterns can be downloaded from http://www.ctan.org/tex-archive/language/hyph-utf8/tex/generic/hyph-utf8/patterns/
     * @param $dictionary (array) Array of words to be returned without applying the hyphenation algorithm.
     * @param $leftmin (int) Minimum number of character to leave on the left of the word without applying the hyphens.
     * @param $rightmin (int) Minimum number of character to leave on the right of the word without applying the hyphens.
     * @param $charmin (int) Minimum word length to apply the hyphenation algorithm.
     * @param $charmax (int) Maximum length of broken piece of word.
     * @return array text with soft hyphens
     * @author Nicola Asuni
     * @since 4.9.012 (2010-04-12)
     * @public
     */
    public function hyphenateText($text, $patterns, $dictionary=array(), $leftmin=1, $rightmin=2, $charmin=1, $charmax=8) {
        $text = $this->unhtmlentities($text);
        $word = array(); // last word
        $txtarr = array(); // text to be returned
        $intag = false; // true if we are inside an HTML tag
        $skip = false; // true to skip hyphenation
        if (!is_array($patterns)) {
            $patterns = TCPDF_STATIC::getHyphenPatternsFromTEX($patterns);
        }
        // get array of characters
        $unichars = TCPDF_FONTS::UTF8StringToArray($text, $this->isunicode, $this->CurrentFont);
        // for each char
        foreach ($unichars as $char) {
            if ((!$intag) AND (!$skip) AND TCPDF_FONT_DATA::$uni_type[$char] == 'L') {
                // letter character
                $word[] = $char;
            } else {
                // other type of character
                if (!TCPDF_STATIC::empty_string($word)) {
                    // hypenate the word
                    $txtarr = array_merge($txtarr, $this->hyphenateWord($word, $patterns, $dictionary, $leftmin, $rightmin, $charmin, $charmax));
                    $word = array();
                }
                $txtarr[] = $char;
                if (chr($char) == '<') {
                    // we are inside an HTML tag
                    $intag = true;
                } elseif ($intag AND (chr($char) == '>')) {
                    // end of HTML tag
                    $intag = false;
                    // check for style tag
                    $expected = array(115, 116, 121, 108, 101); // = 'style'
                    $current = array_slice($txtarr, -6, 5); // last 5 chars
                    $compare = array_diff($expected, $current);
                    if (empty($compare)) {
                        // check if it is a closing tag
                        $expected = array(47); // = '/'
                        $current = array_slice($txtarr, -7, 1);
                        $compare = array_diff($expected, $current);
                        if (empty($compare)) {
                            // closing style tag
                            $skip = false;
                        } else {
                            // opening style tag
                            $skip = true;
                        }
                    }
                }
            }
        }
        if (!TCPDF_STATIC::empty_string($word)) {
            // hypenate the word
            $txtarr = array_merge($txtarr, $this->hyphenateWord($word, $patterns, $dictionary, $leftmin, $rightmin, $charmin, $charmax));
        }
        // convert char array to string and return
        return TCPDF_FONTS::UTF8ArrSubString($txtarr, '', '', $this->isunicode);
    }

    /**
     * Enable/disable rasterization of vector images using ImageMagick library.
     * @param $mode (boolean) if true enable rasterization, false otherwise.
     * @public
     * @since 5.0.000 (2010-04-27)
     */
    public function setRasterizeVectorImages($mode) {
        $this->rasterize_vector_images = $mode;
    }

    /**
     * Enable or disable default option for font subsetting.
     * @param $enable (boolean) if true enable font subsetting by default.
     * @author Nicola Asuni
     * @public
     * @since 5.3.002 (2010-06-07)
     */
    public function setFontSubsetting($enable=true) {
        if ($this->pdfa_mode) {
            $this->font_subsetting = false;
        } else {
            $this->font_subsetting = $enable ? true : false;
        }
    }

    /**
     * Return the default option for font subsetting.
     * @return boolean default font subsetting state.
     * @author Nicola Asuni
     * @public
     * @since 5.3.002 (2010-06-07)
     */
    public function getFontSubsetting() {
        return $this->font_subsetting;
    }

    /**
     * Left trim the input string
     * @param $str (string) string to trim
     * @param $replace (string) string that replace spaces.
     * @return left trimmed string
     * @author Nicola Asuni
     * @public
     * @since 5.8.000 (2010-08-11)
     */
    public function stringLeftTrim($str, $replace='') {
        return preg_replace('/^'.$this->re_space['p'].'+/'.$this->re_space['m'], $replace, $str);
    }

    /**
     * Right trim the input string
     * @param $str (string) string to trim
     * @param $replace (string) string that replace spaces.
     * @return right trimmed string
     * @author Nicola Asuni
     * @public
     * @since 5.8.000 (2010-08-11)
     */
    public function stringRightTrim($str, $replace='') {
        return preg_replace('/'.$this->re_space['p'].'+$/'.$this->re_space['m'], $replace, $str);
    }

    /**
     * Trim the input string
     * @param $str (string) string to trim
     * @param $replace (string) string that replace spaces.
     * @return trimmed string
     * @author Nicola Asuni
     * @public
     * @since 5.8.000 (2010-08-11)
     */
    public function stringTrim($str, $replace='') {
        $str = $this->stringLeftTrim($str, $replace);
        $str = $this->stringRightTrim($str, $replace);
        return $str;
    }

    /**
     * Return true if the current font is unicode type.
     * @return true for unicode font, false otherwise.
     * @author Nicola Asuni
     * @public
     * @since 5.8.002 (2010-08-14)
     */
    public function isUnicodeFont() {
        return (($this->CurrentFont['type'] == 'TrueTypeUnicode') OR ($this->CurrentFont['type'] == 'cidfont0'));
    }

    /**
     * Return normalized font name
     * @param $fontfamily (string) property string containing font family names
     * @return string normalized font name
     * @author Nicola Asuni
     * @public
     * @since 5.8.004 (2010-08-17)
     */
    public function getFontFamilyName($fontfamily) {
        // remove spaces and symbols
        $fontfamily = preg_replace('/[^a-z0-9_\,]/', '', strtolower($fontfamily));
        // extract all font names
        $fontslist = preg_split('/[,]/', $fontfamily);
        // find first valid font name
        foreach ($fontslist as $font) {
            // replace font variations
            $font = preg_replace('/regular$/', '', $font);
            $font = preg_replace('/italic$/', 'I', $font);
            $font = preg_replace('/oblique$/', 'I', $font);
            $font = preg_replace('/bold([I]?)$/', 'B\\1', $font);
            // replace common family names and core fonts
            $pattern = array();
            $replacement = array();
            $pattern[] = '/^serif|^cursive|^fantasy|^timesnewroman/';
            $replacement[] = 'times';
            $pattern[] = '/^sansserif/';
            $replacement[] = 'helvetica';
            $pattern[] = '/^monospace/';
            $replacement[] = 'courier';
            $font = preg_replace($pattern, $replacement, $font);
            if (in_array(strtolower($font), $this->fontlist) OR in_array($font, $this->fontkeys)) {
                return $font;
            }
        }
        // return current font as default
        return $this->CurrentFont['fontkey'];
    }

    /**
     * Start a new XObject Template.
     * An XObject Template is a PDF block that is a self-contained description of any sequence of graphics objects (including path objects, text objects, and sampled images).
     * An XObject Template may be painted multiple times, either on several pages or at several locations on the same page and produces the same results each time, subject only to the graphics state at the time it is invoked.
     * Note: X,Y coordinates will be reset to 0,0.
     * @param $w (int) Template width in user units (empty string or zero = page width less margins).
     * @param $h (int) Template height in user units (empty string or zero = page height less margins).
     * @param $group (mixed) Set transparency group. Can be a boolean value or an array specifying optional parameters: 'CS' (solour space name), 'I' (boolean flag to indicate isolated group) and 'K' (boolean flag to indicate knockout group).
     * @return int the XObject Template ID in case of success or false in case of error.
     * @author Nicola Asuni
     * @public
     * @since 5.8.017 (2010-08-24)
     * @see endTemplate(), printTemplate()
     */
    public function startTemplate($w=0, $h=0, $group=false) {
        if ($this->inxobj) {
            // we are already inside an XObject template
            return false;
        }
        $this->inxobj = true;
        ++$this->n;
        // XObject ID
        $this->xobjid = 'XT'.$this->n;
        // object ID
        $this->xobjects[$this->xobjid] = array('n' => $this->n);
        // store current graphic state
        $this->xobjects[$this->xobjid]['gvars'] = $this->getGraphicVars();
        // initialize data
        $this->xobjects[$this->xobjid]['intmrk'] = 0;
        $this->xobjects[$this->xobjid]['transfmrk'] = array();
        $this->xobjects[$this->xobjid]['outdata'] = '';
        $this->xobjects[$this->xobjid]['xobjects'] = array();
        $this->xobjects[$this->xobjid]['images'] = array();
        $this->xobjects[$this->xobjid]['fonts'] = array();
        $this->xobjects[$this->xobjid]['annotations'] = array();
        $this->xobjects[$this->xobjid]['extgstates'] = array();
        $this->xobjects[$this->xobjid]['gradients'] = array();
        $this->xobjects[$this->xobjid]['spot_colors'] = array();
        // set new environment
        $this->num_columns = 1;
        $this->current_column = 0;
        $this->SetAutoPageBreak(false);
        if (($w === '') OR ($w <= 0)) {
            $w = $this->w - $this->lMargin - $this->rMargin;
        }
        if (($h === '') OR ($h <= 0)) {
            $h = $this->h - $this->tMargin - $this->bMargin;
        }
        $this->xobjects[$this->xobjid]['x'] = 0;
        $this->xobjects[$this->xobjid]['y'] = 0;
        $this->xobjects[$this->xobjid]['w'] = $w;
        $this->xobjects[$this->xobjid]['h'] = $h;
        $this->w = $w;
        $this->h = $h;
        $this->wPt = $this->w * $this->k;
        $this->hPt = $this->h * $this->k;
        $this->fwPt = $this->wPt;
        $this->fhPt = $this->hPt;
        $this->x = 0;
        $this->y = 0;
        $this->lMargin = 0;
        $this->rMargin = 0;
        $this->tMargin = 0;
        $this->bMargin = 0;
        // set group mode
        $this->xobjects[$this->xobjid]['group'] = $group;
        return $this->xobjid;
    }

    /**
     * End the current XObject Template started with startTemplate() and restore the previous graphic state.
     * An XObject Template is a PDF block that is a self-contained description of any sequence of graphics objects (including path objects, text objects, and sampled images).
     * An XObject Template may be painted multiple times, either on several pages or at several locations on the same page and produces the same results each time, subject only to the graphics state at the time it is invoked.
     * @return int the XObject Template ID in case of success or false in case of error.
     * @author Nicola Asuni
     * @public
     * @since 5.8.017 (2010-08-24)
     * @see startTemplate(), printTemplate()
     */
    public function endTemplate() {
        if (!$this->inxobj) {
            // we are not inside a template
            return false;
        }
        $this->inxobj = false;
        // restore previous graphic state
        $this->setGraphicVars($this->xobjects[$this->xobjid]['gvars'], true);
        return $this->xobjid;
    }

    /**
     * Print an XObject Template.
     * You can print an XObject Template inside the currently opened Template.
     * An XObject Template is a PDF block that is a self-contained description of any sequence of graphics objects (including path objects, text objects, and sampled images).
     * An XObject Template may be painted multiple times, either on several pages or at several locations on the same page and produces the same results each time, subject only to the graphics state at the time it is invoked.
     * @param $id (string) The ID of XObject Template to print.
     * @param $x (int) X position in user units (empty string = current x position)
     * @param $y (int) Y position in user units (empty string = current y position)
     * @param $w (int) Width in user units (zero = remaining page width)
     * @param $h (int) Height in user units (zero = remaining page height)
     * @param $align (string) Indicates the alignment of the pointer next to template insertion relative to template height. The value can be:<ul><li>T: top-right for LTR or top-left for RTL</li><li>M: middle-right for LTR or middle-left for RTL</li><li>B: bottom-right for LTR or bottom-left for RTL</li><li>N: next line</li></ul>
     * @param $palign (string) Allows to center or align the template on the current line. Possible values are:<ul><li>L : left align</li><li>C : center</li><li>R : right align</li><li>'' : empty string : left for LTR or right for RTL</li></ul>
     * @param $fitonpage (boolean) If true the template is resized to not exceed page dimensions.
     * @author Nicola Asuni
     * @public
     * @since 5.8.017 (2010-08-24)
     * @see startTemplate(), endTemplate()
     */
    public function printTemplate($id, $x='', $y='', $w=0, $h=0, $align='', $palign='', $fitonpage=false) {
        if ($this->state != 2) {
             return;
        }
        if (!isset($this->xobjects[$id])) {
            $this->Error('The XObject Template \''.$id.'\' doesn\'t exist!');
        }
        if ($this->inxobj) {
            if ($id == $this->xobjid) {
                // close current template
                $this->endTemplate();
            } else {
                // use the template as resource for the template currently opened
                $this->xobjects[$this->xobjid]['xobjects'][$id] = $this->xobjects[$id];
            }
        }
        // set default values
        if ($x === '') {
            $x = $this->x;
        }
        if ($y === '') {
            $y = $this->y;
        }
        // check page for no-write regions and adapt page margins if necessary
        list($x, $y) = $this->checkPageRegions($h, $x, $y);
        $ow = $this->xobjects[$id]['w'];
        if ($ow <= 0) {
            $ow = 1;
        }
        $oh = $this->xobjects[$id]['h'];
        if ($oh <= 0) {
            $oh = 1;
        }
        // calculate template width and height on document
        if (($w <= 0) AND ($h <= 0)) {
            $w = $ow;
            $h = $oh;
        } elseif ($w <= 0) {
            $w = $h * $ow / $oh;
        } elseif ($h <= 0) {
            $h = $w * $oh / $ow;
        }
        // fit the template on available space
        list($w, $h, $x, $y) = $this->fitBlock($w, $h, $x, $y, $fitonpage);
        // set page alignment
        $rb_y = $y + $h;
        // set alignment
        if ($this->rtl) {
            if ($palign == 'L') {
                $xt = $this->lMargin;
            } elseif ($palign == 'C') {
                $xt = ($this->w + $this->lMargin - $this->rMargin - $w) / 2;
            } elseif ($palign == 'R') {
                $xt = $this->w - $this->rMargin - $w;
            } else {
                $xt = $x - $w;
            }
            $rb_x = $xt;
        } else {
            if ($palign == 'L') {
                $xt = $this->lMargin;
            } elseif ($palign == 'C') {
                $xt = ($this->w + $this->lMargin - $this->rMargin - $w) / 2;
            } elseif ($palign == 'R') {
                $xt = $this->w - $this->rMargin - $w;
            } else {
                $xt = $x;
            }
            $rb_x = $xt + $w;
        }
        // print XObject Template + Transformation matrix
        $this->StartTransform();
        // translate and scale
        $sx = ($w / $ow);
        $sy = ($h / $oh);
        $tm = array();
        $tm[0] = $sx;
        $tm[1] = 0;
        $tm[2] = 0;
        $tm[3] = $sy;
        $tm[4] = $xt * $this->k;
        $tm[5] = ($this->h - $h - $y) * $this->k;
        $this->Transform($tm);
        // set object
        $this->_out('/'.$id.' Do');
        $this->StopTransform();
        // add annotations
        if (!empty($this->xobjects[$id]['annotations'])) {
            foreach ($this->xobjects[$id]['annotations'] as $annot) {
                // transform original coordinates
                $coordlt = TCPDF_STATIC::getTransformationMatrixProduct($tm, array(1, 0, 0, 1, ($annot['x'] * $this->k), (-$annot['y'] * $this->k)));
                $ax = ($coordlt[4] / $this->k);
                $ay = ($this->h - $h - ($coordlt[5] / $this->k));
                $coordrb = TCPDF_STATIC::getTransformationMatrixProduct($tm, array(1, 0, 0, 1, (($annot['x'] + $annot['w']) * $this->k), ((-$annot['y'] - $annot['h']) * $this->k)));
                $aw = ($coordrb[4] / $this->k) - $ax;
                $ah = ($this->h - $h - ($coordrb[5] / $this->k)) - $ay;
                $this->Annotation($ax, $ay, $aw, $ah, $annot['text'], $annot['opt'], $annot['spaces']);
            }
        }
        // set pointer to align the next text/objects
        switch($align) {
            case 'T': {
                $this->y = $y;
                $this->x = $rb_x;
                break;
            }
            case 'M': {
                $this->y = $y + round($h/2);
                $this->x = $rb_x;
                break;
            }
            case 'B': {
                $this->y = $rb_y;
                $this->x = $rb_x;
                break;
            }
            case 'N': {
                $this->SetY($rb_y);
                break;
            }
            default:{
                break;
            }
        }
    }

    /**
     * Set the percentage of character stretching.
     * @param $perc (int) percentage of stretching (100 = no stretching)
     * @author Nicola Asuni
     * @public
     * @since 5.9.000 (2010-09-29)
     */
    public function setFontStretching($perc=100) {
        $this->font_stretching = $perc;
    }

    /**
     * Get the percentage of character stretching.
     * @return float stretching value
     * @author Nicola Asuni
     * @public
     * @since 5.9.000 (2010-09-29)
     */
    public function getFontStretching() {
        return $this->font_stretching;
    }

    /**
     * Set the amount to increase or decrease the space between characters in a text.
     * @param $spacing (float) amount to increase or decrease the space between characters in a text (0 = default spacing)
     * @author Nicola Asuni
     * @public
     * @since 5.9.000 (2010-09-29)
     */
    public function setFontSpacing($spacing=0) {
        $this->font_spacing = $spacing;
    }

    /**
     * Get the amount to increase or decrease the space between characters in a text.
     * @return int font spacing (tracking) value
     * @author Nicola Asuni
     * @public
     * @since 5.9.000 (2010-09-29)
     */
    public function getFontSpacing() {
        return $this->font_spacing;
    }

    /**
     * Return an array of no-write page regions
     * @return array of no-write page regions
     * @author Nicola Asuni
     * @public
     * @since 5.9.003 (2010-10-13)
     * @see setPageRegions(), addPageRegion()
     */
    public function getPageRegions() {
        return $this->page_regions;
    }

    /**
     * Set no-write regions on page.
     * A no-write region is a portion of the page with a rectangular or trapezium shape that will not be covered when writing text or html code.
     * A region is always aligned on the left or right side of the page ad is defined using a vertical segment.
     * You can set multiple regions for the same page.
     * @param $regions (array) array of no-write regions. For each region you can define an array as follow: ('page' => page number or empy for current page, 'xt' => X top, 'yt' => Y top, 'xb' => X bottom, 'yb' => Y bottom, 'side' => page side 'L' = left or 'R' = right). Omit this parameter to remove all regions.
     * @author Nicola Asuni
     * @public
     * @since 5.9.003 (2010-10-13)
     * @see addPageRegion(), getPageRegions()
     */
    public function setPageRegions($regions=array()) {
        // empty current regions array
        $this->page_regions = array();
        // add regions
        foreach ($regions as $data) {
            $this->addPageRegion($data);
        }
    }

    /**
     * Add a single no-write region on selected page.
     * A no-write region is a portion of the page with a rectangular or trapezium shape that will not be covered when writing text or html code.
     * A region is always aligned on the left or right side of the page ad is defined using a vertical segment.
     * You can set multiple regions for the same page.
     * @param $region (array) array of a single no-write region array: ('page' => page number or empy for current page, 'xt' => X top, 'yt' => Y top, 'xb' => X bottom, 'yb' => Y bottom, 'side' => page side 'L' = left or 'R' = right).
     * @author Nicola Asuni
     * @public
     * @since 5.9.003 (2010-10-13)
     * @see setPageRegions(), getPageRegions()
     */
    public function addPageRegion($region) {
        if (!isset($region['page']) OR empty($region['page'])) {
            $region['page'] = $this->page;
        }
        if (isset($region['xt']) AND isset($region['xb']) AND ($region['xt'] > 0) AND ($region['xb'] > 0)
            AND isset($region['yt'])  AND isset($region['yb']) AND ($region['yt'] >= 0) AND ($region['yt'] < $region['yb'])
            AND isset($region['side']) AND (($region['side'] == 'L') OR ($region['side'] == 'R'))) {
            $this->page_regions[] = $region;
        }
    }

    /**
     * Remove a single no-write region.
     * @param $key (int) region key
     * @author Nicola Asuni
     * @public
     * @since 5.9.003 (2010-10-13)
     * @see setPageRegions(), getPageRegions()
     */
    public function removePageRegion($key) {
        if (isset($this->page_regions[$key])) {
            unset($this->page_regions[$key]);
        }
    }

    /**
     * Check page for no-write regions and adapt current coordinates and page margins if necessary.
     * A no-write region is a portion of the page with a rectangular or trapezium shape that will not be covered when writing text or html code.
     * A region is always aligned on the left or right side of the page ad is defined using a vertical segment.
     * @param $h (float) height of the text/image/object to print in user units
     * @param $x (float) current X coordinate in user units
     * @param $y (float) current Y coordinate in user units
     * @return array($x, $y)
     * @author Nicola Asuni
     * @protected
     * @since 5.9.003 (2010-10-13)
     */
    protected function checkPageRegions($h, $x, $y) {
        // set default values
        if ($x === '') {
            $x = $this->x;
        }
        if ($y === '') {
            $y = $this->y;
        }
        if (!$this->check_page_regions OR empty($this->page_regions)) {
            // no page regions defined
            return array($x, $y);
        }
        if (empty($h)) {
            $h = $this->getCellHeight($this->FontSize);
        }
        // check for page break
        if ($this->checkPageBreak($h, $y)) {
            // the content will be printed on a new page
            $x = $this->x;
            $y = $this->y;
        }
        if ($this->num_columns > 1) {
            if ($this->rtl) {
                $this->lMargin = ($this->columns[$this->current_column]['x'] - $this->columns[$this->current_column]['w']);
            } else {
                $this->rMargin = ($this->w - $this->columns[$this->current_column]['x'] - $this->columns[$this->current_column]['w']);
            }
        } else {
            if ($this->rtl) {
                $this->lMargin = max($this->clMargin, $this->original_lMargin);
            } else {
                $this->rMargin = max($this->crMargin, $this->original_rMargin);
            }
        }
        // adjust coordinates and page margins
        foreach ($this->page_regions as $regid => $regdata) {
            if ($regdata['page'] == $this->page) {
                // check region boundaries
                if (($y > ($regdata['yt'] - $h)) AND ($y <= $regdata['yb'])) {
                    // Y is inside the region
                    $minv = ($regdata['xb'] - $regdata['xt']) / ($regdata['yb'] - $regdata['yt']); // inverse of angular coefficient
                    $yt = max($y, $regdata['yt']);
                    $yb = min(($yt + $h), $regdata['yb']);
                    $xt = (($yt - $regdata['yt']) * $minv) + $regdata['xt'];
                    $xb = (($yb - $regdata['yt']) * $minv) + $regdata['xt'];
                    if ($regdata['side'] == 'L') { // left side
                        $new_margin = max($xt, $xb);
                        if ($this->lMargin < $new_margin) {
                            if ($this->rtl) {
                                // adjust left page margin
                                $this->lMargin = max(0, $new_margin);
                            }
                            if ($x < $new_margin) {
                                // adjust x position
                                $x = $new_margin;
                                if ($new_margin > ($this->w - $this->rMargin)) {
                                    // adjust y position
                                    $y = $regdata['yb'] - $h;
                                }
                            }
                        }
                    } elseif ($regdata['side'] == 'R') { // right side
                        $new_margin = min($xt, $xb);
                        if (($this->w - $this->rMargin) > $new_margin) {
                            if (!$this->rtl) {
                                // adjust right page margin
                                $this->rMargin = max(0, ($this->w - $new_margin));
                            }
                            if ($x > $new_margin) {
                                // adjust x position
                                $x = $new_margin;
                                if ($new_margin > $this->lMargin) {
                                    // adjust y position
                                    $y = $regdata['yb'] - $h;
                                }
                            }
                        }
                    }
                }
            }
        }
        return array($x, $y);
    }

    // --- SVG METHODS ---------------------------------------------------------

    /**
     * Embedd a Scalable Vector Graphics (SVG) image.
     * NOTE: SVG standard is not yet fully implemented, use the setRasterizeVectorImages() method to enable/disable rasterization of vector images using ImageMagick library.
     * @param $file (string) Name of the SVG file or a '@' character followed by the SVG data string.
     * @param $x (float) Abscissa of the upper-left corner.
     * @param $y (float) Ordinate of the upper-left corner.
     * @param $w (float) Width of the image in the page. If not specified or equal to zero, it is automatically calculated.
     * @param $h (float) Height of the image in the page. If not specified or equal to zero, it is automatically calculated.
     * @param $link (mixed) URL or identifier returned by AddLink().
     * @param $align (string) Indicates the alignment of the pointer next to image insertion relative to image height. The value can be:<ul><li>T: top-right for LTR or top-left for RTL</li><li>M: middle-right for LTR or middle-left for RTL</li><li>B: bottom-right for LTR or bottom-left for RTL</li><li>N: next line</li></ul> If the alignment is an empty string, then the pointer will be restored on the starting SVG position.
     * @param $palign (string) Allows to center or align the image on the current line. Possible values are:<ul><li>L : left align</li><li>C : center</li><li>R : right align</li><li>'' : empty string : left for LTR or right for RTL</li></ul>
     * @param $border (mixed) Indicates if borders must be drawn around the cell. The value can be a number:<ul><li>0: no border (default)</li><li>1: frame</li></ul> or a string containing some or all of the following characters (in any order):<ul><li>L: left</li><li>T: top</li><li>R: right</li><li>B: bottom</li></ul> or an array of line styles for each border group - for example: array('LTRB' => array('width' => 2, 'cap' => 'butt', 'join' => 'miter', 'dash' => 0, 'color' => array(0, 0, 0)))
     * @param $fitonpage (boolean) if true the image is resized to not exceed page dimensions.
     * @author Nicola Asuni
     * @since 5.0.000 (2010-05-02)
     * @public
     */
    public function ImageSVG($file, $x='', $y='', $w=0, $h=0, $link='', $align='', $palign='', $border=0, $fitonpage=false) {
        if ($this->state != 2) {
             return;
        }
        // reset SVG vars
        $this->svggradients = array();
        $this->svggradientid = 0;
        $this->svgdefsmode = false;
        $this->svgdefs = array();
        $this->svgclipmode = false;
        $this->svgclippaths = array();
        $this->svgcliptm = array();
        $this->svgclipid = 0;
        $this->svgtext = '';
        $this->svgtextmode = array();
        if ($this->rasterize_vector_images AND ($w > 0) AND ($h > 0)) {
            // convert SVG to raster image using GD or ImageMagick libraries
            return $this->Image($file, $x, $y, $w, $h, 'SVG', $link, $align, true, 300, $palign, false, false, $border, false, false, false);
        }
        if ($file[0] === '@') { // image from string
            $this->svgdir = '';
            $svgdata = substr($file, 1);
        } else { // SVG file
            $this->svgdir = dirname($file);
            $svgdata = TCPDF_STATIC::fileGetContents($file);
        }
        if ($svgdata === FALSE) {
            $this->Error('SVG file not found: '.$file);
        }
        if ($x === '') {
            $x = $this->x;
        }
        if ($y === '') {
            $y = $this->y;
        }
        // check page for no-write regions and adapt page margins if necessary
        list($x, $y) = $this->checkPageRegions($h, $x, $y);
        $k = $this->k;
        $ox = 0;
        $oy = 0;
        $ow = $w;
        $oh = $h;
        $aspect_ratio_align = 'xMidYMid';
        $aspect_ratio_ms = 'meet';
        $regs = array();
        // get original image width and height
        preg_match('/<svg([^\>]*)>/si', $svgdata, $regs);
        if (isset($regs[1]) AND !empty($regs[1])) {
            $tmp = array();
            if (preg_match('/[\s]+x[\s]*=[\s]*"([^"]*)"/si', $regs[1], $tmp)) {
                $ox = $this->getHTMLUnitToUnits($tmp[1], 0, $this->svgunit, false);
            }
            $tmp = array();
            if (preg_match('/[\s]+y[\s]*=[\s]*"([^"]*)"/si', $regs[1], $tmp)) {
                $oy = $this->getHTMLUnitToUnits($tmp[1], 0, $this->svgunit, false);
            }
            $tmp = array();
            if (preg_match('/[\s]+width[\s]*=[\s]*"([^"]*)"/si', $regs[1], $tmp)) {
                $ow = $this->getHTMLUnitToUnits($tmp[1], 1, $this->svgunit, false);
            }
            $tmp = array();
            if (preg_match('/[\s]+height[\s]*=[\s]*"([^"]*)"/si', $regs[1], $tmp)) {
                $oh = $this->getHTMLUnitToUnits($tmp[1], 1, $this->svgunit, false);
            }
            $tmp = array();
            $view_box = array();
            if (preg_match('/[\s]+viewBox[\s]*=[\s]*"[\s]*([0-9\.\-]+)[\s]+([0-9\.\-]+)[\s]+([0-9\.]+)[\s]+([0-9\.]+)[\s]*"/si', $regs[1], $tmp)) {
                if (count($tmp) == 5) {
                    array_shift($tmp);
                    foreach ($tmp as $key => $val) {
                        $view_box[$key] = $this->getHTMLUnitToUnits($val, 0, $this->svgunit, false);
                    }
                    $ox = $view_box[0];
                    $oy = $view_box[1];
                }
                // get aspect ratio
                $tmp = array();
                if (preg_match('/[\s]+preserveAspectRatio[\s]*=[\s]*"([^"]*)"/si', $regs[1], $tmp)) {
                    $aspect_ratio = preg_split('/[\s]+/si', $tmp[1]);
                    switch (count($aspect_ratio)) {
                        case 3: {
                            $aspect_ratio_align = $aspect_ratio[1];
                            $aspect_ratio_ms = $aspect_ratio[2];
                            break;
                        }
                        case 2: {
                            $aspect_ratio_align = $aspect_ratio[0];
                            $aspect_ratio_ms = $aspect_ratio[1];
                            break;
                        }
                        case 1: {
                            $aspect_ratio_align = $aspect_ratio[0];
                            $aspect_ratio_ms = 'meet';
                            break;
                        }
                    }
                }
            }
        }
        if ($ow <= 0) {
            $ow = 1;
        }
        if ($oh <= 0) {
            $oh = 1;
        }
        // calculate image width and height on document
        if (($w <= 0) AND ($h <= 0)) {
            // convert image size to document unit
            $w = $ow;
            $h = $oh;
        } elseif ($w <= 0) {
            $w = $h * $ow / $oh;
        } elseif ($h <= 0) {
            $h = $w * $oh / $ow;
        }
        // fit the image on available space
        list($w, $h, $x, $y) = $this->fitBlock($w, $h, $x, $y, $fitonpage);
        if ($this->rasterize_vector_images) {
            // convert SVG to raster image using GD or ImageMagick libraries
            return $this->Image($file, $x, $y, $w, $h, 'SVG', $link, $align, true, 300, $palign, false, false, $border, false, false, false);
        }
        // set alignment
        $this->img_rb_y = $y + $h;
        // set alignment
        if ($this->rtl) {
            if ($palign == 'L') {
                $ximg = $this->lMargin;
            } elseif ($palign == 'C') {
                $ximg = ($this->w + $this->lMargin - $this->rMargin - $w) / 2;
            } elseif ($palign == 'R') {
                $ximg = $this->w - $this->rMargin - $w;
            } else {
                $ximg = $x - $w;
            }
            $this->img_rb_x = $ximg;
        } else {
            if ($palign == 'L') {
                $ximg = $this->lMargin;
            } elseif ($palign == 'C') {
                $ximg = ($this->w + $this->lMargin - $this->rMargin - $w) / 2;
            } elseif ($palign == 'R') {
                $ximg = $this->w - $this->rMargin - $w;
            } else {
                $ximg = $x;
            }
            $this->img_rb_x = $ximg + $w;
        }
        // store current graphic vars
        $gvars = $this->getGraphicVars();
        // store SVG position and scale factors
        $svgoffset_x = ($ximg - $ox) * $this->k;
        $svgoffset_y = -($y - $oy) * $this->k;
        if (isset($view_box[2]) AND ($view_box[2] > 0) AND ($view_box[3] > 0)) {
            $ow = $view_box[2];
            $oh = $view_box[3];
        } else {
            if ($ow <= 0) {
                $ow = $w;
            }
            if ($oh <= 0) {
                $oh = $h;
            }
        }
        $svgscale_x = $w / $ow;
        $svgscale_y = $h / $oh;
        // scaling and alignment
        if ($aspect_ratio_align != 'none') {
            // store current scaling values
            $svgscale_old_x = $svgscale_x;
            $svgscale_old_y = $svgscale_y;
            // force uniform scaling
            if ($aspect_ratio_ms == 'slice') {
                // the entire viewport is covered by the viewBox
                if ($svgscale_x > $svgscale_y) {
                    $svgscale_y = $svgscale_x;
                } elseif ($svgscale_x < $svgscale_y) {
                    $svgscale_x = $svgscale_y;
                }
            } else { // meet
                // the entire viewBox is visible within the viewport
                if ($svgscale_x < $svgscale_y) {
                    $svgscale_y = $svgscale_x;
                } elseif ($svgscale_x > $svgscale_y) {
                    $svgscale_x = $svgscale_y;
                }
            }
            // correct X alignment
            switch (substr($aspect_ratio_align, 1, 3)) {
                case 'Min': {
                    // do nothing
                    break;
                }
                case 'Max': {
                    $svgoffset_x += (($w * $this->k) - ($ow * $this->k * $svgscale_x));
                    break;
                }
                default:
                case 'Mid': {
                    $svgoffset_x += ((($w * $this->k) - ($ow * $this->k * $svgscale_x)) / 2);
                    break;
                }
            }
            // correct Y alignment
            switch (substr($aspect_ratio_align, 5)) {
                case 'Min': {
                    // do nothing
                    break;
                }
                case 'Max': {
                    $svgoffset_y -= (($h * $this->k) - ($oh * $this->k * $svgscale_y));
                    break;
                }
                default:
                case 'Mid': {
                    $svgoffset_y -= ((($h * $this->k) - ($oh * $this->k * $svgscale_y)) / 2);
                    break;
                }
            }
        }
        // store current page break mode
        $page_break_mode = $this->AutoPageBreak;
        $page_break_margin = $this->getBreakMargin();
        $cell_padding = $this->cell_padding;
        $this->SetCellPadding(0);
        $this->SetAutoPageBreak(false);
        // save the current graphic state
        $this->_out('q'.$this->epsmarker);
        // set initial clipping mask
        $this->Rect($ximg, $y, $w, $h, 'CNZ', array(), array());
        // scale and translate
        $e = $ox * $this->k * (1 - $svgscale_x);
        $f = ($this->h - $oy) * $this->k * (1 - $svgscale_y);
        $this->_out(sprintf('%F %F %F %F %F %F cm', $svgscale_x, 0, 0, $svgscale_y, ($e + $svgoffset_x), ($f + $svgoffset_y)));
        // creates a new XML parser to be used by the other XML functions
        $this->parser = xml_parser_create('UTF-8');
        // the following function allows to use parser inside object
        xml_set_object($this->parser, $this);
        // disable case-folding for this XML parser
        xml_parser_set_option($this->parser, XML_OPTION_CASE_FOLDING, 0);
        // sets the element handler functions for the XML parser
        xml_set_element_handler($this->parser, 'startSVGElementHandler', 'endSVGElementHandler');
        // sets the character data handler function for the XML parser
        xml_set_character_data_handler($this->parser, 'segSVGContentHandler');
        // start parsing an XML document
        if (!xml_parse($this->parser, $svgdata)) {
            $error_message = sprintf('SVG Error: %s at line %d', xml_error_string(xml_get_error_code($this->parser)), xml_get_current_line_number($this->parser));
            $this->Error($error_message);
        }
        // free this XML parser
        xml_parser_free($this->parser);
        // restore previous graphic state
        $this->_out($this->epsmarker.'Q');
        // restore graphic vars
        $this->setGraphicVars($gvars);
        $this->lasth = $gvars['lasth'];
        if (!empty($border)) {
            $bx = $this->x;
            $by = $this->y;
            $this->x = $ximg;
            if ($this->rtl) {
                $this->x += $w;
            }
            $this->y = $y;
            $this->Cell($w, $h, '', $border, 0, '', 0, '', 0, true);
            $this->x = $bx;
            $this->y = $by;
        }
        if ($link) {
            $this->Link($ximg, $y, $w, $h, $link, 0);
        }
        // set pointer to align the next text/objects
        switch($align) {
            case 'T':{
                $this->y = $y;
                $this->x = $this->img_rb_x;
                break;
            }
            case 'M':{
                $this->y = $y + round($h/2);
                $this->x = $this->img_rb_x;
                break;
            }
            case 'B':{
                $this->y = $this->img_rb_y;
                $this->x = $this->img_rb_x;
                break;
            }
            case 'N':{
                $this->SetY($this->img_rb_y);
                break;
            }
            default:{
                // restore pointer to starting position
                $this->x = $gvars['x'];
                $this->y = $gvars['y'];
                $this->page = $gvars['page'];
                $this->current_column = $gvars['current_column'];
                $this->tMargin = $gvars['tMargin'];
                $this->bMargin = $gvars['bMargin'];
                $this->w = $gvars['w'];
                $this->h = $gvars['h'];
                $this->wPt = $gvars['wPt'];
                $this->hPt = $gvars['hPt'];
                $this->fwPt = $gvars['fwPt'];
                $this->fhPt = $gvars['fhPt'];
                break;
            }
        }
        $this->endlinex = $this->img_rb_x;
        // restore page break
        $this->SetAutoPageBreak($page_break_mode, $page_break_margin);
        $this->cell_padding = $cell_padding;
    }

    /**
     * Convert SVG transformation matrix to PDF.
     * @param $tm (array) original SVG transformation matrix
     * @return array transformation matrix
     * @protected
     * @since 5.0.000 (2010-05-02)
     */
    protected function convertSVGtMatrix($tm) {
        $a = $tm[0];
        $b = -$tm[1];
        $c = -$tm[2];
        $d = $tm[3];
        $e = $this->getHTMLUnitToUnits($tm[4], 1, $this->svgunit, false) * $this->k;
        $f = -$this->getHTMLUnitToUnits($tm[5], 1, $this->svgunit, false) * $this->k;
        $x = 0;
        $y = $this->h * $this->k;
        $e = ($x * (1 - $a)) - ($y * $c) + $e;
        $f = ($y * (1 - $d)) - ($x * $b) + $f;
        return array($a, $b, $c, $d, $e, $f);
    }

    /**
     * Apply SVG graphic transformation matrix.
     * @param $tm (array) original SVG transformation matrix
     * @protected
     * @since 5.0.000 (2010-05-02)
     */
    protected function SVGTransform($tm) {
        $this->Transform($this->convertSVGtMatrix($tm));
    }

    /**
     * Apply the requested SVG styles (*** TO BE COMPLETED ***)
     * @param $svgstyle (array) array of SVG styles to apply
     * @param $prevsvgstyle (array) array of previous SVG style
     * @param $x (int) X origin of the bounding box
     * @param $y (int) Y origin of the bounding box
     * @param $w (int) width of the bounding box
     * @param $h (int) height of the bounding box
     * @param $clip_function (string) clip function
     * @param $clip_params (array) array of parameters for clipping function
     * @return object style
     * @author Nicola Asuni
     * @since 5.0.000 (2010-05-02)
     * @protected
     */
    protected function setSVGStyles($svgstyle, $prevsvgstyle, $x=0, $y=0, $w=1, $h=1, $clip_function='', $clip_params=array()) {
        if ($this->state != 2) {
             return;
        }
        $objstyle = '';
        $minlen = (0.01 / $this->k); // minimum acceptable length
        if (!isset($svgstyle['opacity'])) {
            return $objstyle;
        }
        // clip-path
        $regs = array();
        if (preg_match('/url\([\s]*\#([^\)]*)\)/si', $svgstyle['clip-path'], $regs)) {
            $clip_path = $this->svgclippaths[$regs[1]];
            foreach ($clip_path as $cp) {
                $this->startSVGElementHandler('clip-path', $cp['name'], $cp['attribs'], $cp['tm']);
            }
        }
        // opacity
        if ($svgstyle['opacity'] != 1) {
            $this->setAlpha($svgstyle['opacity'], 'Normal', $svgstyle['opacity'], false);
        }
        // color
        $fill_color = TCPDF_COLORS::convertHTMLColorToDec($svgstyle['color'], $this->spot_colors);
        $this->SetFillColorArray($fill_color);
        // text color
        $text_color = TCPDF_COLORS::convertHTMLColorToDec($svgstyle['text-color'], $this->spot_colors);
        $this->SetTextColorArray($text_color);
        // clip
        if (preg_match('/rect\(([a-z0-9\-\.]*)[\s]*([a-z0-9\-\.]*)[\s]*([a-z0-9\-\.]*)[\s]*([a-z0-9\-\.]*)\)/si', $svgstyle['clip'], $regs)) {
            $top = (isset($regs[1])?$this->getHTMLUnitToUnits($regs[1], 0, $this->svgunit, false):0);
            $right = (isset($regs[2])?$this->getHTMLUnitToUnits($regs[2], 0, $this->svgunit, false):0);
            $bottom = (isset($regs[3])?$this->getHTMLUnitToUnits($regs[3], 0, $this->svgunit, false):0);
            $left = (isset($regs[4])?$this->getHTMLUnitToUnits($regs[4], 0, $this->svgunit, false):0);
            $cx = $x + $left;
            $cy = $y + $top;
            $cw = $w - $left - $right;
            $ch = $h - $top - $bottom;
            if ($svgstyle['clip-rule'] == 'evenodd') {
                $clip_rule = 'CNZ';
            } else {
                $clip_rule = 'CEO';
            }
            $this->Rect($cx, $cy, $cw, $ch, $clip_rule, array(), array());
        }
        // fill
        $regs = array();
        if (preg_match('/url\([\s]*\#([^\)]*)\)/si', $svgstyle['fill'], $regs)) {
            // gradient
            $gradient = $this->svggradients[$regs[1]];
            if (isset($gradient['xref'])) {
                // reference to another gradient definition
                $newgradient = $this->svggradients[$gradient['xref']];
                $newgradient['coords'] = $gradient['coords'];
                $newgradient['mode'] = $gradient['mode'];
                $newgradient['type'] = $gradient['type'];
                $newgradient['gradientUnits'] = $gradient['gradientUnits'];
                if (isset($gradient['gradientTransform'])) {
                    $newgradient['gradientTransform'] = $gradient['gradientTransform'];
                }
                $gradient = $newgradient;
            }
            //save current Graphic State
            $this->_outSaveGraphicsState();
            //set clipping area
            if (!empty($clip_function) AND method_exists($this, $clip_function)) {
                $bbox = call_user_func_array(array($this, $clip_function), $clip_params);
                if ((!isset($gradient['type']) OR ($gradient['type'] != 3)) AND is_array($bbox) AND (count($bbox) == 4)) {
                    list($x, $y, $w, $h) = $bbox;
                }
            }
            if ($gradient['mode'] == 'measure') {
                if (!isset($gradient['coords'][4])) {
                    $gradient['coords'][4] = 0.5;
                }
                if (isset($gradient['gradientTransform']) AND !empty($gradient['gradientTransform'])) {
                    $gtm = $gradient['gradientTransform'];
                    // apply transformation matrix
                    $xa = ($gtm[0] * $gradient['coords'][0]) + ($gtm[2] * $gradient['coords'][1]) + $gtm[4];
                    $ya = ($gtm[1] * $gradient['coords'][0]) + ($gtm[3] * $gradient['coords'][1]) + $gtm[5];
                    $xb = ($gtm[0] * $gradient['coords'][2]) + ($gtm[2] * $gradient['coords'][3]) + $gtm[4];
                    $yb = ($gtm[1] * $gradient['coords'][2]) + ($gtm[3] * $gradient['coords'][3]) + $gtm[5];
                    $r = sqrt(pow(($gtm[0] * $gradient['coords'][4]), 2) + pow(($gtm[1] * $gradient['coords'][4]), 2));
                    $gradient['coords'][0] = $xa;
                    $gradient['coords'][1] = $ya;
                    $gradient['coords'][2] = $xb;
                    $gradient['coords'][3] = $yb;
                    $gradient['coords'][4] = $r;
                }
                // convert SVG coordinates to user units
                $gradient['coords'][0] = $this->getHTMLUnitToUnits($gradient['coords'][0], 0, $this->svgunit, false);
                $gradient['coords'][1] = $this->getHTMLUnitToUnits($gradient['coords'][1], 0, $this->svgunit, false);
                $gradient['coords'][2] = $this->getHTMLUnitToUnits($gradient['coords'][2], 0, $this->svgunit, false);
                $gradient['coords'][3] = $this->getHTMLUnitToUnits($gradient['coords'][3], 0, $this->svgunit, false);
                $gradient['coords'][4] = $this->getHTMLUnitToUnits($gradient['coords'][4], 0, $this->svgunit, false);
                if ($w <= $minlen) {
                    $w = $minlen;
                }
                if ($h <= $minlen) {
                    $h = $minlen;
                }
                // shift units
                if ($gradient['gradientUnits'] == 'objectBoundingBox') {
                    // convert to SVG coordinate system
                    $gradient['coords'][0] += $x;
                    $gradient['coords'][1] += $y;
                    $gradient['coords'][2] += $x;
                    $gradient['coords'][3] += $y;
                }
                // calculate percentages
                $gradient['coords'][0] = (($gradient['coords'][0] - $x) / $w);
                $gradient['coords'][1] = (($gradient['coords'][1] - $y) / $h);
                $gradient['coords'][2] = (($gradient['coords'][2] - $x) / $w);
                $gradient['coords'][3] = (($gradient['coords'][3] - $y) / $h);
                $gradient['coords'][4] /= $w;
            } elseif ($gradient['mode'] == 'percentage') {
                foreach($gradient['coords'] as $key => $val) {
                    $gradient['coords'][$key] = (intval($val) / 100);
                    if ($val < 0) {
                        $gradient['coords'][$key] = 0;
                    } elseif ($val > 1) {
                        $gradient['coords'][$key] = 1;
                    }
                }
            }
            if (($gradient['type'] == 2) AND ($gradient['coords'][0] == $gradient['coords'][2]) AND ($gradient['coords'][1] == $gradient['coords'][3])) {
                // single color (no shading)
                $gradient['coords'][0] = 1;
                $gradient['coords'][1] = 0;
                $gradient['coords'][2] = 0.999;
                $gradient['coords'][3] = 0;
            }
            // swap Y coordinates
            $tmp = $gradient['coords'][1];
            $gradient['coords'][1] = $gradient['coords'][3];
            $gradient['coords'][3] = $tmp;
            // set transformation map for gradient
            $cy = ($this->h - $y);
            if ($gradient['type'] == 3) {
                // circular gradient
                $cy -= ($gradient['coords'][1] * ($w + $h));
                $h = $w = max($w, $h);
            } else {
                $cy -= $h;
            }
            $this->_out(sprintf('%F 0 0 %F %F %F cm', ($w * $this->k), ($h * $this->k), ($x * $this->k), ($cy * $this->k)));
            if (count($gradient['stops']) > 1) {
                $this->Gradient($gradient['type'], $gradient['coords'], $gradient['stops'], array(), false);
            }
        } elseif ($svgstyle['fill'] != 'none') {
            $fill_color = TCPDF_COLORS::convertHTMLColorToDec($svgstyle['fill'], $this->spot_colors);
            if ($svgstyle['fill-opacity'] != 1) {
                $this->setAlpha($this->alpha['CA'], 'Normal', $svgstyle['fill-opacity'], false);
            }
            $this->SetFillColorArray($fill_color);
            if ($svgstyle['fill-rule'] == 'evenodd') {
                $objstyle .= 'F*';
            } else {
                $objstyle .= 'F';
            }
        }
        // stroke
        if ($svgstyle['stroke'] != 'none') {
            if ($svgstyle['stroke-opacity'] != 1) {
                $this->setAlpha($svgstyle['stroke-opacity'], 'Normal', $this->alpha['ca'], false);
            } elseif (preg_match('/rgba\(\d+%?,\s*\d+%?,\s*\d+%?,\s*(\d+(?:\.\d+)?)\)/i', $svgstyle['stroke'], $rgba_matches)) {
                $this->setAlpha($rgba_matches[1], 'Normal', $this->alpha['ca'], false);
            }
            $stroke_style = array(
                'color' => TCPDF_COLORS::convertHTMLColorToDec($svgstyle['stroke'], $this->spot_colors),
                'width' => $this->getHTMLUnitToUnits($svgstyle['stroke-width'], 0, $this->svgunit, false),
                'cap' => $svgstyle['stroke-linecap'],
                'join' => $svgstyle['stroke-linejoin']
                );
            if (isset($svgstyle['stroke-dasharray']) AND !empty($svgstyle['stroke-dasharray']) AND ($svgstyle['stroke-dasharray'] != 'none')) {
                $stroke_style['dash'] = $svgstyle['stroke-dasharray'];
            }
            $this->SetLineStyle($stroke_style);
            $objstyle .= 'D';
        }
        // font
        $regs = array();
        if (!empty($svgstyle['font'])) {
            if (preg_match('/font-family[\s]*:[\s]*([^\;\"]*)/si', $svgstyle['font'], $regs)) {
                $font_family = $this->getFontFamilyName($regs[1]);
            } else {
                $font_family = $svgstyle['font-family'];
            }
            if (preg_match('/font-size[\s]*:[\s]*([^\s\;\"]*)/si', $svgstyle['font'], $regs)) {
                $font_size = trim($regs[1]);
            } else {
                $font_size = $svgstyle['font-size'];
            }
            if (preg_match('/font-style[\s]*:[\s]*([^\s\;\"]*)/si', $svgstyle['font'], $regs)) {
                $font_style = trim($regs[1]);
            } else {
                $font_style = $svgstyle['font-style'];
            }
            if (preg_match('/font-weight[\s]*:[\s]*([^\s\;\"]*)/si', $svgstyle['font'], $regs)) {
                $font_weight = trim($regs[1]);
            } else {
                $font_weight = $svgstyle['font-weight'];
            }
            if (preg_match('/font-stretch[\s]*:[\s]*([^\s\;\"]*)/si', $svgstyle['font'], $regs)) {
                $font_stretch = trim($regs[1]);
            } else {
                $font_stretch = $svgstyle['font-stretch'];
            }
            if (preg_match('/letter-spacing[\s]*:[\s]*([^\s\;\"]*)/si', $svgstyle['font'], $regs)) {
                $font_spacing = trim($regs[1]);
            } else {
                $font_spacing = $svgstyle['letter-spacing'];
            }
        } else {
            $font_family = $this->getFontFamilyName($svgstyle['font-family']);
            $font_size = $svgstyle['font-size'];
            $font_style = $svgstyle['font-style'];
            $font_weight = $svgstyle['font-weight'];
            $font_stretch = $svgstyle['font-stretch'];
            $font_spacing = $svgstyle['letter-spacing'];
        }
        $font_size = $this->getHTMLFontUnits($font_size, $this->svgstyles[0]['font-size'], $prevsvgstyle['font-size'], $this->svgunit);
        $font_stretch = $this->getCSSFontStretching($font_stretch, $svgstyle['font-stretch']);
        $font_spacing = $this->getCSSFontSpacing($font_spacing, $svgstyle['letter-spacing']);
        switch ($font_style) {
            case 'italic': {
                $font_style = 'I';
                break;
            }
            case 'oblique': {
                $font_style = 'I';
                break;
            }
            default:
            case 'normal': {
                $font_style = '';
                break;
            }
        }
        switch ($font_weight) {
            case 'bold':
            case 'bolder': {
                $font_style .= 'B';
                break;
            }
            case 'normal': {
                if ((substr($font_family, -1) == 'I') AND (substr($font_family, -2, 1) == 'B')) {
                    $font_family = substr($font_family, 0, -2).'I';
                } elseif (substr($font_family, -1) == 'B') {
                    $font_family = substr($font_family, 0, -1);
                }
                break;
            }
        }
        switch ($svgstyle['text-decoration']) {
            case 'underline': {
                $font_style .= 'U';
                break;
            }
            case 'overline': {
                $font_style .= 'O';
                break;
            }
            case 'line-through': {
                $font_style .= 'D';
                break;
            }
            default:
            case 'none': {
                break;
            }
        }
        $this->SetFont($font_family, $font_style, $font_size);
        $this->setFontStretching($font_stretch);
        $this->setFontSpacing($font_spacing);
        return $objstyle;
    }

    /**
     * Draws an SVG path
     * @param $d (string) attribute d of the path SVG element
     * @param $style (string) Style of rendering. Possible values are:
     * <ul>
     *   <li>D or empty string: Draw (default).</li>
     *   <li>F: Fill.</li>
     *   <li>F*: Fill using the even-odd rule to determine which regions lie inside the clipping path.</li>
     *   <li>DF or FD: Draw and fill.</li>
     *   <li>DF* or FD*: Draw and fill using the even-odd rule to determine which regions lie inside the clipping path.</li>
     *   <li>CNZ: Clipping mode (using the even-odd rule to determine which regions lie inside the clipping path).</li>
     *   <li>CEO: Clipping mode (using the nonzero winding number rule to determine which regions lie inside the clipping path).</li>
     * </ul>
     * @return array of container box measures (x, y, w, h)
     * @author Nicola Asuni
     * @since 5.0.000 (2010-05-02)
     * @protected
     */
    protected function SVGPath($d, $style='') {
        if ($this->state != 2) {
             return;
        }
        // set fill/stroke style
        $op = TCPDF_STATIC::getPathPaintOperator($style, '');
        if (empty($op)) {
            return;
        }
        $paths = array();
        $d = preg_replace('/([0-9ACHLMQSTVZ])([\-\+])/si', '\\1 \\2', $d);
        preg_match_all('/([ACHLMQSTVZ])[\s]*([^ACHLMQSTVZ\"]*)/si', $d, $paths, PREG_SET_ORDER);
        $x = 0;
        $y = 0;
        $x1 = 0;
        $y1 = 0;
        $x2 = 0;
        $y2 = 0;
        $xmin = 2147483647;
        $xmax = 0;
        $ymin = 2147483647;
        $ymax = 0;
        $relcoord = false;
        $minlen = (0.01 / $this->k); // minimum acceptable length (3 point)
        $firstcmd = true; // used to print first point
        // draw curve pieces
        foreach ($paths as $key => $val) {
            // get curve type
            $cmd = trim($val[1]);
            if (strtolower($cmd) == $cmd) {
                // use relative coordinated instead of absolute
                $relcoord = true;
                $xoffset = $x;
                $yoffset = $y;
            } else {
                $relcoord = false;
                $xoffset = 0;
                $yoffset = 0;
            }
            $params = array();
            if (isset($val[2])) {
                // get curve parameters
                $rawparams = preg_split('/([\,\s]+)/si', trim($val[2]));
                $params = array();
                foreach ($rawparams as $ck => $cp) {
                    $params[$ck] = $this->getHTMLUnitToUnits($cp, 0, $this->svgunit, false);
                    if (abs($params[$ck]) < $minlen) {
                        // approximate little values to zero
                        $params[$ck] = 0;
                    }
                }
            }
            // store current origin point
            $x0 = $x;
            $y0 = $y;
            switch (strtoupper($cmd)) {
                case 'M': { // moveto
                    foreach ($params as $ck => $cp) {
                        if (($ck % 2) == 0) {
                            $x = $cp + $xoffset;
                        } else {
                            $y = $cp + $yoffset;
                            if ($firstcmd OR (abs($x0 - $x) >= $minlen) OR (abs($y0 - $y) >= $minlen)) {
                                if ($ck == 1) {
                                    $this->_outPoint($x, $y);
                                    $firstcmd = false;
                                } else {
                                    $this->_outLine($x, $y);
                                }
                                $x0 = $x;
                                $y0 = $y;
                            }
                            $xmin = min($xmin, $x);
                            $ymin = min($ymin, $y);
                            $xmax = max($xmax, $x);
                            $ymax = max($ymax, $y);
                            if ($relcoord) {
                                $xoffset = $x;
                                $yoffset = $y;
                            }
                        }
                    }
                    break;
                }
                case 'L': { // lineto
                    foreach ($params as $ck => $cp) {
                        if (($ck % 2) == 0) {
                            $x = $cp + $xoffset;
                        } else {
                            $y = $cp + $yoffset;
                            if ((abs($x0 - $x) >= $minlen) OR (abs($y0 - $y) >= $minlen)) {
                                $this->_outLine($x, $y);
                                $x0 = $x;
                                $y0 = $y;
                            }
                            $xmin = min($xmin, $x);
                            $ymin = min($ymin, $y);
                            $xmax = max($xmax, $x);
                            $ymax = max($ymax, $y);
                            if ($relcoord) {
                                $xoffset = $x;
                                $yoffset = $y;
                            }
                        }
                    }
                    break;
                }
                case 'H': { // horizontal lineto
                    foreach ($params as $ck => $cp) {
                        $x = $cp + $xoffset;
                        if ((abs($x0 - $x) >= $minlen) OR (abs($y0 - $y) >= $minlen)) {
                            $this->_outLine($x, $y);
                            $x0 = $x;
                            $y0 = $y;
                        }
                        $xmin = min($xmin, $x);
                        $xmax = max($xmax, $x);
                        if ($relcoord) {
                            $xoffset = $x;
                        }
                    }
                    break;
                }
                case 'V': { // vertical lineto
                    foreach ($params as $ck => $cp) {
                        $y = $cp + $yoffset;
                        if ((abs($x0 - $x) >= $minlen) OR (abs($y0 - $y) >= $minlen)) {
                            $this->_outLine($x, $y);
                            $x0 = $x;
                            $y0 = $y;
                        }
                        $ymin = min($ymin, $y);
                        $ymax = max($ymax, $y);
                        if ($relcoord) {
                            $yoffset = $y;
                        }
                    }
                    break;
                }
                case 'C': { // curveto
                    foreach ($params as $ck => $cp) {
                        $params[$ck] = $cp;
                        if ((($ck + 1) % 6) == 0) {
                            $x1 = $params[($ck - 5)] + $xoffset;
                            $y1 = $params[($ck - 4)] + $yoffset;
                            $x2 = $params[($ck - 3)] + $xoffset;
                            $y2 = $params[($ck - 2)] + $yoffset;
                            $x = $params[($ck - 1)] + $xoffset;
                            $y = $params[($ck)] + $yoffset;
                            $this->_outCurve($x1, $y1, $x2, $y2, $x, $y);
                            $xmin = min($xmin, $x, $x1, $x2);
                            $ymin = min($ymin, $y, $y1, $y2);
                            $xmax = max($xmax, $x, $x1, $x2);
                            $ymax = max($ymax, $y, $y1, $y2);
                            if ($relcoord) {
                                $xoffset = $x;
                                $yoffset = $y;
                            }
                        }
                    }
                    break;
                }
                case 'S': { // shorthand/smooth curveto
                    foreach ($params as $ck => $cp) {
                        $params[$ck] = $cp;
                        if ((($ck + 1) % 4) == 0) {
                            if (($key > 0) AND ((strtoupper($paths[($key - 1)][1]) == 'C') OR (strtoupper($paths[($key - 1)][1]) == 'S'))) {
                                $x1 = (2 * $x) - $x2;
                                $y1 = (2 * $y) - $y2;
                            } else {
                                $x1 = $x;
                                $y1 = $y;
                            }
                            $x2 = $params[($ck - 3)] + $xoffset;
                            $y2 = $params[($ck - 2)] + $yoffset;
                            $x = $params[($ck - 1)] + $xoffset;
                            $y = $params[($ck)] + $yoffset;
                            $this->_outCurve($x1, $y1, $x2, $y2, $x, $y);
                            $xmin = min($xmin, $x, $x1, $x2);
                            $ymin = min($ymin, $y, $y1, $y2);
                            $xmax = max($xmax, $x, $x1, $x2);
                            $ymax = max($ymax, $y, $y1, $y2);
                            if ($relcoord) {
                                $xoffset = $x;
                                $yoffset = $y;
                            }
                        }
                    }
                    break;
                }
                case 'Q': { // quadratic Bezier curveto
                    foreach ($params as $ck => $cp) {
                        $params[$ck] = $cp;
                        if ((($ck + 1) % 4) == 0) {
                            // convert quadratic points to cubic points
                            $x1 = $params[($ck - 3)] + $xoffset;
                            $y1 = $params[($ck - 2)] + $yoffset;
                            $xa = ($x + (2 * $x1)) / 3;
                            $ya = ($y + (2 * $y1)) / 3;
                            $x = $params[($ck - 1)] + $xoffset;
                            $y = $params[($ck)] + $yoffset;
                            $xb = ($x + (2 * $x1)) / 3;
                            $yb = ($y + (2 * $y1)) / 3;
                            $this->_outCurve($xa, $ya, $xb, $yb, $x, $y);
                            $xmin = min($xmin, $x, $xa, $xb);
                            $ymin = min($ymin, $y, $ya, $yb);
                            $xmax = max($xmax, $x, $xa, $xb);
                            $ymax = max($ymax, $y, $ya, $yb);
                            if ($relcoord) {
                                $xoffset = $x;
                                $yoffset = $y;
                            }
                        }
                    }
                    break;
                }
                case 'T': { // shorthand/smooth quadratic Bezier curveto
                    foreach ($params as $ck => $cp) {
                        $params[$ck] = $cp;
                        if (($ck % 2) != 0) {
                            if (($key > 0) AND ((strtoupper($paths[($key - 1)][1]) == 'Q') OR (strtoupper($paths[($key - 1)][1]) == 'T'))) {
                                $x1 = (2 * $x) - $x1;
                                $y1 = (2 * $y) - $y1;
                            } else {
                                $x1 = $x;
                                $y1 = $y;
                            }
                            // convert quadratic points to cubic points
                            $xa = ($x + (2 * $x1)) / 3;
                            $ya = ($y + (2 * $y1)) / 3;
                            $x = $params[($ck - 1)] + $xoffset;
                            $y = $params[($ck)] + $yoffset;
                            $xb = ($x + (2 * $x1)) / 3;
                            $yb = ($y + (2 * $y1)) / 3;
                            $this->_outCurve($xa, $ya, $xb, $yb, $x, $y);
                            $xmin = min($xmin, $x, $xa, $xb);
                            $ymin = min($ymin, $y, $ya, $yb);
                            $xmax = max($xmax, $x, $xa, $xb);
                            $ymax = max($ymax, $y, $ya, $yb);
                            if ($relcoord) {
                                $xoffset = $x;
                                $yoffset = $y;
                            }
                        }
                    }
                    break;
                }
                case 'A': { // elliptical arc
                    foreach ($params as $ck => $cp) {
                        $params[$ck] = $cp;
                        if ((($ck + 1) % 7) == 0) {
                            $x0 = $x;
                            $y0 = $y;
                            $rx = abs($params[($ck - 6)]);
                            $ry = abs($params[($ck - 5)]);
                            $ang = -$rawparams[($ck - 4)];
                            $angle = deg2rad($ang);
                            $fa = $rawparams[($ck - 3)]; // large-arc-flag
                            $fs = $rawparams[($ck - 2)]; // sweep-flag
                            $x = $params[($ck - 1)] + $xoffset;
                            $y = $params[$ck] + $yoffset;
                            if ((abs($x0 - $x) < $minlen) AND (abs($y0 - $y) < $minlen)) {
                                // endpoints are almost identical
                                $xmin = min($xmin, $x);
                                $ymin = min($ymin, $y);
                                $xmax = max($xmax, $x);
                                $ymax = max($ymax, $y);
                            } else {
                                $cos_ang = cos($angle);
                                $sin_ang = sin($angle);
                                $a = (($x0 - $x) / 2);
                                $b = (($y0 - $y) / 2);
                                $xa = ($a * $cos_ang) - ($b * $sin_ang);
                                $ya = ($a * $sin_ang) + ($b * $cos_ang);
                                $rx2 = $rx * $rx;
                                $ry2 = $ry * $ry;
                                $xa2 = $xa * $xa;
                                $ya2 = $ya * $ya;
                                $delta = ($xa2 / $rx2) + ($ya2 / $ry2);
                                if ($delta > 1) {
                                    $rx *= sqrt($delta);
                                    $ry *= sqrt($delta);
                                    $rx2 = $rx * $rx;
                                    $ry2 = $ry * $ry;
                                }
                                $numerator = (($rx2 * $ry2) - ($rx2 * $ya2) - ($ry2 * $xa2));
                                if ($numerator < 0) {
                                    $root = 0;
                                } else {
                                    $root = sqrt($numerator / (($rx2 * $ya2) + ($ry2 * $xa2)));
                                }
                                if ($fa == $fs){
                                    $root *= -1;
                                }
                                $cax = $root * (($rx * $ya) / $ry);
                                $cay = -$root * (($ry * $xa) / $rx);
                                // coordinates of ellipse center
                                $cx = ($cax * $cos_ang) - ($cay * $sin_ang) + (($x0 + $x) / 2);
                                $cy = ($cax * $sin_ang) + ($cay * $cos_ang) + (($y0 + $y) / 2);
                                // get angles
                                $angs = TCPDF_STATIC::getVectorsAngle(1, 0, (($xa - $cax) / $rx), (($cay - $ya) / $ry));
                                $dang = TCPDF_STATIC::getVectorsAngle((($xa - $cax) / $rx), (($ya - $cay) / $ry), ((-$xa - $cax) / $rx), ((-$ya - $cay) / $ry));
                                if (($fs == 0) AND ($dang > 0)) {
                                    $dang -= (2 * M_PI);
                                } elseif (($fs == 1) AND ($dang < 0)) {
                                    $dang += (2 * M_PI);
                                }
                                $angf = $angs - $dang;
                                if ((($fs == 0) AND ($angs > $angf)) OR (($fs == 1) AND ($angs < $angf))) {
                                    // reverse angles
                                    $tmp = $angs;
                                    $angs = $angf;
                                    $angf = $tmp;
                                }
                                $angs = round(rad2deg($angs), 6);
                                $angf = round(rad2deg($angf), 6);
                                // covent angles to positive values
                                if (($angs < 0) AND ($angf < 0)) {
                                    $angs += 360;
                                    $angf += 360;
                                }
                                $pie = false;
                                if (($key == 0) AND (isset($paths[($key + 1)][1])) AND (trim($paths[($key + 1)][1]) == 'z')) {
                                    $pie = true;
                                }
                                list($axmin, $aymin, $axmax, $aymax) = $this->_outellipticalarc($cx, $cy, $rx, $ry, $ang, $angs, $angf, $pie, 2, false, ($fs == 0), true);
                                $xmin = min($xmin, $x, $axmin);
                                $ymin = min($ymin, $y, $aymin);
                                $xmax = max($xmax, $x, $axmax);
                                $ymax = max($ymax, $y, $aymax);
                            }
                            if ($relcoord) {
                                $xoffset = $x;
                                $yoffset = $y;
                            }
                        }
                    }
                    break;
                }
                case 'Z': {
                    $this->_out('h');
                    break;
                }
            }
            $firstcmd = false;
        } // end foreach
        if (!empty($op)) {
            $this->_out($op);
        }
        return array($xmin, $ymin, ($xmax - $xmin), ($ymax - $ymin));
    }

    /**
     * Return the tag name without the namespace
     * @param $name (string) Tag name
     * @protected
     */
    protected function removeTagNamespace($name) {
        if(strpos($name, ':') !== false) {
            $parts = explode(':', $name);
            return $parts[(sizeof($parts) - 1)];
        }
        return $name;
    }
        
    /**
     * Sets the opening SVG element handler function for the XML parser. (*** TO BE COMPLETED ***)
     * @param $parser (resource) The first parameter, parser, is a reference to the XML parser calling the handler.
     * @param $name (string) The second parameter, name, contains the name of the element for which this handler is called. If case-folding is in effect for this parser, the element name will be in uppercase letters.
     * @param $attribs (array) The third parameter, attribs, contains an associative array with the element's attributes (if any). The keys of this array are the attribute names, the values are the attribute values. Attribute names are case-folded on the same criteria as element names. Attribute values are not case-folded. The original order of the attributes can be retrieved by walking through attribs the normal way, using each(). The first key in the array was the first attribute, and so on.
     * @param $ctm (array) tranformation matrix for clipping mode (starting transformation matrix).
     * @author Nicola Asuni
     * @since 5.0.000 (2010-05-02)
     * @protected
     */
    protected function startSVGElementHandler($parser, $name, $attribs, $ctm=array()) {
        $name = $this->removeTagNamespace($name);
        // check if we are in clip mode
        if ($this->svgclipmode) {
            $this->svgclippaths[$this->svgclipid][] = array('name' => $name, 'attribs' => $attribs, 'tm' => $this->svgcliptm[$this->svgclipid]);
            return;
        }
        if ($this->svgdefsmode AND !in_array($name, array('clipPath', 'linearGradient', 'radialGradient', 'stop'))) {
            if (isset($attribs['id'])) {
                $attribs['child_elements'] = array();
                $this->svgdefs[$attribs['id']] = array('name' => $name, 'attribs' => $attribs);
                return;
            }
            if (end($this->svgdefs) !== FALSE) {
                $last_svgdefs_id = key($this->svgdefs);
                if (isset($this->svgdefs[$last_svgdefs_id]['attribs']['child_elements'])) {
                    $attribs['id'] = 'DF_'.(count($this->svgdefs[$last_svgdefs_id]['attribs']['child_elements']) + 1);
                    $this->svgdefs[$last_svgdefs_id]['attribs']['child_elements'][$attribs['id']] = array('name' => $name, 'attribs' => $attribs);
                    return;
                }
            }
            return;
        }
        $clipping = false;
        if ($parser == 'clip-path') {
            // set clipping mode
            $clipping = true;
        }
        // get styling properties
        $prev_svgstyle = $this->svgstyles[max(0,(count($this->svgstyles) - 1))]; // previous style
        $svgstyle = $this->svgstyles[0]; // set default style
        if ($clipping AND !isset($attribs['fill']) AND (!isset($attribs['style']) OR (!preg_match('/[;\"\s]{1}fill[\s]*:[\s]*([^;\"]*)/si', $attribs['style'], $attrval)))) {
            // default fill attribute for clipping
            $attribs['fill'] = 'none';
        }
        if (isset($attribs['style']) AND !TCPDF_STATIC::empty_string($attribs['style']) AND ($attribs['style'][0] != ';')) {
            // fix style for regular expression
            $attribs['style'] = ';'.$attribs['style'];
        }
        foreach ($prev_svgstyle as $key => $val) {
            if (in_array($key, TCPDF_IMAGES::$svginheritprop)) {
                // inherit previous value
                $svgstyle[$key] = $val;
            }
            if (isset($attribs[$key]) AND !TCPDF_STATIC::empty_string($attribs[$key])) {
                // specific attribute settings
                if ($attribs[$key] == 'inherit') {
                    $svgstyle[$key] = $val;
                } else {
                    $svgstyle[$key] = $attribs[$key];
                }
            } elseif (isset($attribs['style']) AND !TCPDF_STATIC::empty_string($attribs['style'])) {
                // CSS style syntax
                $attrval = array();
                if (preg_match('/[;\"\s]{1}'.$key.'[\s]*:[\s]*([^;\"]*)/si', $attribs['style'], $attrval) AND isset($attrval[1])) {
                    if ($attrval[1] == 'inherit') {
                        $svgstyle[$key] = $val;
                    } else {
                        $svgstyle[$key] = $attrval[1];
                    }
                }
            }
        }
        // transformation matrix
        if (!empty($ctm)) {
            $tm = $ctm;
        } else {
            $tm = array(1,0,0,1,0,0);
        }
        if (isset($attribs['transform']) AND !empty($attribs['transform'])) {
            $tm = TCPDF_STATIC::getTransformationMatrixProduct($tm, TCPDF_STATIC::getSVGTransformMatrix($attribs['transform']));
        }
        $svgstyle['transfmatrix'] = $tm;
        $invisible = false;
        if (($svgstyle['visibility'] == 'hidden') OR ($svgstyle['visibility'] == 'collapse') OR ($svgstyle['display'] == 'none')) {
            // the current graphics element is invisible (nothing is painted)
            $invisible = true;
        }
        // process tag
        switch($name) {
            case 'defs': {
                $this->svgdefsmode = true;
                break;
            }
            // clipPath
            case 'clipPath': {
                if ($invisible) {
                    break;
                }
                $this->svgclipmode = true;
                if (!isset($attribs['id'])) {
                    $attribs['id'] = 'CP_'.(count($this->svgcliptm) + 1);
                }
                $this->svgclipid = $attribs['id'];
                $this->svgclippaths[$this->svgclipid] = array();
                $this->svgcliptm[$this->svgclipid] = $tm;
                break;
            }
            case 'svg': {
                // start of SVG object
                if(++$this->svg_tag_depth <= 1) {
                    break;
                }
                // inner SVG
                array_push($this->svgstyles, $svgstyle);
                $this->StartTransform();
                $svgX = (isset($attribs['x'])?$attribs['x']:0);
                $svgY = (isset($attribs['y'])?$attribs['y']:0);
                $svgW = (isset($attribs['width'])?$attribs['width']:0);
                $svgH = (isset($attribs['height'])?$attribs['height']:0);
                // set x, y position using transform matrix
                $tm = TCPDF_STATIC::getTransformationMatrixProduct($tm, array( 1, 0, 0, 1, $svgX, $svgY));
                $this->SVGTransform($tm);
                // set clipping for width and height
                $x = 0;
                $y = 0;
                $w = (isset($attribs['width'])?$this->getHTMLUnitToUnits($attribs['width'], 0, $this->svgunit, false):$this->w);
                $h = (isset($attribs['height'])?$this->getHTMLUnitToUnits($attribs['height'], 0, $this->svgunit, false):$this->h);
                // draw clipping rect
                $this->Rect($x, $y, $w, $h, 'CNZ', array(), array());
                // parse viewbox, calculate extra transformation matrix
                if (isset($attribs['viewBox'])) {
                    $tmp = array();
                    preg_match_all("/[0-9]+/", $attribs['viewBox'], $tmp);
                    $tmp = $tmp[0];
                    if (sizeof($tmp) == 4) {
                        $vx = $tmp[0];
                        $vy = $tmp[1];
                        $vw = $tmp[2];
                        $vh = $tmp[3];
                        // get aspect ratio
                        $tmp = array();
                        $aspectX = 'xMid';
                        $aspectY = 'YMid';
                        $fit = 'meet';
                        if (isset($attribs['preserveAspectRatio'])) {
                            if($attribs['preserveAspectRatio'] == 'none') {
                                $fit = 'none';
                            } else {
                                preg_match_all('/[a-zA-Z]+/', $attribs['preserveAspectRatio'], $tmp);
                                $tmp = $tmp[0];
                                if ((sizeof($tmp) == 2) AND (strlen($tmp[0]) == 8) AND (in_array($tmp[1], array('meet', 'slice', 'none')))) {
                                    $aspectX = substr($tmp[0], 0, 4);
                                    $aspectY = substr($tmp[0], 4, 4);
                                    $fit = $tmp[1];
                                }
                            }
                        }
                        $wr = ($svgW / $vw);
                        $hr = ($svgH / $vh);
                        $ax = $ay = 0;
                        if ((($fit == 'meet') AND ($hr < $wr)) OR (($fit == 'slice') AND ($hr > $wr))) {
                            if ($aspectX == 'xMax') {
                                $ax = (($vw * ($wr / $hr)) - $vw);
                            }
                            if ($aspectX == 'xMid') {
                                $ax = ((($vw * ($wr / $hr)) - $vw) / 2);
                            }
                            $wr = $hr;
                        } elseif ((($fit == 'meet') AND ($hr > $wr)) OR (($fit == 'slice') AND ($hr < $wr))) {
                            if ($aspectY == 'YMax') {
                                $ay = (($vh * ($hr / $wr)) - $vh);
                            }
                            if ($aspectY == 'YMid') {
                                $ay = ((($vh * ($hr / $wr)) - $vh) / 2);
                            }
                            $hr = $wr;
                        }
                        $newtm = array($wr, 0, 0, $hr, (($wr * ($ax - $vx)) - $svgX), (($hr * ($ay - $vy)) - $svgY));
                        $tm = TCPDF_STATIC::getTransformationMatrixProduct($tm, $newtm);
                        $this->SVGTransform($tm);
                    }
                }
                $this->setSVGStyles($svgstyle, $prev_svgstyle);
                break;
            }
            case 'g': {
                // group together related graphics elements
                array_push($this->svgstyles, $svgstyle);
                $this->StartTransform();
                $x = (isset($attribs['x'])?$attribs['x']:0);
                $y = (isset($attribs['y'])?$attribs['y']:0);
                $w = 1;//(isset($attribs['width'])?$attribs['width']:1);
                $h = 1;//(isset($attribs['height'])?$attribs['height']:1);
                $tm = TCPDF_STATIC::getTransformationMatrixProduct($tm, array($w, 0, 0, $h, $x, $y));
                $this->SVGTransform($tm);
                $this->setSVGStyles($svgstyle, $prev_svgstyle);
                break;
            }
            case 'linearGradient': {
                if ($this->pdfa_mode) {
                    break;
                }
                if (!isset($attribs['id'])) {
                    $attribs['id'] = 'GR_'.(count($this->svggradients) + 1);
                }
                $this->svggradientid = $attribs['id'];
                $this->svggradients[$this->svggradientid] = array();
                $this->svggradients[$this->svggradientid]['type'] = 2;
                $this->svggradients[$this->svggradientid]['stops'] = array();
                if (isset($attribs['gradientUnits'])) {
                    $this->svggradients[$this->svggradientid]['gradientUnits'] = $attribs['gradientUnits'];
                } else {
                    $this->svggradients[$this->svggradientid]['gradientUnits'] = 'objectBoundingBox';
                }
                //$attribs['spreadMethod']
                if (((!isset($attribs['x1'])) AND (!isset($attribs['y1'])) AND (!isset($attribs['x2'])) AND (!isset($attribs['y2'])))
                    OR ((isset($attribs['x1']) AND (substr($attribs['x1'], -1) == '%'))
                        OR (isset($attribs['y1']) AND (substr($attribs['y1'], -1) == '%'))
                        OR (isset($attribs['x2']) AND (substr($attribs['x2'], -1) == '%'))
                        OR (isset($attribs['y2']) AND (substr($attribs['y2'], -1) == '%')))) {
                    $this->svggradients[$this->svggradientid]['mode'] = 'percentage';
                } else {
                    $this->svggradients[$this->svggradientid]['mode'] = 'measure';
                }
                $x1 = (isset($attribs['x1'])?$attribs['x1']:'0');
                $y1 = (isset($attribs['y1'])?$attribs['y1']:'0');
                $x2 = (isset($attribs['x2'])?$attribs['x2']:'100');
                $y2 = (isset($attribs['y2'])?$attribs['y2']:'0');
                if (isset($attribs['gradientTransform'])) {
                    $this->svggradients[$this->svggradientid]['gradientTransform'] = TCPDF_STATIC::getSVGTransformMatrix($attribs['gradientTransform']);
                }
                $this->svggradients[$this->svggradientid]['coords'] = array($x1, $y1, $x2, $y2);
                if (isset($attribs['xlink:href']) AND !empty($attribs['xlink:href'])) {
                    // gradient is defined on another place
                    $this->svggradients[$this->svggradientid]['xref'] = substr($attribs['xlink:href'], 1);
                }
                break;
            }
            case 'radialGradient': {
                if ($this->pdfa_mode) {
                    break;
                }
                if (!isset($attribs['id'])) {
                    $attribs['id'] = 'GR_'.(count($this->svggradients) + 1);
                }
                $this->svggradientid = $attribs['id'];
                $this->svggradients[$this->svggradientid] = array();
                $this->svggradients[$this->svggradientid]['type'] = 3;
                $this->svggradients[$this->svggradientid]['stops'] = array();
                if (isset($attribs['gradientUnits'])) {
                    $this->svggradients[$this->svggradientid]['gradientUnits'] = $attribs['gradientUnits'];
                } else {
                    $this->svggradients[$this->svggradientid]['gradientUnits'] = 'objectBoundingBox';
                }
                //$attribs['spreadMethod']
                if (((!isset($attribs['cx'])) AND (!isset($attribs['cy'])))
                    OR ((isset($attribs['cx']) AND (substr($attribs['cx'], -1) == '%'))
                    OR (isset($attribs['cy']) AND (substr($attribs['cy'], -1) == '%')))) {
                    $this->svggradients[$this->svggradientid]['mode'] = 'percentage';
                } elseif (isset($attribs['r']) AND is_numeric($attribs['r']) AND ($attribs['r']) <= 1) {
                    $this->svggradients[$this->svggradientid]['mode'] = 'ratio';
                } else {
                    $this->svggradients[$this->svggradientid]['mode'] = 'measure';
                }
                $cx = (isset($attribs['cx']) ? $attribs['cx'] : 0.5);
                $cy = (isset($attribs['cy']) ? $attribs['cy'] : 0.5);
                $fx = (isset($attribs['fx']) ? $attribs['fx'] : $cx);
                $fy = (isset($attribs['fy']) ? $attribs['fy'] : $cy);
                $r = (isset($attribs['r']) ? $attribs['r'] : 0.5);
                if (isset($attribs['gradientTransform'])) {
                    $this->svggradients[$this->svggradientid]['gradientTransform'] = TCPDF_STATIC::getSVGTransformMatrix($attribs['gradientTransform']);
                }
                $this->svggradients[$this->svggradientid]['coords'] = array($cx, $cy, $fx, $fy, $r);
                if (isset($attribs['xlink:href']) AND !empty($attribs['xlink:href'])) {
                    // gradient is defined on another place
                    $this->svggradients[$this->svggradientid]['xref'] = substr($attribs['xlink:href'], 1);
                }
                break;
            }
            case 'stop': {
                // gradient stops
                if (substr($attribs['offset'], -1) == '%') {
                    $offset = floatval(substr($attribs['offset'], -1)) / 100;
                } else {
                    $offset = floatval($attribs['offset']);
                    if ($offset > 1) {
                        $offset /= 100;
                    }
                }
                $stop_color = isset($svgstyle['stop-color'])?TCPDF_COLORS::convertHTMLColorToDec($svgstyle['stop-color'], $this->spot_colors):'black';
                $opacity = isset($svgstyle['stop-opacity'])?$svgstyle['stop-opacity']:1;
                $this->svggradients[$this->svggradientid]['stops'][] = array('offset' => $offset, 'color' => $stop_color, 'opacity' => $opacity);
                break;
            }
            // paths
            case 'path': {
                if ($invisible) {
                    break;
                }
                if (isset($attribs['d'])) {
                    $d = trim($attribs['d']);
                    if (!empty($d)) {
                        $x = (isset($attribs['x'])?$attribs['x']:0);
                        $y = (isset($attribs['y'])?$attribs['y']:0);
                        $w = (isset($attribs['width'])?$attribs['width']:1);
                        $h = (isset($attribs['height'])?$attribs['height']:1);
                        $tm = TCPDF_STATIC::getTransformationMatrixProduct($tm, array($w, 0, 0, $h, $x, $y));
                        if ($clipping) {
                            $this->SVGTransform($tm);
                            $this->SVGPath($d, 'CNZ');
                        } else {
                            $this->StartTransform();
                            $this->SVGTransform($tm);
                            $obstyle = $this->setSVGStyles($svgstyle, $prev_svgstyle, $x, $y, $w, $h, 'SVGPath', array($d, 'CNZ'));
                            if (!empty($obstyle)) {
                                $this->SVGPath($d, $obstyle);
                            }
                            $this->StopTransform();
                        }
                    }
                }
                break;
            }
            // shapes
            case 'rect': {
                if ($invisible) {
                    break;
                }
                $x = (isset($attribs['x'])?$this->getHTMLUnitToUnits($attribs['x'], 0, $this->svgunit, false):0);
                $y = (isset($attribs['y'])?$this->getHTMLUnitToUnits($attribs['y'], 0, $this->svgunit, false):0);
                $w = (isset($attribs['width'])?$this->getHTMLUnitToUnits($attribs['width'], 0, $this->svgunit, false):0);
                $h = (isset($attribs['height'])?$this->getHTMLUnitToUnits($attribs['height'], 0, $this->svgunit, false):0);
                $rx = (isset($attribs['rx'])?$this->getHTMLUnitToUnits($attribs['rx'], 0, $this->svgunit, false):0);
                $ry = (isset($attribs['ry'])?$this->getHTMLUnitToUnits($attribs['ry'], 0, $this->svgunit, false):$rx);
                if ($clipping) {
                    $this->SVGTransform($tm);
                    $this->RoundedRectXY($x, $y, $w, $h, $rx, $ry, '1111', 'CNZ', array(), array());
                } else {
                    $this->StartTransform();
                    $this->SVGTransform($tm);
                    $obstyle = $this->setSVGStyles($svgstyle, $prev_svgstyle, $x, $y, $w, $h, 'RoundedRectXY', array($x, $y, $w, $h, $rx, $ry, '1111', 'CNZ'));
                    if (!empty($obstyle)) {
                        $this->RoundedRectXY($x, $y, $w, $h, $rx, $ry, '1111', $obstyle, array(), array());
                    }
                    $this->StopTransform();
                }
                break;
            }
            case 'circle': {
                if ($invisible) {
                    break;
                }
                $r = (isset($attribs['r']) ? $this->getHTMLUnitToUnits($attribs['r'], 0, $this->svgunit, false) : 0);
                $cx = (isset($attribs['cx']) ? $this->getHTMLUnitToUnits($attribs['cx'], 0, $this->svgunit, false) : (isset($attribs['x']) ? $this->getHTMLUnitToUnits($attribs['x'], 0, $this->svgunit, false) : 0));
                $cy = (isset($attribs['cy']) ? $this->getHTMLUnitToUnits($attribs['cy'], 0, $this->svgunit, false) : (isset($attribs['y']) ? $this->getHTMLUnitToUnits($attribs['y'], 0, $this->svgunit, false) : 0));
                $x = ($cx - $r);
                $y = ($cy - $r);
                $w = (2 * $r);
                $h = $w;
                if ($clipping) {
                    $this->SVGTransform($tm);
                    $this->Circle($cx, $cy, $r, 0, 360, 'CNZ', array(), array(), 8);
                } else {
                    $this->StartTransform();
                    $this->SVGTransform($tm);
                    $obstyle = $this->setSVGStyles($svgstyle, $prev_svgstyle, $x, $y, $w, $h, 'Circle', array($cx, $cy, $r, 0, 360, 'CNZ'));
                    if (!empty($obstyle)) {
                        $this->Circle($cx, $cy, $r, 0, 360, $obstyle, array(), array(), 8);
                    }
                    $this->StopTransform();
                }
                break;
            }
            case 'ellipse': {
                if ($invisible) {
                    break;
                }
                $rx = (isset($attribs['rx']) ? $this->getHTMLUnitToUnits($attribs['rx'], 0, $this->svgunit, false) : 0);
                $ry = (isset($attribs['ry']) ? $this->getHTMLUnitToUnits($attribs['ry'], 0, $this->svgunit, false) : 0);
                $cx = (isset($attribs['cx']) ? $this->getHTMLUnitToUnits($attribs['cx'], 0, $this->svgunit, false) : (isset($attribs['x']) ? $this->getHTMLUnitToUnits($attribs['x'], 0, $this->svgunit, false) : 0));
                $cy = (isset($attribs['cy']) ? $this->getHTMLUnitToUnits($attribs['cy'], 0, $this->svgunit, false) : (isset($attribs['y']) ? $this->getHTMLUnitToUnits($attribs['y'], 0, $this->svgunit, false) : 0));
                $x = ($cx - $rx);
                $y = ($cy - $ry);
                $w = (2 * $rx);
                $h = (2 * $ry);
                if ($clipping) {
                    $this->SVGTransform($tm);
                    $this->Ellipse($cx, $cy, $rx, $ry, 0, 0, 360, 'CNZ', array(), array(), 8);
                } else {
                    $this->StartTransform();
                    $this->SVGTransform($tm);
                    $obstyle = $this->setSVGStyles($svgstyle, $prev_svgstyle, $x, $y, $w, $h, 'Ellipse', array($cx, $cy, $rx, $ry, 0, 0, 360, 'CNZ'));
                    if (!empty($obstyle)) {
                        $this->Ellipse($cx, $cy, $rx, $ry, 0, 0, 360, $obstyle, array(), array(), 8);
                    }
                    $this->StopTransform();
                }
                break;
            }
            case 'line': {
                if ($invisible) {
                    break;
                }
                $x1 = (isset($attribs['x1'])?$this->getHTMLUnitToUnits($attribs['x1'], 0, $this->svgunit, false):0);
                $y1 = (isset($attribs['y1'])?$this->getHTMLUnitToUnits($attribs['y1'], 0, $this->svgunit, false):0);
                $x2 = (isset($attribs['x2'])?$this->getHTMLUnitToUnits($attribs['x2'], 0, $this->svgunit, false):0);
                $y2 = (isset($attribs['y2'])?$this->getHTMLUnitToUnits($attribs['y2'], 0, $this->svgunit, false):0);
                $x = $x1;
                $y = $y1;
                $w = abs($x2 - $x1);
                $h = abs($y2 - $y1);
                if (!$clipping) {
                    $this->StartTransform();
                    $this->SVGTransform($tm);
                    $obstyle = $this->setSVGStyles($svgstyle, $prev_svgstyle, $x, $y, $w, $h, 'Line', array($x1, $y1, $x2, $y2));
                    $this->Line($x1, $y1, $x2, $y2);
                    $this->StopTransform();
                }
                break;
            }
            case 'polyline':
            case 'polygon': {
                if ($invisible) {
                    break;
                }
                $points = (isset($attribs['points'])?$attribs['points']:'0 0');
                $points = trim($points);
                // note that point may use a complex syntax not covered here
                $points = preg_split('/[\,\s]+/si', $points);
                if (count($points) < 4) {
                    break;
                }
                $p = array();
                $xmin = 2147483647;
                $xmax = 0;
                $ymin = 2147483647;
                $ymax = 0;
                foreach ($points as $key => $val) {
                    $p[$key] = $this->getHTMLUnitToUnits($val, 0, $this->svgunit, false);
                    if (($key % 2) == 0) {
                        // X coordinate
                        $xmin = min($xmin, $p[$key]);
                        $xmax = max($xmax, $p[$key]);
                    } else {
                        // Y coordinate
                        $ymin = min($ymin, $p[$key]);
                        $ymax = max($ymax, $p[$key]);
                    }
                }
                $x = $xmin;
                $y = $ymin;
                $w = ($xmax - $xmin);
                $h = ($ymax - $ymin);
                if ($name == 'polyline') {
                    $this->StartTransform();
                    $this->SVGTransform($tm);
                    $obstyle = $this->setSVGStyles($svgstyle, $prev_svgstyle, $x, $y, $w, $h, 'PolyLine', array($p, 'CNZ'));
                    if (!empty($obstyle)) {
                        $this->PolyLine($p, $obstyle, array(), array());
                    }
                    $this->StopTransform();
                } else { // polygon
                    if ($clipping) {
                        $this->SVGTransform($tm);
                        $this->Polygon($p, 'CNZ', array(), array(), true);
                    } else {
                        $this->StartTransform();
                        $this->SVGTransform($tm);
                        $obstyle = $this->setSVGStyles($svgstyle, $prev_svgstyle, $x, $y, $w, $h, 'Polygon', array($p, 'CNZ'));
                        if (!empty($obstyle)) {
                            $this->Polygon($p, $obstyle, array(), array(), true);
                        }
                        $this->StopTransform();
                    }
                }
                break;
            }
            // image
            case 'image': {
                if ($invisible) {
                    break;
                }
                if (!isset($attribs['xlink:href']) OR empty($attribs['xlink:href'])) {
                    break;
                }
                $x = (isset($attribs['x'])?$this->getHTMLUnitToUnits($attribs['x'], 0, $this->svgunit, false):0);
                $y = (isset($attribs['y'])?$this->getHTMLUnitToUnits($attribs['y'], 0, $this->svgunit, false):0);
                $w = (isset($attribs['width'])?$this->getHTMLUnitToUnits($attribs['width'], 0, $this->svgunit, false):0);
                $h = (isset($attribs['height'])?$this->getHTMLUnitToUnits($attribs['height'], 0, $this->svgunit, false):0);
                $img = $attribs['xlink:href'];
                if (!$clipping) {
                    $this->StartTransform();
                    $this->SVGTransform($tm);
                    $obstyle = $this->setSVGStyles($svgstyle, $prev_svgstyle, $x, $y, $w, $h);
                    if (preg_match('/^data:image\/[^;]+;base64,/', $img, $m) > 0) {
                        // embedded image encoded as base64
                        $img = '@'.base64_decode(substr($img, strlen($m[0])));
                    } else {
                        // fix image path
                        if (!TCPDF_STATIC::empty_string($this->svgdir) AND (($img[0] == '.') OR (basename($img) == $img))) {
                            // replace relative path with full server path
                            $img = $this->svgdir.'/'.$img;
                        }
                        if (($img[0] == '/') AND !empty($_SERVER['DOCUMENT_ROOT']) AND ($_SERVER['DOCUMENT_ROOT'] != '/')) {
                            $findroot = strpos($img, $_SERVER['DOCUMENT_ROOT']);
                            if (($findroot === false) OR ($findroot > 1)) {
                                if (substr($_SERVER['DOCUMENT_ROOT'], -1) == '/') {
                                    $img = substr($_SERVER['DOCUMENT_ROOT'], 0, -1).$img;
                                } else {
                                    $img = $_SERVER['DOCUMENT_ROOT'].$img;
                                }
                            }
                        }
                        $img = urldecode($img);
                        $testscrtype = @parse_url($img);
                        if (!isset($testscrtype['query']) OR empty($testscrtype['query'])) {
                            // convert URL to server path
                            $img = str_replace(K_PATH_URL, K_PATH_MAIN, $img);
                        }
                    }
                    // get image type
                    $imgtype = TCPDF_IMAGES::getImageFileType($img);
                    if (($imgtype == 'eps') OR ($imgtype == 'ai')) {
                        $this->ImageEps($img, $x, $y, $w, $h);
                    } elseif ($imgtype == 'svg') {
                        // store SVG vars
                        $svggradients = $this->svggradients;
                        $svggradientid = $this->svggradientid;
                        $svgdefsmode = $this->svgdefsmode;
                        $svgdefs = $this->svgdefs;
                        $svgclipmode = $this->svgclipmode;
                        $svgclippaths = $this->svgclippaths;
                        $svgcliptm = $this->svgcliptm;
                        $svgclipid = $this->svgclipid;
                        $svgtext = $this->svgtext;
                        $svgtextmode = $this->svgtextmode;
                        $this->ImageSVG($img, $x, $y, $w, $h);
                        // restore SVG vars
                        $this->svggradients = $svggradients;
                        $this->svggradientid = $svggradientid;
                        $this->svgdefsmode = $svgdefsmode;
                        $this->svgdefs = $svgdefs;
                        $this->svgclipmode = $svgclipmode;
                        $this->svgclippaths = $svgclippaths;
                        $this->svgcliptm = $svgcliptm;
                        $this->svgclipid = $svgclipid;
                        $this->svgtext = $svgtext;
                        $this->svgtextmode = $svgtextmode;
                    } else {
                        $this->Image($img, $x, $y, $w, $h);
                    }
                    $this->StopTransform();
                }
                break;
            }
            // text
            case 'text':
            case 'tspan': {
                if (isset($this->svgtextmode['text-anchor']) AND !empty($this->svgtext)) {
                    // @TODO: unsupported feature
                }
                // only basic support - advanced features must be implemented
                $this->svgtextmode['invisible'] = $invisible;
                if ($invisible) {
                    break;
                }
                array_push($this->svgstyles, $svgstyle);
                if (isset($attribs['x'])) {
                    $x = $this->getHTMLUnitToUnits($attribs['x'], 0, $this->svgunit, false);
                } elseif ($name == 'tspan') {
                    $x = $this->x;
                } else {
                    $x = 0;
                }
                if (isset($attribs['dx'])) {
                    $x += $this->getHTMLUnitToUnits($attribs['dx'], 0, $this->svgunit, false);
                }
                if (isset($attribs['y'])) {
                    $y = $this->getHTMLUnitToUnits($attribs['y'], 0, $this->svgunit, false);
                } elseif ($name == 'tspan') {
                    $y = $this->y;
                } else {
                    $y = 0;
                }
                if (isset($attribs['dy'])) {
                    $y += $this->getHTMLUnitToUnits($attribs['dy'], 0, $this->svgunit, false);
                }
                $svgstyle['text-color'] = $svgstyle['fill'];
                $this->svgtext = '';
                if (isset($svgstyle['text-anchor'])) {
                    $this->svgtextmode['text-anchor'] = $svgstyle['text-anchor'];
                } else {
                    $this->svgtextmode['text-anchor'] = 'start';
                }
                if (isset($svgstyle['direction'])) {
                    if ($svgstyle['direction'] == 'rtl') {
                        $this->svgtextmode['rtl'] = true;
                    } else {
                        $this->svgtextmode['rtl'] = false;
                    }
                } else {
                    $this->svgtextmode['rtl'] = false;
                }
                if (isset($svgstyle['stroke']) AND ($svgstyle['stroke'] != 'none') AND isset($svgstyle['stroke-width']) AND ($svgstyle['stroke-width'] > 0)) {
                    $this->svgtextmode['stroke'] = $this->getHTMLUnitToUnits($svgstyle['stroke-width'], 0, $this->svgunit, false);
                } else {
                    $this->svgtextmode['stroke'] = false;
                }
                $this->StartTransform();
                $this->SVGTransform($tm);
                $obstyle = $this->setSVGStyles($svgstyle, $prev_svgstyle, $x, $y, 1, 1);
                $this->x = $x;
                $this->y = $y;
                break;
            }
            // use
            case 'use': {
                if (isset($attribs['xlink:href']) AND !empty($attribs['xlink:href'])) {
                    $svgdefid = substr($attribs['xlink:href'], 1);
                    if (isset($this->svgdefs[$svgdefid])) {
                        $use = $this->svgdefs[$svgdefid];
                        if (isset($attribs['xlink:href'])) {
                            unset($attribs['xlink:href']);
                        }
                        if (isset($attribs['id'])) {
                            unset($attribs['id']);
                        }
                        if (isset($use['attribs']['x']) AND isset($attribs['x'])) {
                            $attribs['x'] += $use['attribs']['x'];
                        }
                        if (isset($use['attribs']['y']) AND isset($attribs['y'])) {
                            $attribs['y'] += $use['attribs']['y'];
                        }
                        if (empty($attribs['style'])) {
                            $attribs['style'] = '';
                        }
                        if (!empty($use['attribs']['style'])) {
                            // merge styles
                            $attribs['style'] = str_replace(';;',';',';'.$use['attribs']['style'].$attribs['style']);
                        }
                        $attribs = array_merge($use['attribs'], $attribs);
                        $this->startSVGElementHandler($parser, $use['name'], $attribs);
                        return;
                    }
                }
                break;
            }
            default: {
                break;
            }
        } // end of switch
        // process child elements
        if (!empty($attribs['child_elements'])) {
            $child_elements = $attribs['child_elements'];
            unset($attribs['child_elements']);
            foreach($child_elements as $child_element) {
                if (empty($child_element['attribs']['closing_tag'])) {
                    $this->startSVGElementHandler('child-tag', $child_element['name'], $child_element['attribs']);
                } else {
                    if (isset($child_element['attribs']['content'])) {
                        $this->svgtext = $child_element['attribs']['content'];
                    }
                    $this->endSVGElementHandler('child-tag', $child_element['name']);
                }
            }
        }
    }

    /**
     * Sets the closing SVG element handler function for the XML parser.
     * @param $parser (resource) The first parameter, parser, is a reference to the XML parser calling the handler.
     * @param $name (string) The second parameter, name, contains the name of the element for which this handler is called. If case-folding is in effect for this parser, the element name will be in uppercase letters.
     * @author Nicola Asuni
     * @since 5.0.000 (2010-05-02)
     * @protected
     */
    protected function endSVGElementHandler($parser, $name) {
        $name = $this->removeTagNamespace($name);
        if ($this->svgdefsmode AND !in_array($name, array('defs', 'clipPath', 'linearGradient', 'radialGradient', 'stop'))) {;
            if (end($this->svgdefs) !== FALSE) {
                $last_svgdefs_id = key($this->svgdefs);
                if (isset($this->svgdefs[$last_svgdefs_id]['attribs']['child_elements'])) {
                    foreach($this->svgdefs[$last_svgdefs_id]['attribs']['child_elements'] as $child_element) {
                        if (isset($child_element['attribs']['id']) AND ($child_element['name'] == $name)) {
                            $this->svgdefs[$last_svgdefs_id]['attribs']['child_elements'][$child_element['attribs']['id'].'_CLOSE'] = array('name' => $name, 'attribs' => array('closing_tag' => TRUE, 'content' => $this->svgtext));
                            return;
                        }
                    }
                    if ($this->svgdefs[$last_svgdefs_id]['name'] == $name) {
                        $this->svgdefs[$last_svgdefs_id]['attribs']['child_elements'][$last_svgdefs_id.'_CLOSE'] = array('name' => $name, 'attribs' => array('closing_tag' => TRUE, 'content' => $this->svgtext));
                        return;
                    }
                }
            }
            return;
        }
        switch($name) {
            case 'defs': {
                $this->svgdefsmode = false;
                break;
            }
            // clipPath
            case 'clipPath': {
                $this->svgclipmode = false;
                break;
            }
            case 'svg': {
                if (--$this->svg_tag_depth <= 0) {
                    break;
                }
            }
            case 'g': {
                // ungroup: remove last style from array
                array_pop($this->svgstyles);
                $this->StopTransform();
                break;
            }
            case 'text':
            case 'tspan': {
                if ($this->svgtextmode['invisible']) {
                    // This implementation must be fixed to following the rule:
                    // If the 'visibility' property is set to hidden on a 'tspan', 'tref' or 'altGlyph' element, then the text is invisible but still takes up space in text layout calculations.
                    break;
                }
                // print text
                $text = $this->svgtext;
                //$text = $this->stringTrim($text);
                $textlen = $this->GetStringWidth($text);
                if ($this->svgtextmode['text-anchor'] != 'start') {
                    // check if string is RTL text
                    if ($this->svgtextmode['text-anchor'] == 'end') {
                        if ($this->svgtextmode['rtl']) {
                            $this->x += $textlen;
                        } else {
                            $this->x -= $textlen;
                        }
                    } elseif ($this->svgtextmode['text-anchor'] == 'middle') {
                        if ($this->svgtextmode['rtl']) {
                            $this->x += ($textlen / 2);
                        } else {
                            $this->x -= ($textlen / 2);
                        }
                    }
                }
                $textrendermode = $this->textrendermode;
                $textstrokewidth = $this->textstrokewidth;
                $this->setTextRenderingMode($this->svgtextmode['stroke'], true, false);
                if ($name == 'text') {
                    // store current coordinates
                    $tmpx = $this->x;
                    $tmpy = $this->y;
                }
                // print the text
                $this->Cell($textlen, 0, $text, 0, 0, '', false, '', 0, false, 'L', 'T');
                if ($name == 'text') {
                    // restore coordinates
                    $this->x = $tmpx;
                    $this->y = $tmpy;
                }
                // restore previous rendering mode
                $this->textrendermode = $textrendermode;
                $this->textstrokewidth = $textstrokewidth;
                $this->svgtext = '';
                $this->StopTransform();
                if (!$this->svgdefsmode) {
                    array_pop($this->svgstyles);
                }
                break;
            }
            default: {
                break;
            }
        }
    }

    /**
     * Sets the character data handler function for the XML parser.
     * @param $parser (resource) The first parameter, parser, is a reference to the XML parser calling the handler.
     * @param $data (string) The second parameter, data, contains the character data as a string.
     * @author Nicola Asuni
     * @since 5.0.000 (2010-05-02)
     * @protected
     */
    protected function segSVGContentHandler($parser, $data) {
        $this->svgtext .= $data;
    }

    // --- END SVG METHODS -----------------------------------------------------

} // END OF TCPDF CLASS

//============================================================+
// END OF FILE
//============================================================+
Admidio API API documentation generated by ApiGen