WooCommerce PDF Invoices & Packing Slips - Version 3.0.0

Version Description

  • Libraries: Updated dompdf to 2.0.0, addressing security vulnerabilities and introducing some changes that could possibly break high level customized setups (more information)
  • Fix: respect custom (filtered) woocommerce template paths
  • Fix: Check if the invoice is allowed before the shortcode output
  • UI: Link to Professional extension for packing slip attachments
Download this release

Release Info

Developer wpovernight
Plugin Icon 128x128 WooCommerce PDF Invoices & Packing Slips
Version 3.0.0
Comparing to
See all releases

Code changes from version 2.16.0 to 3.0.0

Files changed (74) hide show
  1. assets/css/settings-styles.css +5 -1
  2. assets/css/settings-styles.min.css +1 -1
  3. composer.json +1 -1
  4. composer.lock +77 -7
  5. includes/class-wcpdf-admin.php +1 -1
  6. includes/class-wcpdf-font-synchronizer.php +13 -10
  7. includes/class-wcpdf-frontend.php +5 -0
  8. includes/class-wcpdf-install.php +10 -1
  9. includes/class-wcpdf-pdf-maker.php +8 -10
  10. includes/class-wcpdf-settings-callbacks.php +13 -1
  11. includes/class-wcpdf-settings-debug.php +0 -11
  12. includes/class-wcpdf-settings-general.php +9 -10
  13. includes/class-wcpdf-settings.php +42 -10
  14. includes/documents/class-wcpdf-packing-slip.php +27 -2
  15. readme.txt +7 -1
  16. vendor/composer/autoload_classmap.php +0 -5
  17. vendor/composer/autoload_psr4.php +1 -0
  18. vendor/composer/autoload_static.php +8 -5
  19. vendor/composer/installed.json +85 -12
  20. vendor/composer/installed.php +15 -6
  21. vendor/dompdf/dompdf/README.md +0 -6
  22. vendor/dompdf/dompdf/VERSION +1 -1
  23. vendor/dompdf/dompdf/composer.json +1 -0
  24. vendor/dompdf/dompdf/lib/Cpdf.php +178 -170
  25. vendor/dompdf/dompdf/lib/fonts/dompdf_font_family_cache.dist.php +0 -97
  26. vendor/dompdf/dompdf/lib/fonts/installed-fonts.dist.json +80 -0
  27. vendor/dompdf/dompdf/lib/html5lib/Data.php +0 -123
  28. vendor/dompdf/dompdf/lib/html5lib/InputStream.php +0 -299
  29. vendor/dompdf/dompdf/lib/html5lib/Parser.php +0 -37
  30. vendor/dompdf/dompdf/lib/html5lib/Tokenizer.php +0 -2470
  31. vendor/dompdf/dompdf/lib/html5lib/TreeBuilder.php +0 -3989
  32. vendor/dompdf/dompdf/lib/html5lib/named-character-references.ser +0 -1
  33. vendor/dompdf/dompdf/lib/res/html.css +1 -1
  34. vendor/dompdf/dompdf/src/Adapter/CPDF.php +159 -415
  35. vendor/dompdf/dompdf/src/Adapter/GD.php +155 -333
  36. vendor/dompdf/dompdf/src/Adapter/PDFLib.php +145 -374
  37. vendor/dompdf/dompdf/src/Canvas.php +153 -145
  38. vendor/dompdf/dompdf/src/Cellmap.php +44 -54
  39. vendor/dompdf/dompdf/src/Css/Color.php +4 -4
  40. vendor/dompdf/dompdf/src/Css/Style.php +1431 -1324
  41. vendor/dompdf/dompdf/src/Css/Stylesheet.php +44 -81
  42. vendor/dompdf/dompdf/src/Dompdf.php +124 -148
  43. vendor/dompdf/dompdf/src/FontMetrics.php +145 -125
  44. vendor/dompdf/dompdf/src/Frame.php +29 -81
  45. vendor/dompdf/dompdf/src/Frame/Factory.php +2 -2
  46. vendor/dompdf/dompdf/src/Frame/FrameList.php +0 -35
  47. vendor/dompdf/dompdf/src/Frame/FrameTree.php +16 -4
  48. vendor/dompdf/dompdf/src/Frame/FrameTreeIterator.php +1 -4
  49. vendor/dompdf/dompdf/src/Frame/FrameTreeList.php +0 -35
  50. vendor/dompdf/dompdf/src/FrameDecorator/AbstractFrameDecorator.php +62 -37
  51. vendor/dompdf/dompdf/src/FrameDecorator/Block.php +4 -4
  52. vendor/dompdf/dompdf/src/FrameDecorator/Image.php +9 -3
  53. vendor/dompdf/dompdf/src/FrameDecorator/Inline.php +13 -11
  54. vendor/dompdf/dompdf/src/FrameDecorator/ListBullet.php +0 -15
  55. vendor/dompdf/dompdf/src/FrameDecorator/Page.php +4 -6
  56. vendor/dompdf/dompdf/src/FrameDecorator/Table.php +9 -86
  57. vendor/dompdf/dompdf/src/FrameDecorator/TableCell.php +1 -1
  58. vendor/dompdf/dompdf/src/FrameDecorator/TableRow.php +0 -29
  59. vendor/dompdf/dompdf/src/FrameDecorator/Text.php +18 -20
  60. vendor/dompdf/dompdf/src/FrameReflower/AbstractFrameReflower.php +34 -36
  61. vendor/dompdf/dompdf/src/FrameReflower/Block.php +17 -16
  62. vendor/dompdf/dompdf/src/FrameReflower/Image.php +6 -6
  63. vendor/dompdf/dompdf/src/FrameReflower/Inline.php +11 -7
  64. vendor/dompdf/dompdf/src/FrameReflower/ListBullet.php +1 -1
  65. vendor/dompdf/dompdf/src/FrameReflower/Page.php +5 -8
  66. vendor/dompdf/dompdf/src/FrameReflower/Table.php +28 -30
  67. vendor/dompdf/dompdf/src/FrameReflower/TableCell.php +4 -2
  68. vendor/dompdf/dompdf/src/FrameReflower/TableRow.php +2 -2
  69. vendor/dompdf/dompdf/src/FrameReflower/TableRowGroup.php +2 -2
  70. vendor/dompdf/dompdf/src/FrameReflower/Text.php +26 -29
  71. vendor/dompdf/dompdf/src/Helpers.php +107 -47
  72. vendor/dompdf/dompdf/src/Image/Cache.php +93 -104
  73. vendor/dompdf/dompdf/src/LineBox.php +3 -3
  74. vendor/dompdf/dompdf/src/Options.php +23 -7
assets/css/settings-styles.css CHANGED
@@ -392,6 +392,10 @@ body.woocommerce_page_wpo_wcpdf_options_page {
392
  border-left: 4px solid #51266b;
393
  }
394
 
 
 
 
 
395
  #wpo-wcpdf-preview-wrapper[data-preview-states="1"] .sidebar {
396
  flex: 0 0 100%;
397
  }
@@ -901,4 +905,4 @@ body.woocommerce_page_wpo_wcpdf_options_page {
901
  #wpo-wcpdf-preview-wrapper .slider.slide-left:after {
902
  right: 0;
903
  }
904
- }
392
  border-left: 4px solid #51266b;
393
  }
394
 
395
+ #wpo-wcpdf-settings .notice-info.inline {
396
+ border-left-color: #51266b;
397
+ }
398
+
399
  #wpo-wcpdf-preview-wrapper[data-preview-states="1"] .sidebar {
400
  flex: 0 0 100%;
401
  }
905
  #wpo-wcpdf-preview-wrapper .slider.slide-left:after {
906
  right: 0;
907
  }
908
+ }
assets/css/settings-styles.min.css CHANGED
@@ -1 +1 @@
1
- span.wpo-warning{display:inline-block;border:1px solid red;border-left:4px solid red;padding:5px 15px;background-color:#fff}.wcpdf-extensions-ad{position:relative;min-height:90px;border:1px solid #3d5c99;background-color:#ebf5ff;padding:15px;padding-left:100px;margin-top:15px}img.wpo-helper{position:absolute;bottom:0;left:3px}.wcpdf-extensions-ad h3{margin:0}.wcpdf-extensions-ad ul{margin:0;margin-left:1.5em}.wcpdf-extensions li{margin:0}.wcpdf-extensions li ul{list-style-type:square;margin-top:.5em;margin-bottom:.5em}.wcpdf-extensions>li:before{content:"";border-color:transparent transparent transparent #111;border-style:solid;border-width:.35em .35em .35em .45em;display:block;height:0;width:0;left:-1em;top:.9em;position:relative}.wcpdf-extensions li:not(.expanded){cursor:pointer}.wcpdf-extensions .expanded:before{border-color:#111 transparent transparent transparent;left:-1.17em;border-width:.45em .45em .35em .35em!important}.wcpdf-extensions .more{padding:10px;background-color:#fff;border:1px solid #ccc;border-radius:5px}.wcpdf-extensions table td{vertical-align:top}.dropbox-logo{margin-bottom:-10px;margin-right:10px}.cloud-logo{margin-bottom:-10px;margin-top:-5px;margin-right:10px}#img-header_logo{max-height:200px;width:auto;max-width:100%}.multiple-text-input label{min-width:120px;display:inline-block}table.wcpdf_documents_settings_list{width:100%;border-collapse:collapse;border-spacing:0;background-color:#fff;border-top:2px solid #000}table.wcpdf_documents_settings_list tr.odd{background-color:#ebf5ff}table.wcpdf_documents_settings_list td{padding:5px}table.wcpdf_documents_settings_list a{text-decoration:none}table.wcpdf_documents_settings_list td.settings-icon{text-align:right}table.wcpdf_documents_settings_list td.title{font-weight:700}.wcpdf-settings-sections ul{height:3em}.wcpdf-settings-sections ul li{float:left;margin-right:10px}.wcpdf-settings-sections ul li a{text-decoration:none;display:inline-block;padding:.8em 1em;color:#50575e;border:1px solid #c3c4c7;box-sizing:border-box}.wcpdf-settings-sections ul li a.active{border:2px solid #51266b;padding:calc(.8em - 1px) calc(1em - 1px);color:#000}.wcpdf_document_settings_sections{margin-bottom:40px;position:relative}.wcpdf_document_settings_sections>h2{cursor:pointer;padding:1em .8em;margin:0;border:1px solid #eaeaea}.wcpdf_document_settings_sections ul{background:#fff;list-style:none;margin:0;padding:0;width:100%;display:block;height:auto;display:none;box-sizing:border-box;position:absolute;border-left:1px solid #eaeaea;border-right:1px solid #eaeaea;z-index:1000;box-shadow:0 35px 35px -8px rgba(0,0,0,.1);-webkit-box-shadow:0 35px 35px -8px rgba(0,0,0,.1)}.wcpdf_document_settings_sections ul.active{display:block}.wcpdf_document_settings_sections ul li{box-sizing:border-box;padding:0;margin-bottom:0;border-bottom:1px solid #eaeaea;font-size:1.1em}.wcpdf_document_settings_sections ul li:hover{cursor:pointer;background:#51266b;color:#fff}.wcpdf_document_settings_sections ul li:hover a{color:#fff}.wcpdf_document_settings_sections ul li a{color:#000;text-decoration:none;padding:1.2em 1.6em;display:block}.wcpdf_document_settings_sections .arrow-down{font-size:.7em;color:#999;margin-left:8px;font-weight:400;float:right}.wcpdf_document_settings_sections p:hover,.wcpdf_document_settings_sections p:hover>.arrow-down{color:#222}.edit-next-number{opacity:.5}.edit-next-number:hover{opacity:1;cursor:pointer}body.woocommerce_page_wpo_wcpdf_options_page{background:#fff}.wrap [class$=icon32]+h2{font-size:18px;padding:1em}.wrap .notice{margin:15px 0 0}.nav-tab-wrapper a.nav-tab{background:0 0;border:none;border-bottom:3px solid transparent;padding:1em 0;margin:0 1.2em;font-size:15px}.nav-tab-wrapper a.nav-tab.nav-tab-active{border-bottom:3px solid #51266b}#wpo-wcpdf-preview-wrapper{width:100%;height:auto;position:relative;display:flex;align-items:flex-start}#wpo-wcpdf-preview-wrapper .preview-document,#wpo-wcpdf-preview-wrapper .sidebar{transition:.3s ease-in-out}#wpo-wcpdf-preview-wrapper .sidebar{height:auto;padding:4em 0 0 0;box-sizing:border-box;background:0 0;flex:0 0 35%;overflow-x:hidden}#wpo-wcpdf-preview-wrapper .sidebar>form{background:#fff;overflow:visible;padding:0;margin-left:2em;box-sizing:border-box;width:calc(100% - 4em);max-width:50vw}#wpo-wcpdf-preview-wrapper .sidebar>form.editor{max-width:none}#wpo-wcpdf-preview-wrapper .sidebar .form-table,#wpo-wcpdf-preview-wrapper .sidebar .form-table>tbody,#wpo-wcpdf-preview-wrapper .sidebar .form-table>tbody>tr,#wpo-wcpdf-preview-wrapper .sidebar .form-table>tbody>tr>td,#wpo-wcpdf-preview-wrapper .sidebar .form-table>tbody>tr>th,#wpo-wcpdf-preview-wrapper .sidebar .form-table>tr,#wpo-wcpdf-preview-wrapper .sidebar .form-table>tr>td,#wpo-wcpdf-preview-wrapper .sidebar .form-table>tr>th{display:block;width:100%;padding:0}#wpo-wcpdf-preview-wrapper .sidebar .form-table>tbody>tr>th,#wpo-wcpdf-preview-wrapper .sidebar .form-table>tr>th{padding-bottom:.6em}#wpo-wcpdf-preview-wrapper .sidebar .form-table>tbody>tr>td,#wpo-wcpdf-preview-wrapper .sidebar .form-table>tr>td{padding-bottom:2.4em}#wpo-wcpdf-preview-wrapper .sidebar .form-table>tbody>tr>td p.description,#wpo-wcpdf-preview-wrapper .sidebar .form-table>tr>td>p.description{font-size:.85em;padding-top:.7em}#wpo-wcpdf-preview-wrapper .sidebar .form-table>tbody>tr>td>input[type=text],#wpo-wcpdf-preview-wrapper .sidebar .form-table>tbody>tr>td>select,#wpo-wcpdf-preview-wrapper .sidebar .form-table>tbody>tr>td>textarea,#wpo-wcpdf-preview-wrapper .sidebar .form-table>tr>td>input[type=text],#wpo-wcpdf-preview-wrapper .sidebar .form-table>tr>td>select,#wpo-wcpdf-preview-wrapper .sidebar .form-table>tr>td>textarea{max-width:none;width:100%}#wpo-wcpdf-preview-wrapper input[type=text][size]{width:auto!important;max-width:100%!important}#wpo-wcpdf-preview-wrapper .sidebar .form-table>tbody>tr>td>input#next_invoice_number{width:auto!important}#wpo-wcpdf-preview-wrapper[data-preview-states="1"] .sidebar .form-table,#wpo-wcpdf-preview-wrapper[data-preview-states="2"][data-preview-state=closed] .sidebar .form-table,#wpo-wcpdf-preview-wrapper[data-preview-states="3"][data-preview-state=closed] .sidebar .form-table{display:table}#wpo-wcpdf-preview-wrapper[data-preview-states="1"] .sidebar .form-table tbody,#wpo-wcpdf-preview-wrapper[data-preview-states="2"][data-preview-state=closed] .sidebar .form-table tbody,#wpo-wcpdf-preview-wrapper[data-preview-states="3"][data-preview-state=closed] .sidebar .form-table tbody{display:table-row-group}#wpo-wcpdf-preview-wrapper[data-preview-states="1"] .sidebar .form-table tbody tr,#wpo-wcpdf-preview-wrapper[data-preview-states="1"] .sidebar .form-table tr,#wpo-wcpdf-preview-wrapper[data-preview-states="2"][data-preview-state=closed] .sidebar .form-table tbody tr,#wpo-wcpdf-preview-wrapper[data-preview-states="2"][data-preview-state=closed] .sidebar .form-table tr,#wpo-wcpdf-preview-wrapper[data-preview-states="3"][data-preview-state=closed] .sidebar .form-table tbody tr,#wpo-wcpdf-preview-wrapper[data-preview-states="3"][data-preview-state=closed] .sidebar .form-table tr{display:table-row}#wpo-wcpdf-preview-wrapper[data-preview-states="1"] .sidebar .form-table>tbody>tr>td,#wpo-wcpdf-preview-wrapper[data-preview-states="1"] .sidebar .form-table>tbody>tr>th,#wpo-wcpdf-preview-wrapper[data-preview-states="2"][data-preview-state=closed] .sidebar .form-table>tbody>tr>td,#wpo-wcpdf-preview-wrapper[data-preview-states="2"][data-preview-state=closed] .sidebar .form-table>tbody>tr>th,#wpo-wcpdf-preview-wrapper[data-preview-states="3"][data-preview-state=closed] .sidebar .form-table>tbody>tr>td,#wpo-wcpdf-preview-wrapper[data-preview-states="3"][data-preview-state=closed] .sidebar .form-table>tbody>tr>th{display:table-cell;padding:15px 10px 15px 0;width:auto}#wpo-wcpdf-preview-wrapper[data-preview-states="1"] .sidebar .form-table>tbody>tr>th,#wpo-wcpdf-preview-wrapper[data-preview-states="2"][data-preview-state=closed] .sidebar .form-table>tbody>tr>th,#wpo-wcpdf-preview-wrapper[data-preview-states="3"][data-preview-state=closed] .sidebar .form-table>tbody>tr>th{width:300px!important}#wpo-wcpdf-settings .form-table .ui-tabs-nav{padding-left:0!important;margin-left:0!important}#wpo-wcpdf-settings .translations input,#wpo-wcpdf-settings .translations textarea{width:100%}#wpo-wcpdf-settings .wcpdf-attachment-settings-hint{border-left:4px solid #51266b}#wpo-wcpdf-preview-wrapper[data-preview-states="1"] .sidebar{flex:0 0 100%}#wpo-wcpdf-preview-wrapper[data-preview-states="2"][data-preview-state=closed] .sidebar{flex:0 0 95%}#wpo-wcpdf-preview-wrapper[data-preview-states="2"][data-preview-state=full] .sidebar{flex:0 0 95%;margin-left:-95%}#wpo-wcpdf-preview-wrapper[data-preview-states="3"][data-preview-state=closed] .sidebar{flex:0 0 95%}#wpo-wcpdf-preview-wrapper[data-preview-states="3"][data-preview-state=sidebar] .sidebar{flex:0 0 35%}#wpo-wcpdf-preview-wrapper[data-preview-states="3"][data-preview-state=full] .sidebar{margin-left:-35%}#wpo-wcpdf-preview-wrapper[data-preview-states="3"][data-from-preview-state=full] .sidebar{transition-delay:.4s}#wpo-wcpdf-preview-wrapper .preview-document{padding:0;box-sizing:border-box;position:sticky;top:2.4em;flex:0 0 60%}#wpo-wcpdf-preview-wrapper[data-preview-states="1"] .preview-document{display:none}#wpo-wcpdf-preview-wrapper[data-preview-states="2"][data-preview-state=closed] .preview-document{display:none}#wpo-wcpdf-preview-wrapper[data-preview-states="2"][data-preview-state=full] .preview-document{flex:0 0 95%}#wpo-wcpdf-preview-wrapper[data-preview-states="3"][data-preview-state=closed] .preview-document{flex:0 0 60%;margin-right:-60%}#wpo-wcpdf-preview-wrapper[data-preview-states="3"][data-preview-state=sidebar] .preview-document{flex:0 0 60%}#wpo-wcpdf-preview-wrapper[data-preview-states="3"][data-preview-state=full] .preview-document{flex:0 0 95%}#wpo-wcpdf-preview-wrapper[data-preview-states="3"][data-from-preview-state=full] .preview-document{transition-delay:.4s}.preview-document .preview{width:100%;box-sizing:border-box;padding-right:5%}.preview-document .preview>#preview-canvas{display:block;max-width:800px;max-height:85vh;width:auto!important;margin:0 auto;background:#fff;box-shadow:0 0 35px -8px rgba(0,0,0,.12);-webkit-box-shadow:0 0 35px -8px rgba(0,0,0,.12)}#wpo-wcpdf-preview-wrapper[data-preview-states="2"] #preview-canvas{max-height:170vh}#wpo-wcpdf-preview-wrapper[data-preview-states="3"][data-from-preview-state=sidebar] #preview-canvas{max-height:170vh;transition:max-height .4s ease-in-out .3s}#wpo-wcpdf-preview-wrapper[data-preview-states="3"][data-from-preview-state=full] #preview-canvas{transition:max-height .4s ease-in-out 0s}.preview-document .preview-data-wrapper{width:100%;height:4em}.preview-document .preview-data-wrapper .preview-document-type,.preview-document .preview-data-wrapper .preview-order-data{float:right}.preview-document .preview-data-wrapper .preview-document-type{margin-right:30px}.preview-document .preview-data-wrapper .preview-document-type ul>li{text-decoration:none;color:initial;padding:1.4em 1.6em}.preview-document .preview-data-wrapper .preview-document-type ul>li:hover{color:#fff!important}.preview-document .preview-data-wrapper .save-settings{padding:1em 0 0 0;float:right;overflow:hidden;position:relative}.preview-document .preview-data-wrapper .save-settings p{padding:0;margin:0 0 0 2em;position:relative;margin-right:-200px;transition:margin-right .3s ease-out}.preview-document .preview-data-wrapper .save-settings p:after{content:'';display:block;pointer-events:none;position:absolute;box-sizing:border-box;border-radius:3px;right:0;top:0;background:0 0;width:100%;height:100%;z-index:10;border:0 solid #fff;animation:border-pulse 4s infinite}@keyframes border-pulse{0%{border-color:rgba(255,255,255,0);border-width:8px}50%{border-color:#fff;border-width:0}}.preview-document .preview-data-wrapper .save-settings p input:focus{outline-width:0;box-shadow:none}.preview-document .preview-data p{padding:1.4em 0;margin:0;color:#666;text-align:right;cursor:pointer;font-weight:lighter;float:right}.preview-document .preview-data p.order-search{display:none}.preview-document .preview-data input{float:right;margin:1em 0 0 1em;padding:.1em .5em;width:20ch;margin-right:-25ch;display:none}.preview-document .preview-data input.active{margin-right:0;display:inline-block}.preview-document .preview-data ul{position:absolute;right:0;top:4em;background:#fff;box-shadow:0 0 25px -10px rgba(0,0,0,.2);-webkit-box-shadow:0 0 25px -10px rgba(0,0,0,.2);list-style:none;margin:0;padding:0;min-width:24em;display:block;height:0;overflow:hidden}.preview-document .preview-data ul.active{height:auto;z-index:1}.preview-document .preview-data ul li{box-sizing:border-box;padding:0;margin-bottom:0;border-bottom:1px solid #eaeaea;font-size:1.1em}.preview-document .preview-data ul li:hover{cursor:pointer;background:#51266b;color:#fff}.preview-document .preview-data ul li a,.preview-document .preview-data.preview-order-data ul li{display:block;padding:1.4em 1.6em}.preview-document .preview-data .arrow-down{font-size:.8em;color:#999;margin-left:8px}.preview-document .preview-data p:hover,.preview-document .preview-data p:hover>.arrow-down{color:#222}.preview-document .preview-data #preview-order-search-results{display:none;position:absolute;right:0;top:4em;width:300px;box-shadow:0 0 25px -10px rgba(0,0,0,.2);-webkit-box-shadow:0 0 25px -10px rgba(0,0,0,.2);padding:20px 0;background-color:#fff}.preview-document .preview-data #preview-order-search-results a{display:block;border-left:1px solid #999;border-right:1px solid #999;border-top:1px solid #999;color:#000;padding:10px;margin:0 20px;text-decoration:none;cursor:pointer}.preview-document .preview-data #preview-order-search-results a:last-child{border-bottom:1px solid #999}.preview-document .preview-data #preview-order-search-results a:hover{background-color:#51266b;color:#fff}.preview-document .preview-data #preview-order-search-results .order-number{font-weight:700}.preview-document .preview-data #preview-order-search-results .date,.preview-document .preview-data #preview-order-search-results .total{margin-top:6px;display:inline-block}.preview-document .preview-data #preview-order-search-results .total{float:right}.preview-document .preview-data #preview-order-search-results .error{margin:0 20px}.preview-document .preview-order-search-wrapper{position:relative;float:right}.preview-document .preview-order-search-wrapper img.preview-order-search-clear{position:absolute;width:30px;height:16px;top:22px;right:6px;display:none;cursor:pointer}#wpo-wcpdf-preview-wrapper .gutter{flex:0 0 5%;position:sticky;top:2.4em;height:170vh}#wpo-wcpdf-preview-wrapper[data-preview-states="2"][data-preview-state=closed] .gutter .slide-left,#wpo-wcpdf-preview-wrapper[data-preview-states="3"][data-preview-state=closed] .gutter .slide-left{float:right}#wpo-wcpdf-preview-wrapper[data-preview-states="3"][data-preview-state=sidebar] .gutter .slide-left{border:none}#wpo-wcpdf-preview-wrapper .slider{box-sizing:border-box;padding-top:2.4em;color:#999;font-weight:700;cursor:pointer;font-size:.7em;line-height:1em;width:50%;height:100%;float:left}#wpo-wcpdf-preview-wrapper .slider.slide-left{text-align:right;padding-right:10px;border-right:1px solid #ccc}#wpo-wcpdf-preview-wrapper .slider.slide-right{text-align:left;padding-left:10px;border-left:1px solid #ccc;display:none}#wpo-wcpdf-preview-wrapper .gutter-arrow{width:0;height:0;border-top:3px solid transparent;border-bottom:3px solid transparent;display:block}#wpo-wcpdf-preview-wrapper .arrow-left{border-right:7px solid #999;float:right}#wpo-wcpdf-preview-wrapper .arrow-right{border-left:7px solid #999}#wpo-wcpdf-preview-wrapper .slider:hover>.arrow-left{border-right:7px solid #222}#wpo-wcpdf-preview-wrapper .slider:hover>.arrow-right{border-left:7px solid #222}#wpo-wcpdf-preview-wrapper .slider.slide-left:after{content:'Preview';position:absolute;top:1.55em;right:2em;font-size:1.4em;display:none}#wpo-wcpdf-preview-wrapper .slider.slide-right:after{content:'Settings';position:absolute;top:1.55em;left:2em;font-size:1.4em;display:none}#wpo-wcpdf-preview-wrapper[data-preview-states="1"] .gutter{display:none}#wpo-wcpdf-preview-wrapper[data-preview-states="3"][data-preview-state=sidebar] .gutter{height:100vh}#wpo-wcpdf-preview-wrapper[data-preview-state=full] .slide-right:after{display:inline-block}#wpo-wcpdf-preview-wrapper[data-preview-state=closed] .slide-left:after{display:inline-block}#wpo-wcpdf-preview-wrapper.static .gutter,#wpo-wcpdf-preview-wrapper.static .preview-document{position:static!important}#wpo-wcpdf-preview-wrapper.static .sidebar{height:170vh!important;overflow:hidden}#wpo-wcpdf-preview-wrapper input.readonly,#wpo-wcpdf-preview-wrapper input[readonly],#wpo-wcpdf-preview-wrapper textarea.readonly,#wpo-wcpdf-preview-wrapper textarea[readonly]{background-color:#f8f8f8}#wpo-wcpdf-preview-wrapper[data-preview-state=sidebar] .select2.select2-container{width:100%!important}@media screen and (min-width:1920px){.preview-document .preview>#preview-canvas{max-width:900px}}@media screen and (max-width:1200px){.preview-document .preview>#preview-canvas{max-width:680px}.nav-tab-wrapper a.nav-tab{padding:1em 2em;margin:0 .5em .5em 0;border:1px solid #ccc;box-sizing:border-box;height:4em}.nav-tab-wrapper a.nav-tab.nav-tab-active{border:3px solid #51266b}}@media screen and (max-width:960px){.preview-document .preview>#preview-canvas{width:80vw!important}#wpo-wcpdf-preview-wrapper .sidebar>form{max-width:100%}#wpo-wcpdf-preview-wrapper[data-preview-state=closed] .select2.select2-container{width:100%!important}#wpo-wcpdf-preview-wrapper .sidebar .form-table,#wpo-wcpdf-preview-wrapper .sidebar .form-table>tbody,#wpo-wcpdf-preview-wrapper .sidebar .form-table>tbody>tr,#wpo-wcpdf-preview-wrapper .sidebar .form-table>tbody>tr>td,#wpo-wcpdf-preview-wrapper .sidebar .form-table>tbody>tr>th,#wpo-wcpdf-preview-wrapper .sidebar .form-table>tr,#wpo-wcpdf-preview-wrapper .sidebar .form-table>tr>td,#wpo-wcpdf-preview-wrapper .sidebar .form-table>tr>th{display:block!important}.preview-document .preview-data-wrapper{height:6em}.preview-document .preview-data p{padding:2.2em 0}#wpo-wcpdf-preview-wrapper .slider.slide-left:after,#wpo-wcpdf-preview-wrapper .slider.slide-right:after{top:1.5em;padding:1em;background:#fff;border:1px solid #ccc}#wpo-wcpdf-preview-wrapper .slider.slide-right:after{left:0}#wpo-wcpdf-preview-wrapper .slider.slide-left:after{right:0}}
1
+ span.wpo-warning{display:inline-block;border:1px solid red;border-left:4px solid red;padding:5px 15px;background-color:#fff}.wcpdf-extensions-ad{position:relative;min-height:90px;border:1px solid #3d5c99;background-color:#ebf5ff;padding:15px;padding-left:100px;margin-top:15px}img.wpo-helper{position:absolute;bottom:0;left:3px}.wcpdf-extensions-ad h3{margin:0}.wcpdf-extensions-ad ul{margin:0;margin-left:1.5em}.wcpdf-extensions li{margin:0}.wcpdf-extensions li ul{list-style-type:square;margin-top:.5em;margin-bottom:.5em}.wcpdf-extensions>li:before{content:"";border-color:transparent transparent transparent #111;border-style:solid;border-width:.35em .35em .35em .45em;display:block;height:0;width:0;left:-1em;top:.9em;position:relative}.wcpdf-extensions li:not(.expanded){cursor:pointer}.wcpdf-extensions .expanded:before{border-color:#111 transparent transparent transparent;left:-1.17em;border-width:.45em .45em .35em .35em!important}.wcpdf-extensions .more{padding:10px;background-color:#fff;border:1px solid #ccc;border-radius:5px}.wcpdf-extensions table td{vertical-align:top}.dropbox-logo{margin-bottom:-10px;margin-right:10px}.cloud-logo{margin-bottom:-10px;margin-top:-5px;margin-right:10px}#img-header_logo{max-height:200px;width:auto;max-width:100%}.multiple-text-input label{min-width:120px;display:inline-block}table.wcpdf_documents_settings_list{width:100%;border-collapse:collapse;border-spacing:0;background-color:#fff;border-top:2px solid #000}table.wcpdf_documents_settings_list tr.odd{background-color:#ebf5ff}table.wcpdf_documents_settings_list td{padding:5px}table.wcpdf_documents_settings_list a{text-decoration:none}table.wcpdf_documents_settings_list td.settings-icon{text-align:right}table.wcpdf_documents_settings_list td.title{font-weight:700}.wcpdf-settings-sections ul{height:3em}.wcpdf-settings-sections ul li{float:left;margin-right:10px}.wcpdf-settings-sections ul li a{text-decoration:none;display:inline-block;padding:.8em 1em;color:#50575e;border:1px solid #c3c4c7;box-sizing:border-box}.wcpdf-settings-sections ul li a.active{border:2px solid #51266b;padding:calc(.8em - 1px) calc(1em - 1px);color:#000}.wcpdf_document_settings_sections{margin-bottom:40px;position:relative}.wcpdf_document_settings_sections>h2{cursor:pointer;padding:1em .8em;margin:0;border:1px solid #eaeaea}.wcpdf_document_settings_sections ul{background:#fff;list-style:none;margin:0;padding:0;width:100%;display:block;height:auto;display:none;box-sizing:border-box;position:absolute;border-left:1px solid #eaeaea;border-right:1px solid #eaeaea;z-index:1000;box-shadow:0 35px 35px -8px rgba(0,0,0,.1);-webkit-box-shadow:0 35px 35px -8px rgba(0,0,0,.1)}.wcpdf_document_settings_sections ul.active{display:block}.wcpdf_document_settings_sections ul li{box-sizing:border-box;padding:0;margin-bottom:0;border-bottom:1px solid #eaeaea;font-size:1.1em}.wcpdf_document_settings_sections ul li:hover{cursor:pointer;background:#51266b;color:#fff}.wcpdf_document_settings_sections ul li:hover a{color:#fff}.wcpdf_document_settings_sections ul li a{color:#000;text-decoration:none;padding:1.2em 1.6em;display:block}.wcpdf_document_settings_sections .arrow-down{font-size:.7em;color:#999;margin-left:8px;font-weight:400;float:right}.wcpdf_document_settings_sections p:hover,.wcpdf_document_settings_sections p:hover>.arrow-down{color:#222}.edit-next-number{opacity:.5}.edit-next-number:hover{opacity:1;cursor:pointer}body.woocommerce_page_wpo_wcpdf_options_page{background:#fff}.wrap [class$=icon32]+h2{font-size:18px;padding:1em}.wrap .notice{margin:15px 0 0}.nav-tab-wrapper a.nav-tab{background:0 0;border:none;border-bottom:3px solid transparent;padding:1em 0;margin:0 1.2em;font-size:15px}.nav-tab-wrapper a.nav-tab.nav-tab-active{border-bottom:3px solid #51266b}#wpo-wcpdf-preview-wrapper{width:100%;height:auto;position:relative;display:flex;align-items:flex-start}#wpo-wcpdf-preview-wrapper .preview-document,#wpo-wcpdf-preview-wrapper .sidebar{transition:.3s ease-in-out}#wpo-wcpdf-preview-wrapper .sidebar{height:auto;padding:4em 0 0 0;box-sizing:border-box;background:0 0;flex:0 0 35%;overflow-x:hidden}#wpo-wcpdf-preview-wrapper .sidebar>form{background:#fff;overflow:visible;padding:0;margin-left:2em;box-sizing:border-box;width:calc(100% - 4em);max-width:50vw}#wpo-wcpdf-preview-wrapper .sidebar>form.editor{max-width:none}#wpo-wcpdf-preview-wrapper .sidebar .form-table,#wpo-wcpdf-preview-wrapper .sidebar .form-table>tbody,#wpo-wcpdf-preview-wrapper .sidebar .form-table>tbody>tr,#wpo-wcpdf-preview-wrapper .sidebar .form-table>tbody>tr>td,#wpo-wcpdf-preview-wrapper .sidebar .form-table>tbody>tr>th,#wpo-wcpdf-preview-wrapper .sidebar .form-table>tr,#wpo-wcpdf-preview-wrapper .sidebar .form-table>tr>td,#wpo-wcpdf-preview-wrapper .sidebar .form-table>tr>th{display:block;width:100%;padding:0}#wpo-wcpdf-preview-wrapper .sidebar .form-table>tbody>tr>th,#wpo-wcpdf-preview-wrapper .sidebar .form-table>tr>th{padding-bottom:.6em}#wpo-wcpdf-preview-wrapper .sidebar .form-table>tbody>tr>td,#wpo-wcpdf-preview-wrapper .sidebar .form-table>tr>td{padding-bottom:2.4em}#wpo-wcpdf-preview-wrapper .sidebar .form-table>tbody>tr>td p.description,#wpo-wcpdf-preview-wrapper .sidebar .form-table>tr>td>p.description{font-size:.85em;padding-top:.7em}#wpo-wcpdf-preview-wrapper .sidebar .form-table>tbody>tr>td>input[type=text],#wpo-wcpdf-preview-wrapper .sidebar .form-table>tbody>tr>td>select,#wpo-wcpdf-preview-wrapper .sidebar .form-table>tbody>tr>td>textarea,#wpo-wcpdf-preview-wrapper .sidebar .form-table>tr>td>input[type=text],#wpo-wcpdf-preview-wrapper .sidebar .form-table>tr>td>select,#wpo-wcpdf-preview-wrapper .sidebar .form-table>tr>td>textarea{max-width:none;width:100%}#wpo-wcpdf-preview-wrapper input[type=text][size]{width:auto!important;max-width:100%!important}#wpo-wcpdf-preview-wrapper .sidebar .form-table>tbody>tr>td>input#next_invoice_number{width:auto!important}#wpo-wcpdf-preview-wrapper[data-preview-states="1"] .sidebar .form-table,#wpo-wcpdf-preview-wrapper[data-preview-states="2"][data-preview-state=closed] .sidebar .form-table,#wpo-wcpdf-preview-wrapper[data-preview-states="3"][data-preview-state=closed] .sidebar .form-table{display:table}#wpo-wcpdf-preview-wrapper[data-preview-states="1"] .sidebar .form-table tbody,#wpo-wcpdf-preview-wrapper[data-preview-states="2"][data-preview-state=closed] .sidebar .form-table tbody,#wpo-wcpdf-preview-wrapper[data-preview-states="3"][data-preview-state=closed] .sidebar .form-table tbody{display:table-row-group}#wpo-wcpdf-preview-wrapper[data-preview-states="1"] .sidebar .form-table tbody tr,#wpo-wcpdf-preview-wrapper[data-preview-states="1"] .sidebar .form-table tr,#wpo-wcpdf-preview-wrapper[data-preview-states="2"][data-preview-state=closed] .sidebar .form-table tbody tr,#wpo-wcpdf-preview-wrapper[data-preview-states="2"][data-preview-state=closed] .sidebar .form-table tr,#wpo-wcpdf-preview-wrapper[data-preview-states="3"][data-preview-state=closed] .sidebar .form-table tbody tr,#wpo-wcpdf-preview-wrapper[data-preview-states="3"][data-preview-state=closed] .sidebar .form-table tr{display:table-row}#wpo-wcpdf-preview-wrapper[data-preview-states="1"] .sidebar .form-table>tbody>tr>td,#wpo-wcpdf-preview-wrapper[data-preview-states="1"] .sidebar .form-table>tbody>tr>th,#wpo-wcpdf-preview-wrapper[data-preview-states="2"][data-preview-state=closed] .sidebar .form-table>tbody>tr>td,#wpo-wcpdf-preview-wrapper[data-preview-states="2"][data-preview-state=closed] .sidebar .form-table>tbody>tr>th,#wpo-wcpdf-preview-wrapper[data-preview-states="3"][data-preview-state=closed] .sidebar .form-table>tbody>tr>td,#wpo-wcpdf-preview-wrapper[data-preview-states="3"][data-preview-state=closed] .sidebar .form-table>tbody>tr>th{display:table-cell;padding:15px 10px 15px 0;width:auto}#wpo-wcpdf-preview-wrapper[data-preview-states="1"] .sidebar .form-table>tbody>tr>th,#wpo-wcpdf-preview-wrapper[data-preview-states="2"][data-preview-state=closed] .sidebar .form-table>tbody>tr>th,#wpo-wcpdf-preview-wrapper[data-preview-states="3"][data-preview-state=closed] .sidebar .form-table>tbody>tr>th{width:300px!important}#wpo-wcpdf-settings .form-table .ui-tabs-nav{padding-left:0!important;margin-left:0!important}#wpo-wcpdf-settings .translations input,#wpo-wcpdf-settings .translations textarea{width:100%}#wpo-wcpdf-settings .wcpdf-attachment-settings-hint{border-left:4px solid #51266b}#wpo-wcpdf-settings .notice-info.inline{border-left-color:#51266b}#wpo-wcpdf-preview-wrapper[data-preview-states="1"] .sidebar{flex:0 0 100%}#wpo-wcpdf-preview-wrapper[data-preview-states="2"][data-preview-state=closed] .sidebar{flex:0 0 95%}#wpo-wcpdf-preview-wrapper[data-preview-states="2"][data-preview-state=full] .sidebar{flex:0 0 95%;margin-left:-95%}#wpo-wcpdf-preview-wrapper[data-preview-states="3"][data-preview-state=closed] .sidebar{flex:0 0 95%}#wpo-wcpdf-preview-wrapper[data-preview-states="3"][data-preview-state=sidebar] .sidebar{flex:0 0 35%}#wpo-wcpdf-preview-wrapper[data-preview-states="3"][data-preview-state=full] .sidebar{margin-left:-35%}#wpo-wcpdf-preview-wrapper[data-preview-states="3"][data-from-preview-state=full] .sidebar{transition-delay:.4s}#wpo-wcpdf-preview-wrapper .preview-document{padding:0;box-sizing:border-box;position:sticky;top:2.4em;flex:0 0 60%}#wpo-wcpdf-preview-wrapper[data-preview-states="1"] .preview-document{display:none}#wpo-wcpdf-preview-wrapper[data-preview-states="2"][data-preview-state=closed] .preview-document{display:none}#wpo-wcpdf-preview-wrapper[data-preview-states="2"][data-preview-state=full] .preview-document{flex:0 0 95%}#wpo-wcpdf-preview-wrapper[data-preview-states="3"][data-preview-state=closed] .preview-document{flex:0 0 60%;margin-right:-60%}#wpo-wcpdf-preview-wrapper[data-preview-states="3"][data-preview-state=sidebar] .preview-document{flex:0 0 60%}#wpo-wcpdf-preview-wrapper[data-preview-states="3"][data-preview-state=full] .preview-document{flex:0 0 95%}#wpo-wcpdf-preview-wrapper[data-preview-states="3"][data-from-preview-state=full] .preview-document{transition-delay:.4s}.preview-document .preview{width:100%;box-sizing:border-box;padding-right:5%}.preview-document .preview>#preview-canvas{display:block;max-width:800px;max-height:85vh;width:auto!important;margin:0 auto;background:#fff;box-shadow:0 0 35px -8px rgba(0,0,0,.12);-webkit-box-shadow:0 0 35px -8px rgba(0,0,0,.12)}#wpo-wcpdf-preview-wrapper[data-preview-states="2"] #preview-canvas{max-height:170vh}#wpo-wcpdf-preview-wrapper[data-preview-states="3"][data-from-preview-state=sidebar] #preview-canvas{max-height:170vh;transition:max-height .4s ease-in-out .3s}#wpo-wcpdf-preview-wrapper[data-preview-states="3"][data-from-preview-state=full] #preview-canvas{transition:max-height .4s ease-in-out 0s}.preview-document .preview-data-wrapper{width:100%;height:4em}.preview-document .preview-data-wrapper .preview-document-type,.preview-document .preview-data-wrapper .preview-order-data{float:right}.preview-document .preview-data-wrapper .preview-document-type{margin-right:30px}.preview-document .preview-data-wrapper .preview-document-type ul>li{text-decoration:none;color:initial;padding:1.4em 1.6em}.preview-document .preview-data-wrapper .preview-document-type ul>li:hover{color:#fff!important}.preview-document .preview-data-wrapper .save-settings{padding:1em 0 0 0;float:right;overflow:hidden;position:relative}.preview-document .preview-data-wrapper .save-settings p{padding:0;margin:0 0 0 2em;position:relative;margin-right:-200px;transition:margin-right .3s ease-out}.preview-document .preview-data-wrapper .save-settings p:after{content:'';display:block;pointer-events:none;position:absolute;box-sizing:border-box;border-radius:3px;right:0;top:0;background:0 0;width:100%;height:100%;z-index:10;border:0 solid #fff;animation:border-pulse 4s infinite}@keyframes border-pulse{0%{border-color:rgba(255,255,255,0);border-width:8px}50%{border-color:#fff;border-width:0}}.preview-document .preview-data-wrapper .save-settings p input:focus{outline-width:0;box-shadow:none}.preview-document .preview-data p{padding:1.4em 0;margin:0;color:#666;text-align:right;cursor:pointer;font-weight:lighter;float:right}.preview-document .preview-data p.order-search{display:none}.preview-document .preview-data input{float:right;margin:1em 0 0 1em;padding:.1em .5em;width:20ch;margin-right:-25ch;display:none}.preview-document .preview-data input.active{margin-right:0;display:inline-block}.preview-document .preview-data ul{position:absolute;right:0;top:4em;background:#fff;box-shadow:0 0 25px -10px rgba(0,0,0,.2);-webkit-box-shadow:0 0 25px -10px rgba(0,0,0,.2);list-style:none;margin:0;padding:0;min-width:24em;display:block;height:0;overflow:hidden}.preview-document .preview-data ul.active{height:auto;z-index:1}.preview-document .preview-data ul li{box-sizing:border-box;padding:0;margin-bottom:0;border-bottom:1px solid #eaeaea;font-size:1.1em}.preview-document .preview-data ul li:hover{cursor:pointer;background:#51266b;color:#fff}.preview-document .preview-data ul li a,.preview-document .preview-data.preview-order-data ul li{display:block;padding:1.4em 1.6em}.preview-document .preview-data .arrow-down{font-size:.8em;color:#999;margin-left:8px}.preview-document .preview-data p:hover,.preview-document .preview-data p:hover>.arrow-down{color:#222}.preview-document .preview-data #preview-order-search-results{display:none;position:absolute;right:0;top:4em;width:300px;box-shadow:0 0 25px -10px rgba(0,0,0,.2);-webkit-box-shadow:0 0 25px -10px rgba(0,0,0,.2);padding:20px 0;background-color:#fff}.preview-document .preview-data #preview-order-search-results a{display:block;border-left:1px solid #999;border-right:1px solid #999;border-top:1px solid #999;color:#000;padding:10px;margin:0 20px;text-decoration:none;cursor:pointer}.preview-document .preview-data #preview-order-search-results a:last-child{border-bottom:1px solid #999}.preview-document .preview-data #preview-order-search-results a:hover{background-color:#51266b;color:#fff}.preview-document .preview-data #preview-order-search-results .order-number{font-weight:700}.preview-document .preview-data #preview-order-search-results .date,.preview-document .preview-data #preview-order-search-results .total{margin-top:6px;display:inline-block}.preview-document .preview-data #preview-order-search-results .total{float:right}.preview-document .preview-data #preview-order-search-results .error{margin:0 20px}.preview-document .preview-order-search-wrapper{position:relative;float:right}.preview-document .preview-order-search-wrapper img.preview-order-search-clear{position:absolute;width:30px;height:16px;top:22px;right:6px;display:none;cursor:pointer}#wpo-wcpdf-preview-wrapper .gutter{flex:0 0 5%;position:sticky;top:2.4em;height:170vh}#wpo-wcpdf-preview-wrapper[data-preview-states="2"][data-preview-state=closed] .gutter .slide-left,#wpo-wcpdf-preview-wrapper[data-preview-states="3"][data-preview-state=closed] .gutter .slide-left{float:right}#wpo-wcpdf-preview-wrapper[data-preview-states="3"][data-preview-state=sidebar] .gutter .slide-left{border:none}#wpo-wcpdf-preview-wrapper .slider{box-sizing:border-box;padding-top:2.4em;color:#999;font-weight:700;cursor:pointer;font-size:.7em;line-height:1em;width:50%;height:100%;float:left}#wpo-wcpdf-preview-wrapper .slider.slide-left{text-align:right;padding-right:10px;border-right:1px solid #ccc}#wpo-wcpdf-preview-wrapper .slider.slide-right{text-align:left;padding-left:10px;border-left:1px solid #ccc;display:none}#wpo-wcpdf-preview-wrapper .gutter-arrow{width:0;height:0;border-top:3px solid transparent;border-bottom:3px solid transparent;display:block}#wpo-wcpdf-preview-wrapper .arrow-left{border-right:7px solid #999;float:right}#wpo-wcpdf-preview-wrapper .arrow-right{border-left:7px solid #999}#wpo-wcpdf-preview-wrapper .slider:hover>.arrow-left{border-right:7px solid #222}#wpo-wcpdf-preview-wrapper .slider:hover>.arrow-right{border-left:7px solid #222}#wpo-wcpdf-preview-wrapper .slider.slide-left:after{content:'Preview';position:absolute;top:1.55em;right:2em;font-size:1.4em;display:none}#wpo-wcpdf-preview-wrapper .slider.slide-right:after{content:'Settings';position:absolute;top:1.55em;left:2em;font-size:1.4em;display:none}#wpo-wcpdf-preview-wrapper[data-preview-states="1"] .gutter{display:none}#wpo-wcpdf-preview-wrapper[data-preview-states="3"][data-preview-state=sidebar] .gutter{height:100vh}#wpo-wcpdf-preview-wrapper[data-preview-state=full] .slide-right:after{display:inline-block}#wpo-wcpdf-preview-wrapper[data-preview-state=closed] .slide-left:after{display:inline-block}#wpo-wcpdf-preview-wrapper.static .gutter,#wpo-wcpdf-preview-wrapper.static .preview-document{position:static!important}#wpo-wcpdf-preview-wrapper.static .sidebar{height:170vh!important;overflow:hidden}#wpo-wcpdf-preview-wrapper input.readonly,#wpo-wcpdf-preview-wrapper input[readonly],#wpo-wcpdf-preview-wrapper textarea.readonly,#wpo-wcpdf-preview-wrapper textarea[readonly]{background-color:#f8f8f8}#wpo-wcpdf-preview-wrapper[data-preview-state=sidebar] .select2.select2-container{width:100%!important}@media screen and (min-width:1920px){.preview-document .preview>#preview-canvas{max-width:900px}}@media screen and (max-width:1200px){.preview-document .preview>#preview-canvas{max-width:680px}.nav-tab-wrapper a.nav-tab{padding:1em 2em;margin:0 .5em .5em 0;border:1px solid #ccc;box-sizing:border-box;height:4em}.nav-tab-wrapper a.nav-tab.nav-tab-active{border:3px solid #51266b}}@media screen and (max-width:960px){.preview-document .preview>#preview-canvas{width:80vw!important}#wpo-wcpdf-preview-wrapper .sidebar>form{max-width:100%}#wpo-wcpdf-preview-wrapper[data-preview-state=closed] .select2.select2-container{width:100%!important}#wpo-wcpdf-preview-wrapper .sidebar .form-table,#wpo-wcpdf-preview-wrapper .sidebar .form-table>tbody,#wpo-wcpdf-preview-wrapper .sidebar .form-table>tbody>tr,#wpo-wcpdf-preview-wrapper .sidebar .form-table>tbody>tr>td,#wpo-wcpdf-preview-wrapper .sidebar .form-table>tbody>tr>th,#wpo-wcpdf-preview-wrapper .sidebar .form-table>tr,#wpo-wcpdf-preview-wrapper .sidebar .form-table>tr>td,#wpo-wcpdf-preview-wrapper .sidebar .form-table>tr>th{display:block!important}.preview-document .preview-data-wrapper{height:6em}.preview-document .preview-data p{padding:2.2em 0}#wpo-wcpdf-preview-wrapper .slider.slide-left:after,#wpo-wcpdf-preview-wrapper .slider.slide-right:after{top:1.5em;padding:1em;background:#fff;border:1px solid #ccc}#wpo-wcpdf-preview-wrapper .slider.slide-right:after{left:0}#wpo-wcpdf-preview-wrapper .slider.slide-left:after{right:0}}
composer.json CHANGED
@@ -1,6 +1,6 @@
1
  {
2
  "require": {
3
- "dompdf/dompdf": "^1.2",
4
  "symfony/polyfill-mbstring": "1.20",
5
  "symfony/polyfill-iconv": "1.20.0"
6
  },
1
  {
2
  "require": {
3
+ "dompdf/dompdf": "^2.0",
4
  "symfony/polyfill-mbstring": "1.20",
5
  "symfony/polyfill-iconv": "1.20.0"
6
  },
composer.lock CHANGED
@@ -4,25 +4,26 @@
4
  "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
5
  "This file is @generated automatically"
6
  ],
7
- "content-hash": "8a62b914f789a38ee769610fd975c124",
8
  "packages": [
9
  {
10
  "name": "dompdf/dompdf",
11
- "version": "v1.2.2",
12
  "source": {
13
  "type": "git",
14
  "url": "https://github.com/dompdf/dompdf.git",
15
- "reference": "5031045d9640b38cfc14aac9667470df09c9e090"
16
  },
17
  "dist": {
18
  "type": "zip",
19
- "url": "https://api.github.com/repos/dompdf/dompdf/zipball/5031045d9640b38cfc14aac9667470df09c9e090",
20
- "reference": "5031045d9640b38cfc14aac9667470df09c9e090",
21
  "shasum": ""
22
  },
23
  "require": {
24
  "ext-dom": "*",
25
  "ext-mbstring": "*",
 
26
  "phenx/php-font-lib": "^0.5.4",
27
  "phenx/php-svg-lib": "^0.3.3 || ^0.4.0",
28
  "php": "^7.1 || ^8.0"
@@ -71,9 +72,78 @@
71
  "homepage": "https://github.com/dompdf/dompdf",
72
  "support": {
73
  "issues": "https://github.com/dompdf/dompdf/issues",
74
- "source": "https://github.com/dompdf/dompdf/tree/v1.2.2"
75
  },
76
- "time": "2022-04-27T13:50:54+00:00"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
77
  },
78
  {
79
  "name": "phenx/php-font-lib",
4
  "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
5
  "This file is @generated automatically"
6
  ],
7
+ "content-hash": "83022a180b511a4284de8fcf7b79a57a",
8
  "packages": [
9
  {
10
  "name": "dompdf/dompdf",
11
+ "version": "v2.0.0",
12
  "source": {
13
  "type": "git",
14
  "url": "https://github.com/dompdf/dompdf.git",
15
+ "reference": "79573d8b8a141ec8a17312515de8740eed014fa9"
16
  },
17
  "dist": {
18
  "type": "zip",
19
+ "url": "https://api.github.com/repos/dompdf/dompdf/zipball/79573d8b8a141ec8a17312515de8740eed014fa9",
20
+ "reference": "79573d8b8a141ec8a17312515de8740eed014fa9",
21
  "shasum": ""
22
  },
23
  "require": {
24
  "ext-dom": "*",
25
  "ext-mbstring": "*",
26
+ "masterminds/html5": "^2.0",
27
  "phenx/php-font-lib": "^0.5.4",
28
  "phenx/php-svg-lib": "^0.3.3 || ^0.4.0",
29
  "php": "^7.1 || ^8.0"
72
  "homepage": "https://github.com/dompdf/dompdf",
73
  "support": {
74
  "issues": "https://github.com/dompdf/dompdf/issues",
75
+ "source": "https://github.com/dompdf/dompdf/tree/v2.0.0"
76
  },
77
+ "time": "2022-06-21T21:14:57+00:00"
78
+ },
79
+ {
80
+ "name": "masterminds/html5",
81
+ "version": "2.7.5",
82
+ "source": {
83
+ "type": "git",
84
+ "url": "https://github.com/Masterminds/html5-php.git",
85
+ "reference": "f640ac1bdddff06ea333a920c95bbad8872429ab"
86
+ },
87
+ "dist": {
88
+ "type": "zip",
89
+ "url": "https://api.github.com/repos/Masterminds/html5-php/zipball/f640ac1bdddff06ea333a920c95bbad8872429ab",
90
+ "reference": "f640ac1bdddff06ea333a920c95bbad8872429ab",
91
+ "shasum": ""
92
+ },
93
+ "require": {
94
+ "ext-ctype": "*",
95
+ "ext-dom": "*",
96
+ "ext-libxml": "*",
97
+ "php": ">=5.3.0"
98
+ },
99
+ "require-dev": {
100
+ "phpunit/phpunit": "^4.8.35 || ^5.7.21 || ^6 || ^7"
101
+ },
102
+ "type": "library",
103
+ "extra": {
104
+ "branch-alias": {
105
+ "dev-master": "2.7-dev"
106
+ }
107
+ },
108
+ "autoload": {
109
+ "psr-4": {
110
+ "Masterminds\\": "src"
111
+ }
112
+ },
113
+ "notification-url": "https://packagist.org/downloads/",
114
+ "license": [
115
+ "MIT"
116
+ ],
117
+ "authors": [
118
+ {
119
+ "name": "Matt Butcher",
120
+ "email": "technosophos@gmail.com"
121
+ },
122
+ {
123
+ "name": "Matt Farina",
124
+ "email": "matt@mattfarina.com"
125
+ },
126
+ {
127
+ "name": "Asmir Mustafic",
128
+ "email": "goetas@gmail.com"
129
+ }
130
+ ],
131
+ "description": "An HTML5 parser and serializer.",
132
+ "homepage": "http://masterminds.github.io/html5-php",
133
+ "keywords": [
134
+ "HTML5",
135
+ "dom",
136
+ "html",
137
+ "parser",
138
+ "querypath",
139
+ "serializer",
140
+ "xml"
141
+ ],
142
+ "support": {
143
+ "issues": "https://github.com/Masterminds/html5-php/issues",
144
+ "source": "https://github.com/Masterminds/html5-php/tree/2.7.5"
145
+ },
146
+ "time": "2021-07-01T14:25:37+00:00"
147
  },
148
  {
149
  "name": "phenx/php-font-lib",
includes/class-wcpdf-admin.php CHANGED
@@ -18,7 +18,7 @@ class Admin {
18
  add_action( 'manage_shop_order_posts_custom_column', array( $this, 'invoice_number_column_data' ), 2 );
19
  add_action( 'add_meta_boxes_shop_order', array( $this, 'add_meta_boxes' ) );
20
  if ( defined( 'WC_VERSION' ) && version_compare( WC_VERSION, '3.3', '>=' ) ) {
21
- add_action( 'bulk_actions-edit-shop_order', array( $this, 'bulk_actions' ), 20 );
22
  } else {
23
  add_action( 'admin_footer', array( $this, 'bulk_actions_js' ) );
24
  }
18
  add_action( 'manage_shop_order_posts_custom_column', array( $this, 'invoice_number_column_data' ), 2 );
19
  add_action( 'add_meta_boxes_shop_order', array( $this, 'add_meta_boxes' ) );
20
  if ( defined( 'WC_VERSION' ) && version_compare( WC_VERSION, '3.3', '>=' ) ) {
21
+ add_filter( 'bulk_actions-edit-shop_order', array( $this, 'bulk_actions' ), 20 );
22
  } else {
23
  add_action( 'admin_footer', array( $this, 'bulk_actions_js' ) );
24
  }
includes/class-wcpdf-font-synchronizer.php CHANGED
@@ -17,7 +17,7 @@ class Font_Synchronizer {
17
  *
18
  * @var string
19
  */
20
- public $font_cache_filename = "dompdf_font_family_cache.php";
21
 
22
  /**
23
  * Vanilla instance of dompdf
@@ -48,7 +48,7 @@ class Font_Synchronizer {
48
  $plugin_fonts = $this->get_plugin_fonts();
49
  $dompdf_fonts = $this->get_dompdf_fonts();
50
  if ( $merge_with_local ) {
51
- $local_fonts = $this->get_local_fonts( $destination );
52
  } else {
53
  $local_fonts = array();
54
  }
@@ -72,11 +72,8 @@ class Font_Synchronizer {
72
 
73
  // normalize one last time
74
  $local_fonts = $this->normalize_font_paths( $local_fonts );
75
-
76
  // rebuild font cache file
77
- $fonts_export = var_export( $local_fonts, true );
78
- $fonts_export = str_replace( '\'' . untrailingslashit( $destination ) , '$fontDir . \'', $fonts_export );
79
- $cacheData = sprintf( "<?php return %s;%s?>", $fonts_export, PHP_EOL );
80
  // write file with merged cache data
81
  file_put_contents( $destination . $this->font_cache_filename, $cacheData );
82
  }
@@ -136,11 +133,17 @@ class Font_Synchronizer {
136
  */
137
  public function get_local_fonts( $path ) {
138
  // prepare variables used in the cache list
139
- $fontDir = $path;
140
- $rootDir = $this->dompdf->getOptions()->getRootDir();
141
- $cache_file = trailingslashit( $path ) . $this->font_cache_filename;
 
 
142
  if ( is_readable( $cache_file ) ) {
143
- $font_data = include $cache_file;
 
 
 
 
144
  } else {
145
  $font_data = array();
146
  }
17
  *
18
  * @var string
19
  */
20
+ public $font_cache_filename = "installed-fonts.json";
21
 
22
  /**
23
  * Vanilla instance of dompdf
48
  $plugin_fonts = $this->get_plugin_fonts();
49
  $dompdf_fonts = $this->get_dompdf_fonts();
50
  if ( $merge_with_local ) {
51
+ $local_fonts = $this->get_local_fonts( $destination );
52
  } else {
53
  $local_fonts = array();
54
  }
72
 
73
  // normalize one last time
74
  $local_fonts = $this->normalize_font_paths( $local_fonts );
 
75
  // rebuild font cache file
76
+ $cacheData = json_encode( $local_fonts, JSON_PRETTY_PRINT );
 
 
77
  // write file with merged cache data
78
  file_put_contents( $destination . $this->font_cache_filename, $cacheData );
79
  }
133
  */
134
  public function get_local_fonts( $path ) {
135
  // prepare variables used in the cache list
136
+ $fontDir = $path;
137
+ $rootDir = $this->dompdf->getOptions()->getRootDir();
138
+ $cache_file = trailingslashit( $path ) . $this->font_cache_filename;
139
+ $legacy_cache_file = trailingslashit( $path ) . 'dompdf_font_family_cache.php'; // Dompdf <2.0
140
+
141
  if ( is_readable( $cache_file ) ) {
142
+ $json_data = file_get_contents( $cache_file );
143
+ $font_data = json_decode( $json_data, true );
144
+ } elseif ( is_readable( $legacy_cache_file ) ) {
145
+ $font_data = include $legacy_cache_file;
146
+ @unlink( $legacy_cache_file );
147
  } else {
148
  $font_data = array();
149
  }
includes/class-wcpdf-frontend.php CHANGED
@@ -139,6 +139,11 @@ class Frontend {
139
  return;
140
  }
141
 
 
 
 
 
 
142
  // Link text
143
  if( ! empty( $values['link_text'] ) ) {
144
  $link_text = $values['link_text'];
139
  return;
140
  }
141
 
142
+ $invoice = wcpdf_get_invoice( $order );
143
+ if ( ! $invoice || ! $invoice->is_allowed() ) {
144
+ return;
145
+ }
146
+
147
  // Link text
148
  if( ! empty( $values['link_text'] ) ) {
149
  $link_text = $values['link_text'];
includes/class-wcpdf-install.php CHANGED
@@ -404,7 +404,16 @@ class Install {
404
  }
405
  }
406
  }
407
-
 
 
 
 
 
 
 
 
 
408
  }
409
 
410
  /**
404
  }
405
  }
406
  }
407
+
408
+ // 3.0.0-dev-1: remove saved option 'use_html5_parser'
409
+ if ( version_compare( $installed_version, '3.0.0-dev-1', '<' ) ) {
410
+ // removes 'HTML5 parser' setting value
411
+ $debug_settings = get_option( 'wpo_wcpdf_settings_debug', array() );
412
+ if ( ! empty( $debug_settings['use_html5_parser'] ) ) {
413
+ unset( $debug_settings['use_html5_parser'] );
414
+ update_option( 'wpo_wcpdf_settings_debug', $debug_settings );
415
+ }
416
+ }
417
  }
418
 
419
  /**
includes/class-wcpdf-pdf-maker.php CHANGED
@@ -34,16 +34,14 @@ class PDF_Maker {
34
 
35
  // set options
36
  $options = new Options( apply_filters( 'wpo_wcpdf_dompdf_options', array(
37
- 'tempDir' => WPO_WCPDF()->main->get_tmp_path('dompdf'),
38
- 'fontDir' => WPO_WCPDF()->main->get_tmp_path('fonts'),
39
- 'fontCache' => WPO_WCPDF()->main->get_tmp_path('fonts'),
40
- 'chroot' => $this->get_chroot_paths(),
41
- 'logOutputFile' => WPO_WCPDF()->main->get_tmp_path('dompdf') . "/log.htm",
42
- 'defaultFont' => 'dejavu sans',
43
- 'isRemoteEnabled' => true,
44
- // HTML5 parser requires iconv
45
- 'isHtml5ParserEnabled' => ( isset(WPO_WCPDF()->settings->debug_settings['use_html5_parser']) && extension_loaded('iconv') ) ? true : false,
46
- 'isFontSubsettingEnabled' => $this->settings['font_subsetting'],
47
  ) ) );
48
 
49
  // instantiate and use the dompdf class
34
 
35
  // set options
36
  $options = new Options( apply_filters( 'wpo_wcpdf_dompdf_options', array(
37
+ 'tempDir' => WPO_WCPDF()->main->get_tmp_path( 'dompdf' ),
38
+ 'fontDir' => WPO_WCPDF()->main->get_tmp_path( 'fonts' ),
39
+ 'fontCache' => WPO_WCPDF()->main->get_tmp_path( 'fonts' ),
40
+ 'chroot' => $this->get_chroot_paths(),
41
+ 'logOutputFile' => WPO_WCPDF()->main->get_tmp_path( 'dompdf' ) . "/log.htm",
42
+ 'defaultFont' => 'dejavu sans',
43
+ 'isRemoteEnabled' => true,
44
+ 'isFontSubsettingEnabled' => $this->settings['font_subsetting'],
 
 
45
  ) ) );
46
 
47
  // instantiate and use the dompdf class
includes/class-wcpdf-settings-callbacks.php CHANGED
@@ -35,6 +35,18 @@ class Settings_Callbacks {
35
  public function custom_fields_section() {
36
  echo wp_kses_post( __( 'These are used for the (optional) footer columns in the <em>Modern (Premium)</em> template, but can also be used for other elements in your custom template' , 'woocommerce-pdf-invoices-packing-slips' ) );
37
  }
 
 
 
 
 
 
 
 
 
 
 
 
38
 
39
  /**
40
  * Checkbox callback.
@@ -664,4 +676,4 @@ class Settings_Callbacks {
664
 
665
  endif; // class_exists
666
 
667
- return new Settings_Callbacks();
35
  public function custom_fields_section() {
36
  echo wp_kses_post( __( 'These are used for the (optional) footer columns in the <em>Modern (Premium)</em> template, but can also be used for other elements in your custom template' , 'woocommerce-pdf-invoices-packing-slips' ) );
37
  }
38
+
39
+ /**
40
+ * HTML section callback.
41
+ *
42
+ * @return void.
43
+ */
44
+ public function html_section( $args ) {
45
+ extract( $this->normalize_settings_args( $args ) );
46
+
47
+ // output HTML
48
+ echo wp_kses_post( $html );
49
+ }
50
 
51
  /**
52
  * Checkbox callback.
676
 
677
  endif; // class_exists
678
 
679
+ return new Settings_Callbacks();
includes/class-wcpdf-settings-debug.php CHANGED
@@ -258,17 +258,6 @@ class Settings_Debug {
258
  __( 'You can also add <code>&output=html</code> to the URL to apply this on a per-order basis.', 'woocommerce-pdf-invoices-packing-slips' ),
259
  )
260
  ),
261
- array(
262
- 'type' => 'setting',
263
- 'id' => 'use_html5_parser',
264
- 'title' => __( 'Use alternative HTML5 parser to parse HTML', 'woocommerce-pdf-invoices-packing-slips' ),
265
- 'callback' => 'checkbox',
266
- 'section' => 'debug_settings',
267
- 'args' => array(
268
- 'option_name' => $option_name,
269
- 'id' => 'use_html5_parser',
270
- )
271
- ),
272
  array(
273
  'type' => 'setting',
274
  'id' => 'log_to_order_notes',
258
  __( 'You can also add <code>&output=html</code> to the URL to apply this on a per-order basis.', 'woocommerce-pdf-invoices-packing-slips' ),
259
  )
260
  ),
 
 
 
 
 
 
 
 
 
 
 
261
  array(
262
  'type' => 'setting',
263
  'id' => 'log_to_order_notes',
includes/class-wcpdf-settings-general.php CHANGED
@@ -300,15 +300,14 @@ class Settings_General {
300
  */
301
  public function find_templates() {
302
  $installed_templates = array();
303
-
304
  // get base paths
305
- $template_base_path = ( function_exists( 'WC' ) && is_callable( array( 'WC', 'template_path' ) ) ) ? WC()->template_path() : 'woocommerce/';
306
- $template_base_path = untrailingslashit( $template_base_path );
307
- $template_paths = array (
308
  // note the order: child-theme before theme, so that array_unique filters out parent doubles
309
- 'default' => WPO_WCPDF()->plugin_path() . '/templates/',
310
- 'child-theme' => get_stylesheet_directory() . "/{$template_base_path}/pdf/",
311
- 'theme' => get_template_directory() . "/{$template_base_path}/pdf/",
312
  );
313
 
314
  $template_paths = apply_filters( 'wpo_wcpdf_template_paths', $template_paths );
@@ -319,10 +318,10 @@ class Settings_General {
319
  $forwardslash_basepath = str_replace( '\\', '/', WP_CONTENT_DIR );
320
  }
321
 
322
- foreach ($template_paths as $template_source => $template_path) {
323
  $dirs = (array) glob( $template_path . '*' , GLOB_ONLYDIR );
324
 
325
- foreach ($dirs as $dir) {
326
  // we're stripping abspath to make the plugin settings more portable
327
  $forwardslash_dir = str_replace( '\\', '/', $dir );
328
  $installed_templates[ str_replace( $forwardslash_basepath, '', $forwardslash_dir ) ] = basename($dir);
@@ -332,7 +331,7 @@ class Settings_General {
332
  // remove parent doubles
333
  $installed_templates = array_unique($installed_templates);
334
 
335
- if (empty($installed_templates)) {
336
  // fallback to Simple template for servers with glob() disabled
337
  $simple_template_path = str_replace( ABSPATH, '', $template_paths['default'] . 'Simple' );
338
  $installed_templates[$simple_template_path] = 'Simple';
300
  */
301
  public function find_templates() {
302
  $installed_templates = array();
 
303
  // get base paths
304
+ $template_base_path = ( function_exists( 'WC' ) && is_callable( array( WC(), 'template_path' ) ) ) ? WC()->template_path() : apply_filters( 'woocommerce_template_path', 'woocommerce/' );
305
+ $template_base_path = untrailingslashit( $template_base_path );
306
+ $template_paths = array (
307
  // note the order: child-theme before theme, so that array_unique filters out parent doubles
308
+ 'default' => WPO_WCPDF()->plugin_path() . '/templates/',
309
+ 'child-theme' => get_stylesheet_directory() . "/{$template_base_path}/pdf/",
310
+ 'theme' => get_template_directory() . "/{$template_base_path}/pdf/",
311
  );
312
 
313
  $template_paths = apply_filters( 'wpo_wcpdf_template_paths', $template_paths );
318
  $forwardslash_basepath = str_replace( '\\', '/', WP_CONTENT_DIR );
319
  }
320
 
321
+ foreach ( $template_paths as $template_source => $template_path ) {
322
  $dirs = (array) glob( $template_path . '*' , GLOB_ONLYDIR );
323
 
324
+ foreach ( $dirs as $dir ) {
325
  // we're stripping abspath to make the plugin settings more portable
326
  $forwardslash_dir = str_replace( '\\', '/', $dir );
327
  $installed_templates[ str_replace( $forwardslash_basepath, '', $forwardslash_dir ) ] = basename($dir);
331
  // remove parent doubles
332
  $installed_templates = array_unique($installed_templates);
333
 
334
+ if ( empty( $installed_templates ) ) {
335
  // fallback to Simple template for servers with glob() disabled
336
  $simple_template_path = str_replace( ABSPATH, '', $template_paths['default'] . 'Simple' );
337
  $installed_templates[$simple_template_path] = 'Simple';
includes/class-wcpdf-settings.php CHANGED
@@ -480,27 +480,26 @@ class Settings {
480
  }
481
 
482
  $installed_templates = array();
483
-
484
  // get base paths
485
- $template_base_path = ( function_exists( 'WC' ) && is_callable( array( 'WC', 'template_path' ) ) ) ? WC()->template_path() : 'woocommerce/';
486
- $template_base_path = untrailingslashit( $template_base_path );
487
- $template_paths = array (
488
  // note the order: theme before child-theme, so that child theme is always preferred (overwritten)
489
- 'default' => WPO_WCPDF()->plugin_path() . '/templates/',
490
- 'theme' => get_template_directory() . "/{$template_base_path}/pdf/",
491
- 'child-theme' => get_stylesheet_directory() . "/{$template_base_path}/pdf/",
492
  );
493
 
494
  $template_paths = apply_filters( 'wpo_wcpdf_template_paths', $template_paths );
495
 
496
- foreach ($template_paths as $template_source => $template_path) {
497
  $dirs = (array) glob( $template_path . '*' , GLOB_ONLYDIR );
498
 
499
  foreach ( $dirs as $dir ) {
500
- $clean_dir = $this->normalize_path( $dir );
501
  $template_name = basename( $clean_dir );
502
  // let child theme override parent theme
503
- $group = ( $template_source == 'child-theme' ) ? 'theme' : $template_source;
504
  $installed_templates[ $clean_dir ] = "{$group}/{$template_name}" ;
505
  }
506
  }
@@ -674,6 +673,39 @@ class Settings {
674
  return wp_send_json_success( $html );
675
  }
676
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
677
  }
678
 
679
  endif; // class_exists
480
  }
481
 
482
  $installed_templates = array();
 
483
  // get base paths
484
+ $template_base_path = ( function_exists( 'WC' ) && is_callable( array( WC(), 'template_path' ) ) ) ? WC()->template_path() : apply_filters( 'woocommerce_template_path', 'woocommerce/' );
485
+ $template_base_path = untrailingslashit( $template_base_path );
486
+ $template_paths = array (
487
  // note the order: theme before child-theme, so that child theme is always preferred (overwritten)
488
+ 'default' => WPO_WCPDF()->plugin_path() . '/templates/',
489
+ 'theme' => get_template_directory() . "/{$template_base_path}/pdf/",
490
+ 'child-theme' => get_stylesheet_directory() . "/{$template_base_path}/pdf/",
491
  );
492
 
493
  $template_paths = apply_filters( 'wpo_wcpdf_template_paths', $template_paths );
494
 
495
+ foreach ( $template_paths as $template_source => $template_path ) {
496
  $dirs = (array) glob( $template_path . '*' , GLOB_ONLYDIR );
497
 
498
  foreach ( $dirs as $dir ) {
499
+ $clean_dir = $this->normalize_path( $dir );
500
  $template_name = basename( $clean_dir );
501
  // let child theme override parent theme
502
+ $group = ( $template_source == 'child-theme' ) ? 'theme' : $template_source;
503
  $installed_templates[ $clean_dir ] = "{$group}/{$template_name}" ;
504
  }
505
  }
673
  return wp_send_json_success( $html );
674
  }
675
 
676
+ public function move_setting_after_id( $settings, $insert_settings, $after_setting_id ) {
677
+ $pos = 1; // this is already +1 to insert after the actual pos
678
+ foreach ( $settings as $setting ) {
679
+ if ( isset( $setting['id'] ) && $setting['id'] == $after_setting_id ) {
680
+ $section = $setting['section'];
681
+ break;
682
+ } else {
683
+ $pos++;
684
+ }
685
+ }
686
+
687
+ // replace section
688
+ if ( isset( $section ) ) {
689
+ foreach ( $insert_settings as $key => $insert_setting ) {
690
+ $insert_settings[$key]['section'] = $section;
691
+ }
692
+ } else {
693
+ $empty_section = array(
694
+ array(
695
+ 'type' => 'section',
696
+ 'id' => 'custom',
697
+ 'title' => '',
698
+ 'callback' => 'section',
699
+ ),
700
+ );
701
+ $insert_settings = array_merge( $empty_section, $insert_settings );
702
+ }
703
+ // insert our api settings
704
+ $new_settings = array_merge( array_slice( $settings, 0, $pos, true ), $insert_settings, array_slice( $settings, $pos, NULL, true ) );
705
+
706
+ return $new_settings;
707
+ }
708
+
709
  }
710
 
711
  endif; // class_exists
includes/documents/class-wcpdf-packing-slip.php CHANGED
@@ -146,7 +146,32 @@ class Packing_Slip extends Order_Document_Methods {
146
  )
147
  ),
148
  );
149
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
150
 
151
  // allow plugins to alter settings fields
152
  $settings_fields = apply_filters( 'wpo_wcpdf_settings_fields_documents_packing_slip', $settings_fields, $page, $option_group, $option_name );
@@ -175,4 +200,4 @@ class Packing_Slip extends Order_Document_Methods {
175
 
176
  endif; // class_exists
177
 
178
- return new Packing_Slip();
146
  )
147
  ),
148
  );
149
+
150
+ if ( ! function_exists( 'WPO_WCPDF_Pro' ) ) {
151
+ ob_start();
152
+ ?>
153
+ <div class="notice notice-info inline">
154
+ <p><a href="https://wpovernight.com/downloads/woocommerce-pdf-invoices-packing-slips-professional/" target="_blank"><?php _e( 'Upgrade to our Professional extension to attach packing slips to any email!', 'woocommerce-pdf-invoices-packing-slips' ); ?></a></p>
155
+ </div>
156
+ <?php
157
+ $html = ob_get_clean();
158
+
159
+ $pro_notice = array(
160
+ array(
161
+ 'type' => 'setting',
162
+ 'id' => 'attach_to_email_ids',
163
+ 'title' => __( 'Attach to:', 'woocommerce-pdf-invoices-packing-slips' ),
164
+ 'callback' => 'html_section',
165
+ 'section' => 'packing_slip',
166
+ 'args' => array(
167
+ 'option_name' => $option_name,
168
+ 'id' => 'attach_to_email_ids',
169
+ 'html' => $html,
170
+ )
171
+ ),
172
+ );
173
+ $settings_fields = WPO_WCPDF()->settings->move_setting_after_id( $settings_fields, $pro_notice, 'enabled' );
174
+ }
175
 
176
  // allow plugins to alter settings fields
177
  $settings_fields = apply_filters( 'wpo_wcpdf_settings_fields_documents_packing_slip', $settings_fields, $page, $option_group, $option_name );
200
 
201
  endif; // class_exists
202
 
203
+ return new Packing_Slip();
readme.txt CHANGED
@@ -5,7 +5,7 @@ Tags: woocommerce, pdf, invoices, packing slips, print, delivery notes, invoice,
5
  Requires at least: 3.5
6
  Tested up to: 6.0
7
  Requires PHP: 7.1
8
- Stable tag: 2.16.0
9
  License: GPLv2 or later
10
  License URI: http://www.gnu.org/licenses/gpl-2.0.html
11
 
@@ -102,6 +102,12 @@ There's a setting on the Status tab of the settings page that allows you to togg
102
 
103
  == Changelog ==
104
 
 
 
 
 
 
 
105
  = 2.16.0 =
106
  * Security: Fix authenticated reflected XSS on the settings page
107
  * Fix: Redirection URLs in wizard and when sending emails manually
5
  Requires at least: 3.5
6
  Tested up to: 6.0
7
  Requires PHP: 7.1
8
+ Stable tag: 3.0.0
9
  License: GPLv2 or later
10
  License URI: http://www.gnu.org/licenses/gpl-2.0.html
11
 
102
 
103
  == Changelog ==
104
 
105
+ = 3.0.0 =
106
+ * Libraries: Updated dompdf to 2.0.0, addressing security vulnerabilities and introducing some changes that could possibly break high level customized setups ([more information](https://github.com/dompdf/dompdf/releases/tag/v2.0.0))
107
+ * Fix: respect custom (filtered) woocommerce template paths
108
+ * Fix: Check if the invoice is allowed before the shortcode output
109
+ * UI: Link to Professional extension for packing slip attachments
110
+
111
  = 2.16.0 =
112
  * Security: Fix authenticated reflected XSS on the settings page
113
  * Fix: Redirection URLs in wizard and when sending emails manually
vendor/composer/autoload_classmap.php CHANGED
@@ -8,9 +8,4 @@ $baseDir = dirname($vendorDir);
8
  return array(
9
  'Composer\\InstalledVersions' => $vendorDir . '/composer/InstalledVersions.php',
10
  'Dompdf\\Cpdf' => $vendorDir . '/dompdf/dompdf/lib/Cpdf.php',
11
- 'HTML5_Data' => $vendorDir . '/dompdf/dompdf/lib/html5lib/Data.php',
12
- 'HTML5_InputStream' => $vendorDir . '/dompdf/dompdf/lib/html5lib/InputStream.php',
13
- 'HTML5_Parser' => $vendorDir . '/dompdf/dompdf/lib/html5lib/Parser.php',
14
- 'HTML5_Tokenizer' => $vendorDir . '/dompdf/dompdf/lib/html5lib/Tokenizer.php',
15
- 'HTML5_TreeBuilder' => $vendorDir . '/dompdf/dompdf/lib/html5lib/TreeBuilder.php',
16
  );
8
  return array(
9
  'Composer\\InstalledVersions' => $vendorDir . '/composer/InstalledVersions.php',
10
  'Dompdf\\Cpdf' => $vendorDir . '/dompdf/dompdf/lib/Cpdf.php',
 
 
 
 
 
11
  );
vendor/composer/autoload_psr4.php CHANGED
@@ -10,6 +10,7 @@ return array(
10
  'Symfony\\Polyfill\\Iconv\\' => array($vendorDir . '/symfony/polyfill-iconv'),
11
  'Svg\\' => array($vendorDir . '/phenx/php-svg-lib/src/Svg'),
12
  'Sabberworm\\CSS\\' => array($vendorDir . '/sabberworm/php-css-parser/src'),
 
13
  'FontLib\\' => array($vendorDir . '/phenx/php-font-lib/src/FontLib'),
14
  'Dompdf\\' => array($vendorDir . '/dompdf/dompdf/src'),
15
  );
10
  'Symfony\\Polyfill\\Iconv\\' => array($vendorDir . '/symfony/polyfill-iconv'),
11
  'Svg\\' => array($vendorDir . '/phenx/php-svg-lib/src/Svg'),
12
  'Sabberworm\\CSS\\' => array($vendorDir . '/sabberworm/php-css-parser/src'),
13
+ 'Masterminds\\' => array($vendorDir . '/masterminds/html5/src'),
14
  'FontLib\\' => array($vendorDir . '/phenx/php-font-lib/src/FontLib'),
15
  'Dompdf\\' => array($vendorDir . '/dompdf/dompdf/src'),
16
  );
vendor/composer/autoload_static.php CHANGED
@@ -19,6 +19,10 @@ class ComposerStaticInita44f2cb79329ce99e54227132018137a
19
  'Svg\\' => 4,
20
  'Sabberworm\\CSS\\' => 15,
21
  ),
 
 
 
 
22
  'F' =>
23
  array (
24
  'FontLib\\' => 8,
@@ -46,6 +50,10 @@ class ComposerStaticInita44f2cb79329ce99e54227132018137a
46
  array (
47
  0 => __DIR__ . '/..' . '/sabberworm/php-css-parser/src',
48
  ),
 
 
 
 
49
  'FontLib\\' =>
50
  array (
51
  0 => __DIR__ . '/..' . '/phenx/php-font-lib/src/FontLib',
@@ -59,11 +67,6 @@ class ComposerStaticInita44f2cb79329ce99e54227132018137a
59
  public static $classMap = array (
60
  'Composer\\InstalledVersions' => __DIR__ . '/..' . '/composer/InstalledVersions.php',
61
  'Dompdf\\Cpdf' => __DIR__ . '/..' . '/dompdf/dompdf/lib/Cpdf.php',
62
- 'HTML5_Data' => __DIR__ . '/..' . '/dompdf/dompdf/lib/html5lib/Data.php',
63
- 'HTML5_InputStream' => __DIR__ . '/..' . '/dompdf/dompdf/lib/html5lib/InputStream.php',
64
- 'HTML5_Parser' => __DIR__ . '/..' . '/dompdf/dompdf/lib/html5lib/Parser.php',
65
- 'HTML5_Tokenizer' => __DIR__ . '/..' . '/dompdf/dompdf/lib/html5lib/Tokenizer.php',
66
- 'HTML5_TreeBuilder' => __DIR__ . '/..' . '/dompdf/dompdf/lib/html5lib/TreeBuilder.php',
67
  );
68
 
69
  public static function getInitializer(ClassLoader $loader)
19
  'Svg\\' => 4,
20
  'Sabberworm\\CSS\\' => 15,
21
  ),
22
+ 'M' =>
23
+ array (
24
+ 'Masterminds\\' => 12,
25
+ ),
26
  'F' =>
27
  array (
28
  'FontLib\\' => 8,
50
  array (
51
  0 => __DIR__ . '/..' . '/sabberworm/php-css-parser/src',
52
  ),
53
+ 'Masterminds\\' =>
54
+ array (
55
+ 0 => __DIR__ . '/..' . '/masterminds/html5/src',
56
+ ),
57
  'FontLib\\' =>
58
  array (
59
  0 => __DIR__ . '/..' . '/phenx/php-font-lib/src/FontLib',
67
  public static $classMap = array (
68
  'Composer\\InstalledVersions' => __DIR__ . '/..' . '/composer/InstalledVersions.php',
69
  'Dompdf\\Cpdf' => __DIR__ . '/..' . '/dompdf/dompdf/lib/Cpdf.php',
 
 
 
 
 
70
  );
71
 
72
  public static function getInitializer(ClassLoader $loader)
vendor/composer/installed.json CHANGED
@@ -2,22 +2,23 @@
2
  "packages": [
3
  {
4
  "name": "dompdf/dompdf",
5
- "version": "v1.2.2",
6
- "version_normalized": "1.2.2.0",
7
  "source": {
8
  "type": "git",
9
  "url": "https://github.com/dompdf/dompdf.git",
10
- "reference": "5031045d9640b38cfc14aac9667470df09c9e090"
11
  },
12
  "dist": {
13
  "type": "zip",
14
- "url": "https://api.github.com/repos/dompdf/dompdf/zipball/5031045d9640b38cfc14aac9667470df09c9e090",
15
- "reference": "5031045d9640b38cfc14aac9667470df09c9e090",
16
  "shasum": ""
17
  },
18
  "require": {
19
  "ext-dom": "*",
20
  "ext-mbstring": "*",
 
21
  "phenx/php-font-lib": "^0.5.4",
22
  "phenx/php-svg-lib": "^0.3.3 || ^0.4.0",
23
  "php": "^7.1 || ^8.0"
@@ -35,7 +36,7 @@
35
  "ext-imagick": "Improves image processing performance",
36
  "ext-zlib": "Needed for pdf stream compression"
37
  },
38
- "time": "2022-04-27T13:50:54+00:00",
39
  "type": "library",
40
  "installation-source": "dist",
41
  "autoload": {
@@ -68,10 +69,82 @@
68
  "homepage": "https://github.com/dompdf/dompdf",
69
  "support": {
70
  "issues": "https://github.com/dompdf/dompdf/issues",
71
- "source": "https://github.com/dompdf/dompdf/tree/v1.2.2"
72
  },
73
  "install-path": "../dompdf/dompdf"
74
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
75
  {
76
  "name": "phenx/php-font-lib",
77
  "version": "0.5.4",
@@ -258,12 +331,12 @@
258
  },
259
  "installation-source": "dist",
260
  "autoload": {
261
- "psr-4": {
262
- "Symfony\\Polyfill\\Iconv\\": ""
263
- },
264
  "files": [
265
  "bootstrap.php"
266
- ]
 
 
 
267
  },
268
  "notification-url": "https://packagist.org/downloads/",
269
  "license": [
@@ -391,6 +464,6 @@
391
  "install-path": "../symfony/polyfill-mbstring"
392
  }
393
  ],
394
- "dev": false,
395
  "dev-package-names": []
396
  }
2
  "packages": [
3
  {
4
  "name": "dompdf/dompdf",
5
+ "version": "v2.0.0",
6
+ "version_normalized": "2.0.0.0",
7
  "source": {
8
  "type": "git",
9
  "url": "https://github.com/dompdf/dompdf.git",
10
+ "reference": "79573d8b8a141ec8a17312515de8740eed014fa9"
11
  },
12
  "dist": {
13
  "type": "zip",
14
+ "url": "https://api.github.com/repos/dompdf/dompdf/zipball/79573d8b8a141ec8a17312515de8740eed014fa9",
15
+ "reference": "79573d8b8a141ec8a17312515de8740eed014fa9",
16
  "shasum": ""
17
  },
18
  "require": {
19
  "ext-dom": "*",
20
  "ext-mbstring": "*",
21
+ "masterminds/html5": "^2.0",
22
  "phenx/php-font-lib": "^0.5.4",
23
  "phenx/php-svg-lib": "^0.3.3 || ^0.4.0",
24
  "php": "^7.1 || ^8.0"
36
  "ext-imagick": "Improves image processing performance",
37
  "ext-zlib": "Needed for pdf stream compression"
38
  },
39
+ "time": "2022-06-21T21:14:57+00:00",
40
  "type": "library",
41
  "installation-source": "dist",
42
  "autoload": {
69
  "homepage": "https://github.com/dompdf/dompdf",
70
  "support": {
71
  "issues": "https://github.com/dompdf/dompdf/issues",
72
+ "source": "https://github.com/dompdf/dompdf/tree/v2.0.0"
73
  },
74
  "install-path": "../dompdf/dompdf"
75
  },
76
+ {
77
+ "name": "masterminds/html5",
78
+ "version": "2.7.5",
79
+ "version_normalized": "2.7.5.0",
80
+ "source": {
81
+ "type": "git",
82
+ "url": "https://github.com/Masterminds/html5-php.git",
83
+ "reference": "f640ac1bdddff06ea333a920c95bbad8872429ab"
84
+ },
85
+ "dist": {
86
+ "type": "zip",
87
+ "url": "https://api.github.com/repos/Masterminds/html5-php/zipball/f640ac1bdddff06ea333a920c95bbad8872429ab",
88
+ "reference": "f640ac1bdddff06ea333a920c95bbad8872429ab",
89
+ "shasum": ""
90
+ },
91
+ "require": {
92
+ "ext-ctype": "*",
93
+ "ext-dom": "*",
94
+ "ext-libxml": "*",
95
+ "php": ">=5.3.0"
96
+ },
97
+ "require-dev": {
98
+ "phpunit/phpunit": "^4.8.35 || ^5.7.21 || ^6 || ^7"
99
+ },
100
+ "time": "2021-07-01T14:25:37+00:00",
101
+ "type": "library",
102
+ "extra": {
103
+ "branch-alias": {
104
+ "dev-master": "2.7-dev"
105
+ }
106
+ },
107
+ "installation-source": "dist",
108
+ "autoload": {
109
+ "psr-4": {
110
+ "Masterminds\\": "src"
111
+ }
112
+ },
113
+ "notification-url": "https://packagist.org/downloads/",
114
+ "license": [
115
+ "MIT"
116
+ ],
117
+ "authors": [
118
+ {
119
+ "name": "Matt Butcher",
120
+ "email": "technosophos@gmail.com"
121
+ },
122
+ {
123
+ "name": "Matt Farina",
124
+ "email": "matt@mattfarina.com"
125
+ },
126
+ {
127
+ "name": "Asmir Mustafic",
128
+ "email": "goetas@gmail.com"
129
+ }
130
+ ],
131
+ "description": "An HTML5 parser and serializer.",
132
+ "homepage": "http://masterminds.github.io/html5-php",
133
+ "keywords": [
134
+ "HTML5",
135
+ "dom",
136
+ "html",
137
+ "parser",
138
+ "querypath",
139
+ "serializer",
140
+ "xml"
141
+ ],
142
+ "support": {
143
+ "issues": "https://github.com/Masterminds/html5-php/issues",
144
+ "source": "https://github.com/Masterminds/html5-php/tree/2.7.5"
145
+ },
146
+ "install-path": "../masterminds/html5"
147
+ },
148
  {
149
  "name": "phenx/php-font-lib",
150
  "version": "0.5.4",
331
  },
332
  "installation-source": "dist",
333
  "autoload": {
 
 
 
334
  "files": [
335
  "bootstrap.php"
336
+ ],
337
+ "psr-4": {
338
+ "Symfony\\Polyfill\\Iconv\\": ""
339
+ }
340
  },
341
  "notification-url": "https://packagist.org/downloads/",
342
  "license": [
464
  "install-path": "../symfony/polyfill-mbstring"
465
  }
466
  ],
467
+ "dev": true,
468
  "dev-package-names": []
469
  }
vendor/composer/installed.php CHANGED
@@ -5,9 +5,9 @@
5
  'type' => 'library',
6
  'install_path' => __DIR__ . '/../../',
7
  'aliases' => array(),
8
- 'reference' => 'ba052659300ab6fd8e71ca3b71859586ead9607f',
9
  'name' => '__root__',
10
- 'dev' => false,
11
  ),
12
  'versions' => array(
13
  '__root__' => array(
@@ -16,16 +16,25 @@
16
  'type' => 'library',
17
  'install_path' => __DIR__ . '/../../',
18
  'aliases' => array(),
19
- 'reference' => 'ba052659300ab6fd8e71ca3b71859586ead9607f',
20
  'dev_requirement' => false,
21
  ),
22
  'dompdf/dompdf' => array(
23
- 'pretty_version' => 'v1.2.2',
24
- 'version' => '1.2.2.0',
25
  'type' => 'library',
26
  'install_path' => __DIR__ . '/../dompdf/dompdf',
27
  'aliases' => array(),
28
- 'reference' => '5031045d9640b38cfc14aac9667470df09c9e090',
 
 
 
 
 
 
 
 
 
29
  'dev_requirement' => false,
30
  ),
31
  'phenx/php-font-lib' => array(
5
  'type' => 'library',
6
  'install_path' => __DIR__ . '/../../',
7
  'aliases' => array(),
8
+ 'reference' => 'de3f055345804a78a51fa043ba01333e0a00fd50',
9
  'name' => '__root__',
10
+ 'dev' => true,
11
  ),
12
  'versions' => array(
13
  '__root__' => array(
16
  'type' => 'library',
17
  'install_path' => __DIR__ . '/../../',
18
  'aliases' => array(),
19
+ 'reference' => 'de3f055345804a78a51fa043ba01333e0a00fd50',
20
  'dev_requirement' => false,
21
  ),
22
  'dompdf/dompdf' => array(
23
+ 'pretty_version' => 'v2.0.0',
24
+ 'version' => '2.0.0.0',
25
  'type' => 'library',
26
  'install_path' => __DIR__ . '/../dompdf/dompdf',
27
  'aliases' => array(),
28
+ 'reference' => '79573d8b8a141ec8a17312515de8740eed014fa9',
29
+ 'dev_requirement' => false,
30
+ ),
31
+ 'masterminds/html5' => array(
32
+ 'pretty_version' => '2.7.5',
33
+ 'version' => '2.7.5.0',
34
+ 'type' => 'library',
35
+ 'install_path' => __DIR__ . '/../masterminds/html5',
36
+ 'aliases' => array(),
37
+ 'reference' => 'f640ac1bdddff06ea333a920c95bbad8872429ab',
38
  'dev_requirement' => false,
39
  ),
40
  'phenx/php-font-lib' => array(
vendor/dompdf/dompdf/README.md CHANGED
@@ -214,12 +214,6 @@ Files accessed through the local file system have the following requirement:
214
 
215
  ## Limitations (Known Issues)
216
 
217
- * Dompdf is not particularly tolerant to poorly-formed HTML input. To avoid
218
- any unexpected rendering issues you should either enable the built-in HTML5
219
- parser at runtime (`$options->setIsHtml5ParserEnabled(true);`)
220
- or run your HTML through a HTML validator/cleaner (such as
221
- [Tidy](http://tidy.sourceforge.net) or the
222
- [W3C Markup Validation Service](http://validator.w3.org)).
223
  * Table cells are not pageable, meaning a table row must fit on a single page.
224
  * Elements are rendered on the active page when they are parsed.
225
  * Embedding "raw" SVG's (`<svg><path...></svg>`) isn't working yet, you need to
214
 
215
  ## Limitations (Known Issues)
216
 
 
 
 
 
 
 
217
  * Table cells are not pageable, meaning a table row must fit on a single page.
218
  * Elements are rendered on the active page when they are parsed.
219
  * Embedding "raw" SVG's (`<svg><path...></svg>`) isn't working yet, you need to
vendor/dompdf/dompdf/VERSION CHANGED
@@ -1 +1 @@
1
- 1.2.2
1
+ 2.0.0
vendor/dompdf/dompdf/composer.json CHANGED
@@ -35,6 +35,7 @@
35
  "php": "^7.1 || ^8.0",
36
  "ext-dom": "*",
37
  "ext-mbstring": "*",
 
38
  "phenx/php-font-lib": "^0.5.4",
39
  "phenx/php-svg-lib": "^0.3.3 || ^0.4.0"
40
  },
35
  "php": "^7.1 || ^8.0",
36
  "ext-dom": "*",
37
  "ext-mbstring": "*",
38
+ "masterminds/html5": "^2.0",
39
  "phenx/php-font-lib": "^0.5.4",
40
  "phenx/php-svg-lib": "^0.3.3 || ^0.4.0"
41
  },
vendor/dompdf/dompdf/lib/Cpdf.php CHANGED
@@ -1048,7 +1048,7 @@ class Cpdf
1048
  }
1049
  }
1050
 
1051
- return 'SUB' . str_pad($base_26, 3 , 'A', STR_PAD_LEFT);
1052
  }
1053
 
1054
  /**
@@ -1381,7 +1381,7 @@ EOT;
1381
 
1382
  $res = "\n$id 0 obj\n";
1383
  $res .= "<</Length " . mb_strlen($stream, '8bit') . " >>\n";
1384
- $res .= "stream\n" . $stream . "\nendstream" . "\nendobj";;
1385
 
1386
  return $res;
1387
  }
@@ -2423,8 +2423,7 @@ EOT;
2423
  $res = "\n$id 0 obj\n<< /Type /XObject\n";
2424
 
2425
  foreach ($o["info"] as $k => $v) {
2426
- switch($k)
2427
- {
2428
  case 'Subtype':
2429
  $res .= "/Subtype /$v\n";
2430
  break;
@@ -2477,7 +2476,7 @@ EOT;
2477
  }
2478
 
2479
  $res .= "/Length " . mb_strlen($tmp, '8bit') . " >>\n";
2480
- $res .= "stream\n" . $tmp . "\nendstream" . "\nendobj";;
2481
 
2482
  return $res;
2483
  }
@@ -2515,7 +2514,7 @@ EOT;
2515
  $res = "\n$id 0 obj\n<<";
2516
 
2517
  foreach ($o["info"] as $k => $v) {
2518
- switch($k) {
2519
  case 'Fields':
2520
  $res .= " /Fields [";
2521
  foreach ($v as $i) {
@@ -2619,7 +2618,7 @@ EOT;
2619
  $res .= ">>\n";
2620
  break;
2621
  case 'T':
2622
- if($encrypted) {
2623
  $v = $this->filterText($this->ARC4($v), false, false);
2624
  }
2625
  $res .= "/T ($v)\n";
@@ -2662,7 +2661,7 @@ EOT;
2662
  $pos = strpos($content, sprintf("/ByteRange [ %'.010d", $id));
2663
  $len = strlen('/ByteRange [ ********** ********** ********** ********** ]');
2664
  $rangeStartPos = $pos + $len + 1 + 10; // before '<'
2665
- $content = substr_replace($content, str_pad(sprintf('/ByteRange [ 0 %u %u %u ]', $rangeStartPos, $rangeStartPos + $sign_maxlen + 2, $content_len - 2 - $sign_maxlen - $rangeStartPos ), $len, ' ', STR_PAD_RIGHT), $pos, $len);
2666
 
2667
  $fuid = uniqid();
2668
  $tmpInput = $this->tmp . "/pkcs7.tmp." . $fuid . '.in';
@@ -2726,7 +2725,7 @@ EOT;
2726
 
2727
  $o = &$this->objects[$id];
2728
  foreach ($o['info'] as $k => $v) {
2729
- switch($k) {
2730
  case 'Name':
2731
  case 'Location':
2732
  case 'Reason':
@@ -2852,7 +2851,7 @@ EOT;
2852
  case 'out':
2853
  $res = "\n$id 0 obj << ";
2854
 
2855
- foreach($this->objects[$id]['info'] as $referenceObjName => $referenceObjId) {
2856
  $res .= "/$referenceObjName $referenceObjId 0 R ";
2857
  }
2858
 
@@ -3310,11 +3309,11 @@ EOT;
3310
  {
3311
  // assume that $font contains the path and file but not the extension
3312
  $name = basename($font);
3313
- $dir = dirname($font) . '/';
3314
 
3315
  $fontcache = $this->fontcache;
3316
  if ($fontcache == '') {
3317
- $fontcache = rtrim($dir, DIRECTORY_SEPARATOR."/\\");
3318
  }
3319
 
3320
  //$name filename without folder and extension of font metrics
@@ -3331,36 +3330,22 @@ EOT;
3331
  $metrics_name = "$name.ufm";
3332
  }
3333
 
3334
- $cache_name = "$metrics_name.php";
3335
  $this->addMessage("metrics: $metrics_name, cache: $cache_name");
3336
-
3337
  if (file_exists($fontcache . '/' . $cache_name)) {
3338
- $this->addMessage("openFont: php file exists $fontcache/$cache_name");
3339
- $this->fonts[$font] = require($fontcache . '/' . $cache_name);
3340
-
3341
- if (!isset($this->fonts[$font]['_version_']) || $this->fonts[$font]['_version_'] != $this->fontcacheVersion) {
3342
- // if the font file is old, then clear it out and prepare for re-creation
3343
- $this->addMessage('openFont: clear out, make way for new version.');
3344
- $this->fonts[$font] = null;
3345
- unset($this->fonts[$font]);
3346
- }
3347
- } else {
3348
- $old_cache_name = "php_$metrics_name";
3349
- if (file_exists($fontcache . '/' . $old_cache_name)) {
3350
- $this->addMessage(
3351
- "openFont: php file doesn't exist $fontcache/$cache_name, creating it from the old format"
3352
- );
3353
- $old_cache = file_get_contents($fontcache . '/' . $old_cache_name);
3354
- file_put_contents($fontcache . '/' . $cache_name, '<?php return ' . $old_cache . ';');
3355
-
3356
- $this->openFont($font);
3357
- return;
3358
  }
3359
  }
3360
 
3361
- if (!isset($this->fonts[$font]) && file_exists($dir . $metrics_name)) {
3362
  // then rebuild the php_<font>.afm file from the <font>.afm file
3363
- $this->addMessage("openFont: build php file from $dir$metrics_name");
3364
  $data = [];
3365
 
3366
  // 20 => 'space'
@@ -3375,7 +3360,7 @@ EOT;
3375
  $cidtogid = str_pad('', 256 * 256 * 2, "\x00");
3376
  }
3377
 
3378
- $file = file($dir . $metrics_name);
3379
 
3380
  foreach ($file as $rowA) {
3381
  $row = trim($rowA);
@@ -3529,7 +3514,7 @@ EOT;
3529
  //Because of potential trouble with php safe mode, expect that the folder already exists.
3530
  //If not existing, this will hit performance because of missing cached results.
3531
  if (is_dir($fontcache) && is_writable($fontcache)) {
3532
- file_put_contents($fontcache . '/' . $cache_name, '<?php return ' . var_export($data, true) . ';');
3533
  }
3534
  $data = null;
3535
  }
@@ -3537,8 +3522,6 @@ EOT;
3537
  if (!isset($this->fonts[$font])) {
3538
  $this->addMessage("openFont: no font file found for $font. Do you need to run load_font.php?");
3539
  }
3540
-
3541
- //pre_r($this->messages);
3542
  }
3543
 
3544
  /**
@@ -3548,7 +3531,7 @@ EOT;
3548
  * note that encoding='none' will need to be used for symbolic fonts
3549
  * and 'differences' => an array of mappings between numbers 0->255 and character names.
3550
  *
3551
- * @param $fontName
3552
  * @param string $encoding
3553
  * @param bool $set
3554
  * @param bool $isSubsetting
@@ -3696,8 +3679,8 @@ EOT;
3696
  /**
3697
  * sets the color for fill operations
3698
  *
3699
- * @param $color
3700
- * @param bool $force
3701
  */
3702
  function setColor($color, $force = false)
3703
  {
@@ -3719,9 +3702,7 @@ EOT;
3719
  }
3720
 
3721
  /**
3722
- * sets the color for fill operations
3723
- *
3724
- * @param $fillRule
3725
  */
3726
  function setFillRule($fillRule)
3727
  {
@@ -3735,8 +3716,8 @@ EOT;
3735
  /**
3736
  * sets the color for stroke operations
3737
  *
3738
- * @param $color
3739
- * @param bool $force
3740
  */
3741
  function setStrokeColor($color, $force = false)
3742
  {
@@ -3887,11 +3868,11 @@ EOT;
3887
  /**
3888
  * draw a line from one set of coordinates to another
3889
  *
3890
- * @param $x1
3891
- * @param $y1
3892
- * @param $x2
3893
- * @param $y2
3894
- * @param bool $stroke
3895
  */
3896
  function line($x1, $y1, $x2, $y2, $stroke = true)
3897
  {
@@ -3905,14 +3886,14 @@ EOT;
3905
  /**
3906
  * draw a bezier curve based on 4 control points
3907
  *
3908
- * @param $x0
3909
- * @param $y0
3910
- * @param $x1
3911
- * @param $y1
3912
- * @param $x2
3913
- * @param $y2
3914
- * @param $x3
3915
- * @param $y3
3916
  */
3917
  function curve($x0, $y0, $x1, $y1, $x2, $y2, $x3, $y3)
3918
  {
@@ -3926,13 +3907,13 @@ EOT;
3926
  /**
3927
  * draw a part of an ellipse
3928
  *
3929
- * @param $x0
3930
- * @param $y0
3931
- * @param $astart
3932
- * @param $afinish
3933
- * @param $r1
3934
- * @param int $r2
3935
- * @param int $angle
3936
  * @param int $nSeg
3937
  */
3938
  function partEllipse($x0, $y0, $astart, $afinish, $r1, $r2 = 0, $angle = 0, $nSeg = 8)
@@ -3943,14 +3924,14 @@ EOT;
3943
  /**
3944
  * draw a filled ellipse
3945
  *
3946
- * @param $x0
3947
- * @param $y0
3948
- * @param $r1
3949
- * @param int $r2
3950
- * @param int $angle
3951
  * @param int $nSeg
3952
- * @param int $astart
3953
- * @param int $afinish
3954
  */
3955
  function filledEllipse($x0, $y0, $r1, $r2 = 0, $angle = 0, $nSeg = 8, $astart = 0, $afinish = 360)
3956
  {
@@ -3958,8 +3939,8 @@ EOT;
3958
  }
3959
 
3960
  /**
3961
- * @param $x
3962
- * @param $y
3963
  */
3964
  function lineTo($x, $y)
3965
  {
@@ -3967,8 +3948,8 @@ EOT;
3967
  }
3968
 
3969
  /**
3970
- * @param $x
3971
- * @param $y
3972
  */
3973
  function moveTo($x, $y)
3974
  {
@@ -3978,12 +3959,12 @@ EOT;
3978
  /**
3979
  * draw a bezier curve based on 4 control points
3980
  *
3981
- * @param $x1
3982
- * @param $y1
3983
- * @param $x2
3984
- * @param $y2
3985
- * @param $x3
3986
- * @param $y3
3987
  */
3988
  function curveTo($x1, $y1, $x2, $y2, $x3, $y3)
3989
  {
@@ -3992,6 +3973,11 @@ EOT;
3992
 
3993
  /**
3994
  * draw a bezier curve based on 4 control points
 
 
 
 
 
3995
  */
3996
  function quadTo($cpx, $cpy, $x, $y)
3997
  {
@@ -4019,18 +4005,18 @@ EOT;
4019
  * nSeg is not allowed to be less than 2, as this will simply draw a line (and will even draw a
4020
  * pretty crappy shape at 2, as we are approximating with bezier curves.
4021
  *
4022
- * @param $x0
4023
- * @param $y0
4024
- * @param $r1
4025
- * @param int $r2
4026
- * @param int $angle
4027
- * @param int $nSeg
4028
- * @param int $astart
4029
- * @param int $afinish
4030
- * @param bool $close
4031
- * @param bool $fill
4032
- * @param bool $stroke
4033
- * @param bool $incomplete
4034
  */
4035
  function ellipse(
4036
  $x0,
@@ -4144,11 +4130,11 @@ EOT;
4144
  * (2,1) is 2 on, 1 off, 2 on, 1 off.. etc
4145
  * phase is a modifier on the dash pattern which is used to shift the point at which the pattern starts.
4146
  *
4147
- * @param int $width
4148
  * @param string $cap
4149
  * @param string $join
4150
- * @param string $dash
4151
- * @param int $phase
4152
  */
4153
  function setLineStyle($width = 1, $cap = '', $join = '', $dash = '', $phase = 0)
4154
  {
@@ -4182,19 +4168,19 @@ EOT;
4182
  /**
4183
  * draw a polygon, the syntax for this is similar to the GD polygon command
4184
  *
4185
- * @param $p
4186
- * @param $np
4187
- * @param bool $f
4188
  */
4189
- function polygon($p, $np, $f = false)
4190
  {
4191
  $this->addContent(sprintf("\n%.3F %.3F m ", $p[0], $p[1]));
4192
 
4193
- for ($i = 2; $i < $np * 2; $i = $i + 2) {
 
4194
  $this->addContent(sprintf("%.3F %.3F l ", $p[$i], $p[$i + 1]));
4195
  }
4196
 
4197
- if ($f) {
4198
  $this->addContent(' f');
4199
  } else {
4200
  $this->addContent(' S');
@@ -4205,10 +4191,10 @@ EOT;
4205
  * a filled rectangle, note that it is the width and height of the rectangle which are the secondary parameters, not
4206
  * the coordinates of the upper-right corner
4207
  *
4208
- * @param $x1
4209
- * @param $y1
4210
- * @param $width
4211
- * @param $height
4212
  */
4213
  function filledRectangle($x1, $y1, $width, $height)
4214
  {
@@ -4219,10 +4205,10 @@ EOT;
4219
  * draw a rectangle, note that it is the width and height of the rectangle which are the secondary parameters, not
4220
  * the coordinates of the upper-right corner
4221
  *
4222
- * @param $x1
4223
- * @param $y1
4224
- * @param $width
4225
- * @param $height
4226
  */
4227
  function rectangle($x1, $y1, $width, $height)
4228
  {
@@ -4233,10 +4219,10 @@ EOT;
4233
  * draw a rectangle, note that it is the width and height of the rectangle which are the secondary parameters, not
4234
  * the coordinates of the upper-right corner
4235
  *
4236
- * @param $x1
4237
- * @param $y1
4238
- * @param $width
4239
- * @param $height
4240
  */
4241
  function rect($x1, $y1, $width, $height)
4242
  {
@@ -4348,8 +4334,10 @@ EOT;
4348
  $color = implode(' ', $color) . ' rg';
4349
 
4350
  $currentFontNum = $this->currentFontNum;
4351
- $font = array_filter($this->objects[$this->currentNode]['info']['fonts'],
4352
- function($item) use ($currentFontNum) { return $item['fontNum'] == $currentFontNum; });
 
 
4353
 
4354
  $this->o_acroform($this->acroFormId, 'font',
4355
  ['objNum' => $font[0]['objNum'], 'fontNum' => $font[0]['fontNum']]);
@@ -4464,10 +4452,10 @@ EOT;
4464
  /**
4465
  * draw a clipping rectangle, all the elements added after this will be clipped
4466
  *
4467
- * @param $x1
4468
- * @param $y1
4469
- * @param $width
4470
- * @param $height
4471
  */
4472
  function clippingRectangle($x1, $y1, $width, $height)
4473
  {
@@ -4478,14 +4466,14 @@ EOT;
4478
  /**
4479
  * draw a clipping rounded rectangle, all the elements added after this will be clipped
4480
  *
4481
- * @param $x1
4482
- * @param $y1
4483
- * @param $w
4484
- * @param $h
4485
- * @param $rTL
4486
- * @param $rTR
4487
- * @param $rBR
4488
- * @param $rBL
4489
  */
4490
  function clippingRectangleRounded($x1, $y1, $w, $h, $rTL, $rTR, $rBR, $rBL)
4491
  {
@@ -4525,6 +4513,25 @@ EOT;
4525
  $this->addContent(" W n");
4526
  }
4527
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
4528
  /**
4529
  * ends the last clipping shape
4530
  */
@@ -4741,10 +4748,11 @@ EOT;
4741
  /**
4742
  * return the height in units of the current font in the given size
4743
  *
4744
- * @param $size
4745
- * @return float|int
 
4746
  */
4747
- function getFontHeight($size)
4748
  {
4749
  if (!$this->numFonts) {
4750
  $this->selectFont($this->defaultFont);
@@ -4776,10 +4784,11 @@ EOT;
4776
  }
4777
 
4778
  /**
4779
- * @param $size
4780
- * @return float|int
 
4781
  */
4782
- function getFontXHeight($size)
4783
  {
4784
  if (!$this->numFonts) {
4785
  $this->selectFont($this->defaultFont);
@@ -4802,10 +4811,11 @@ EOT;
4802
  * if you add this number to the baseline, you get the level of the bottom of the font
4803
  * it is in the pdf user units
4804
  *
4805
- * @param $size
4806
- * @return float|int
 
4807
  */
4808
- function getFontDescender($size)
4809
  {
4810
  // note that this will most likely return a negative value
4811
  if (!$this->numFonts) {
@@ -4843,7 +4853,7 @@ EOT;
4843
  //$text = html_entity_decode($text, ENT_QUOTES);
4844
  $text = mb_convert_encoding($text, self::$targetEncoding, 'UTF-8');
4845
  }
4846
- } else if ($bom) {
4847
  $text = $this->utf8toUtf16BE($text, $bom);
4848
  }
4849
 
@@ -5014,8 +5024,8 @@ EOT;
5014
  /**
5015
  * register text for font subsetting
5016
  *
5017
- * @param $font
5018
- * @param $text
5019
  */
5020
  function registerText($font, $text)
5021
  {
@@ -5035,14 +5045,14 @@ EOT;
5035
  /**
5036
  * add text to the document, at a specified location, size and angle on the page
5037
  *
5038
- * @param $x
5039
- * @param $y
5040
- * @param $size
5041
- * @param $text
5042
- * @param int $angle
5043
- * @param int $wordSpaceAdjust
5044
- * @param int $charSpaceAdjust
5045
- * @param bool $smallCaps
5046
  */
5047
  function addText($x, $y, $size, $text, $angle = 0, $wordSpaceAdjust = 0, $charSpaceAdjust = 0, $smallCaps = false)
5048
  {
@@ -5155,13 +5165,14 @@ EOT;
5155
  * calculate how wide a given text string will be on a page, at a given size.
5156
  * this can be called externally, but is also used by the other class functions
5157
  *
5158
- * @param float $size
5159
  * @param string $text
5160
- * @param float $word_spacing
5161
- * @param float $char_spacing
 
5162
  * @return float
5163
  */
5164
- function getTextWidth($size, $text, $word_spacing = 0, $char_spacing = 0)
5165
  {
5166
  static $ord_cache = [];
5167
 
@@ -5176,9 +5187,6 @@ EOT;
5176
 
5177
  $text = str_replace(["\r", "\n"], "", $text);
5178
 
5179
- // converts a number or a float to a string so it can get the width
5180
- $text = "$text";
5181
-
5182
  // hmm, this is where it all starts to get tricky - use the font information to
5183
  // calculate the width of each character, add them up and convert to user units
5184
  $w = 0;
@@ -5205,14 +5213,14 @@ EOT;
5205
 
5206
  // add additional padding for space
5207
  if (isset($current_font['codeToName'][$char]) && $current_font['codeToName'][$char] === 'space') { // Space
5208
- $w += $word_spacing * $space_scale;
5209
  }
5210
  }
5211
  }
5212
 
5213
  // add additional char spacing
5214
- if ($char_spacing != 0) {
5215
- $w += $char_spacing * $space_scale * count($unicode);
5216
  }
5217
 
5218
  } else {
@@ -5240,14 +5248,14 @@ EOT;
5240
 
5241
  // add additional padding for space
5242
  if (isset($current_font['codeToName'][$char]) && $current_font['codeToName'][$char] === 'space') { // Space
5243
- $w += $word_spacing * $space_scale;
5244
  }
5245
  }
5246
  }
5247
 
5248
  // add additional char spacing
5249
- if ($char_spacing != 0) {
5250
- $w += $char_spacing * $space_scale * $len;
5251
  }
5252
  }
5253
 
@@ -5487,12 +5495,12 @@ EOT;
5487
  }
5488
 
5489
  /**
5490
- * add content to the documents info object
5491
  *
5492
- * @param $label
5493
- * @param int $value
5494
  */
5495
- function addInfo($label, $value = 0)
5496
  {
5497
  // this will only work if the label is one of the valid ones.
5498
  // modify this so that arrays can be passed as well.
@@ -5500,7 +5508,7 @@ EOT;
5500
  // else assume that they are both scalar, anything else will probably error
5501
  if (is_array($label)) {
5502
  foreach ($label as $l => $v) {
5503
- $this->o_info($this->infoObject, $l, $v);
5504
  }
5505
  } else {
5506
  $this->o_info($this->infoObject, $label, $value);
1048
  }
1049
  }
1050
 
1051
+ return 'SUB' . str_pad($base_26, 3, 'A', STR_PAD_LEFT);
1052
  }
1053
 
1054
  /**
1381
 
1382
  $res = "\n$id 0 obj\n";
1383
  $res .= "<</Length " . mb_strlen($stream, '8bit') . " >>\n";
1384
+ $res .= "stream\n" . $stream . "\nendstream" . "\nendobj";
1385
 
1386
  return $res;
1387
  }
2423
  $res = "\n$id 0 obj\n<< /Type /XObject\n";
2424
 
2425
  foreach ($o["info"] as $k => $v) {
2426
+ switch ($k) {
 
2427
  case 'Subtype':
2428
  $res .= "/Subtype /$v\n";
2429
  break;
2476
  }
2477
 
2478
  $res .= "/Length " . mb_strlen($tmp, '8bit') . " >>\n";
2479
+ $res .= "stream\n" . $tmp . "\nendstream" . "\nendobj";
2480
 
2481
  return $res;
2482
  }
2514
  $res = "\n$id 0 obj\n<<";
2515
 
2516
  foreach ($o["info"] as $k => $v) {
2517
+ switch ($k) {
2518
  case 'Fields':
2519
  $res .= " /Fields [";
2520
  foreach ($v as $i) {
2618
  $res .= ">>\n";
2619
  break;
2620
  case 'T':
2621
+ if ($encrypted) {
2622
  $v = $this->filterText($this->ARC4($v), false, false);
2623
  }
2624
  $res .= "/T ($v)\n";
2661
  $pos = strpos($content, sprintf("/ByteRange [ %'.010d", $id));
2662
  $len = strlen('/ByteRange [ ********** ********** ********** ********** ]');
2663
  $rangeStartPos = $pos + $len + 1 + 10; // before '<'
2664
+ $content = substr_replace($content, str_pad(sprintf('/ByteRange [ 0 %u %u %u ]', $rangeStartPos, $rangeStartPos + $sign_maxlen + 2, $content_len - 2 - $sign_maxlen - $rangeStartPos), $len, ' ', STR_PAD_RIGHT), $pos, $len);
2665
 
2666
  $fuid = uniqid();
2667
  $tmpInput = $this->tmp . "/pkcs7.tmp." . $fuid . '.in';
2725
 
2726
  $o = &$this->objects[$id];
2727
  foreach ($o['info'] as $k => $v) {
2728
+ switch ($k) {
2729
  case 'Name':
2730
  case 'Location':
2731
  case 'Reason':
2851
  case 'out':
2852
  $res = "\n$id 0 obj << ";
2853
 
2854
+ foreach ($this->objects[$id]['info'] as $referenceObjName => $referenceObjId) {
2855
  $res .= "/$referenceObjName $referenceObjId 0 R ";
2856
  }
2857
 
3309
  {
3310
  // assume that $font contains the path and file but not the extension
3311
  $name = basename($font);
3312
+ $dir = dirname($font);
3313
 
3314
  $fontcache = $this->fontcache;
3315
  if ($fontcache == '') {
3316
+ $fontcache = $dir;
3317
  }
3318
 
3319
  //$name filename without folder and extension of font metrics
3330
  $metrics_name = "$name.ufm";
3331
  }
3332
 
3333
+ $cache_name = "$metrics_name.json";
3334
  $this->addMessage("metrics: $metrics_name, cache: $cache_name");
3335
+
3336
  if (file_exists($fontcache . '/' . $cache_name)) {
3337
+ $this->addMessage("openFont: json metrics file exists $fontcache/$cache_name");
3338
+ $cached_font_info = json_decode(file_get_contents($fontcache . '/' . $cache_name), true);
3339
+ if (!isset($cached_font_info['_version_']) || $cached_font_info['_version_'] != $this->fontcacheVersion) {
3340
+ $this->addMessage('openFont: font cache is out of date, regenerating');
3341
+ } else {
3342
+ $this->fonts[$font] = $cached_font_info;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3343
  }
3344
  }
3345
 
3346
+ if (!isset($this->fonts[$font]) && file_exists("$dir/$metrics_name")) {
3347
  // then rebuild the php_<font>.afm file from the <font>.afm file
3348
+ $this->addMessage("openFont: build php file from $dir/$metrics_name");
3349
  $data = [];
3350
 
3351
  // 20 => 'space'
3360
  $cidtogid = str_pad('', 256 * 256 * 2, "\x00");
3361
  }
3362
 
3363
+ $file = file("$dir/$metrics_name");
3364
 
3365
  foreach ($file as $rowA) {
3366
  $row = trim($rowA);
3514
  //Because of potential trouble with php safe mode, expect that the folder already exists.
3515
  //If not existing, this will hit performance because of missing cached results.
3516
  if (is_dir($fontcache) && is_writable($fontcache)) {
3517
+ file_put_contents("$fontcache/$cache_name", json_encode($data, JSON_PRETTY_PRINT));
3518
  }
3519
  $data = null;
3520
  }
3522
  if (!isset($this->fonts[$font])) {
3523
  $this->addMessage("openFont: no font file found for $font. Do you need to run load_font.php?");
3524
  }
 
 
3525
  }
3526
 
3527
  /**
3531
  * note that encoding='none' will need to be used for symbolic fonts
3532
  * and 'differences' => an array of mappings between numbers 0->255 and character names.
3533
  *
3534
+ * @param string $fontName
3535
  * @param string $encoding
3536
  * @param bool $set
3537
  * @param bool $isSubsetting
3679
  /**
3680
  * sets the color for fill operations
3681
  *
3682
+ * @param array $color
3683
+ * @param bool $force
3684
  */
3685
  function setColor($color, $force = false)
3686
  {
3702
  }
3703
 
3704
  /**
3705
+ * @param string $fillRule
 
 
3706
  */
3707
  function setFillRule($fillRule)
3708
  {
3716
  /**
3717
  * sets the color for stroke operations
3718
  *
3719
+ * @param array $color
3720
+ * @param bool $force
3721
  */
3722
  function setStrokeColor($color, $force = false)
3723
  {
3868
  /**
3869
  * draw a line from one set of coordinates to another
3870
  *
3871
+ * @param float $x1
3872
+ * @param float $y1
3873
+ * @param float $x2
3874
+ * @param float $y2
3875
+ * @param bool $stroke
3876
  */
3877
  function line($x1, $y1, $x2, $y2, $stroke = true)
3878
  {
3886
  /**
3887
  * draw a bezier curve based on 4 control points
3888
  *
3889
+ * @param float $x0
3890
+ * @param float $y0
3891
+ * @param float $x1
3892
+ * @param float $y1
3893
+ * @param float $x2
3894
+ * @param float $y2
3895
+ * @param float $x3
3896
+ * @param float $y3
3897
  */
3898
  function curve($x0, $y0, $x1, $y1, $x2, $y2, $x3, $y3)
3899
  {
3907
  /**
3908
  * draw a part of an ellipse
3909
  *
3910
+ * @param float $x0
3911
+ * @param float $y0
3912
+ * @param float $astart
3913
+ * @param float $afinish
3914
+ * @param float $r1
3915
+ * @param float $r2
3916
+ * @param float $angle
3917
  * @param int $nSeg
3918
  */
3919
  function partEllipse($x0, $y0, $astart, $afinish, $r1, $r2 = 0, $angle = 0, $nSeg = 8)
3924
  /**
3925
  * draw a filled ellipse
3926
  *
3927
+ * @param float $x0
3928
+ * @param float $y0
3929
+ * @param float $r1
3930
+ * @param float $r2
3931
+ * @param float $angle
3932
  * @param int $nSeg
3933
+ * @param float $astart
3934
+ * @param float $afinish
3935
  */
3936
  function filledEllipse($x0, $y0, $r1, $r2 = 0, $angle = 0, $nSeg = 8, $astart = 0, $afinish = 360)
3937
  {
3939
  }
3940
 
3941
  /**
3942
+ * @param float $x
3943
+ * @param float $y
3944
  */
3945
  function lineTo($x, $y)
3946
  {
3948
  }
3949
 
3950
  /**
3951
+ * @param float $x
3952
+ * @param float $y
3953
  */
3954
  function moveTo($x, $y)
3955
  {
3959
  /**
3960
  * draw a bezier curve based on 4 control points
3961
  *
3962
+ * @param float $x1
3963
+ * @param float $y1
3964
+ * @param float $x2
3965
+ * @param float $y2
3966
+ * @param float $x3
3967
+ * @param float $y3
3968
  */
3969
  function curveTo($x1, $y1, $x2, $y2, $x3, $y3)
3970
  {
3973
 
3974
  /**
3975
  * draw a bezier curve based on 4 control points
3976
+ *
3977
+ * @param float $cpx
3978
+ * @param float $cpy
3979
+ * @param float $x
3980
+ * @param float $y
3981
  */
3982
  function quadTo($cpx, $cpy, $x, $y)
3983
  {
4005
  * nSeg is not allowed to be less than 2, as this will simply draw a line (and will even draw a
4006
  * pretty crappy shape at 2, as we are approximating with bezier curves.
4007
  *
4008
+ * @param float $x0
4009
+ * @param float $y0
4010
+ * @param float $r1
4011
+ * @param float $r2
4012
+ * @param float $angle
4013
+ * @param int $nSeg
4014
+ * @param float $astart
4015
+ * @param float $afinish
4016
+ * @param bool $close
4017
+ * @param bool $fill
4018
+ * @param bool $stroke
4019
+ * @param bool $incomplete
4020
  */
4021
  function ellipse(
4022
  $x0,
4130
  * (2,1) is 2 on, 1 off, 2 on, 1 off.. etc
4131
  * phase is a modifier on the dash pattern which is used to shift the point at which the pattern starts.
4132
  *
4133
+ * @param float $width
4134
  * @param string $cap
4135
  * @param string $join
4136
+ * @param array $dash
4137
+ * @param int $phase
4138
  */
4139
  function setLineStyle($width = 1, $cap = '', $join = '', $dash = '', $phase = 0)
4140
  {
4168
  /**
4169
  * draw a polygon, the syntax for this is similar to the GD polygon command
4170
  *
4171
+ * @param float[] $p
4172
+ * @param bool $fill
 
4173
  */
4174
+ public function polygon(array $p, bool $fill = false): void
4175
  {
4176
  $this->addContent(sprintf("\n%.3F %.3F m ", $p[0], $p[1]));
4177
 
4178
+ $n = count($p);
4179
+ for ($i = 2; $i < $n; $i = $i + 2) {
4180
  $this->addContent(sprintf("%.3F %.3F l ", $p[$i], $p[$i + 1]));
4181
  }
4182
 
4183
+ if ($fill) {
4184
  $this->addContent(' f');
4185
  } else {
4186
  $this->addContent(' S');
4191
  * a filled rectangle, note that it is the width and height of the rectangle which are the secondary parameters, not
4192
  * the coordinates of the upper-right corner
4193
  *
4194
+ * @param float $x1
4195
+ * @param float $y1
4196
+ * @param float $width
4197
+ * @param float $height
4198
  */
4199
  function filledRectangle($x1, $y1, $width, $height)
4200
  {
4205
  * draw a rectangle, note that it is the width and height of the rectangle which are the secondary parameters, not
4206
  * the coordinates of the upper-right corner
4207
  *
4208
+ * @param float $x1
4209
+ * @param float $y1
4210
+ * @param float $width
4211
+ * @param float $height
4212
  */
4213
  function rectangle($x1, $y1, $width, $height)
4214
  {
4219
  * draw a rectangle, note that it is the width and height of the rectangle which are the secondary parameters, not
4220
  * the coordinates of the upper-right corner
4221
  *
4222
+ * @param float $x1
4223
+ * @param float $y1
4224
+ * @param float $width
4225
+ * @param float $height
4226
  */
4227
  function rect($x1, $y1, $width, $height)
4228
  {
4334
  $color = implode(' ', $color) . ' rg';
4335
 
4336
  $currentFontNum = $this->currentFontNum;
4337
+ $font = array_filter(
4338
+ $this->objects[$this->currentNode]['info']['fonts'],
4339
+ function ($item) use ($currentFontNum) { return $item['fontNum'] == $currentFontNum; }
4340
+ );
4341
 
4342
  $this->o_acroform($this->acroFormId, 'font',
4343
  ['objNum' => $font[0]['objNum'], 'fontNum' => $font[0]['fontNum']]);
4452
  /**
4453
  * draw a clipping rectangle, all the elements added after this will be clipped
4454
  *
4455
+ * @param float $x1
4456
+ * @param float $y1
4457
+ * @param float $width
4458
+ * @param float $height
4459
  */
4460
  function clippingRectangle($x1, $y1, $width, $height)
4461
  {
4466
  /**
4467
  * draw a clipping rounded rectangle, all the elements added after this will be clipped
4468
  *
4469
+ * @param float $x1
4470
+ * @param float $y1
4471
+ * @param float $w
4472
+ * @param float $h
4473
+ * @param float $rTL
4474
+ * @param float $rTR
4475
+ * @param float $rBR
4476
+ * @param float $rBL
4477
  */
4478
  function clippingRectangleRounded($x1, $y1, $w, $h, $rTL, $rTR, $rBR, $rBL)
4479
  {
4513
  $this->addContent(" W n");
4514
  }
4515
 
4516
+ /**
4517
+ * draw a clipping polygon, the syntax for this is similar to the GD polygon command
4518
+ *
4519
+ * @param float[] $p
4520
+ */
4521
+ public function clippingPolygon(array $p): void
4522
+ {
4523
+ $this->save();
4524
+
4525
+ $this->addContent(sprintf("\n%.3F %.3F m ", $p[0], $p[1]));
4526
+
4527
+ $n = count($p);
4528
+ for ($i = 2; $i < $n; $i = $i + 2) {
4529
+ $this->addContent(sprintf("%.3F %.3F l ", $p[$i], $p[$i + 1]));
4530
+ }
4531
+
4532
+ $this->addContent("W n");
4533
+ }
4534
+
4535
  /**
4536
  * ends the last clipping shape
4537
  */
4748
  /**
4749
  * return the height in units of the current font in the given size
4750
  *
4751
+ * @param float $size
4752
+ *
4753
+ * @return float
4754
  */
4755
+ public function getFontHeight(float $size): float
4756
  {
4757
  if (!$this->numFonts) {
4758
  $this->selectFont($this->defaultFont);
4784
  }
4785
 
4786
  /**
4787
+ * @param float $size
4788
+ *
4789
+ * @return float
4790
  */
4791
+ public function getFontXHeight(float $size): float
4792
  {
4793
  if (!$this->numFonts) {
4794
  $this->selectFont($this->defaultFont);
4811
  * if you add this number to the baseline, you get the level of the bottom of the font
4812
  * it is in the pdf user units
4813
  *
4814
+ * @param float $size
4815
+ *
4816
+ * @return float
4817
  */
4818
+ public function getFontDescender(float $size): float
4819
  {
4820
  // note that this will most likely return a negative value
4821
  if (!$this->numFonts) {
4853
  //$text = html_entity_decode($text, ENT_QUOTES);
4854
  $text = mb_convert_encoding($text, self::$targetEncoding, 'UTF-8');
4855
  }
4856
+ } elseif ($bom) {
4857
  $text = $this->utf8toUtf16BE($text, $bom);
4858
  }
4859
 
5024
  /**
5025
  * register text for font subsetting
5026
  *
5027
+ * @param string $font
5028
+ * @param string $text
5029
  */
5030
  function registerText($font, $text)
5031
  {
5045
  /**
5046
  * add text to the document, at a specified location, size and angle on the page
5047
  *
5048
+ * @param float $x
5049
+ * @param float $y
5050
+ * @param float $size
5051
+ * @param string $text
5052
+ * @param float $angle
5053
+ * @param float $wordSpaceAdjust
5054
+ * @param float $charSpaceAdjust
5055
+ * @param bool $smallCaps
5056
  */
5057
  function addText($x, $y, $size, $text, $angle = 0, $wordSpaceAdjust = 0, $charSpaceAdjust = 0, $smallCaps = false)
5058
  {
5165
  * calculate how wide a given text string will be on a page, at a given size.
5166
  * this can be called externally, but is also used by the other class functions
5167
  *
5168
+ * @param float $size
5169
  * @param string $text
5170
+ * @param float $wordSpacing
5171
+ * @param float $charSpacing
5172
+ *
5173
  * @return float
5174
  */
5175
+ public function getTextWidth(float $size, string $text, float $wordSpacing = 0.0, float $charSpacing = 0.0): float
5176
  {
5177
  static $ord_cache = [];
5178
 
5187
 
5188
  $text = str_replace(["\r", "\n"], "", $text);
5189
 
 
 
 
5190
  // hmm, this is where it all starts to get tricky - use the font information to
5191
  // calculate the width of each character, add them up and convert to user units
5192
  $w = 0;
5213
 
5214
  // add additional padding for space
5215
  if (isset($current_font['codeToName'][$char]) && $current_font['codeToName'][$char] === 'space') { // Space
5216
+ $w += $wordSpacing * $space_scale;
5217
  }
5218
  }
5219
  }
5220
 
5221
  // add additional char spacing
5222
+ if ($charSpacing != 0) {
5223
+ $w += $charSpacing * $space_scale * count($unicode);
5224
  }
5225
 
5226
  } else {
5248
 
5249
  // add additional padding for space
5250
  if (isset($current_font['codeToName'][$char]) && $current_font['codeToName'][$char] === 'space') { // Space
5251
+ $w += $wordSpacing * $space_scale;
5252
  }
5253
  }
5254
  }
5255
 
5256
  // add additional char spacing
5257
+ if ($charSpacing != 0) {
5258
+ $w += $charSpacing * $space_scale * $len;
5259
  }
5260
  }
5261
 
5495
  }
5496
 
5497
  /**
5498
+ * Add content to the documents info object
5499
  *
5500
+ * @param string|array $label
5501
+ * @param string $value
5502
  */
5503
+ public function addInfo($label, string $value = ""): void
5504
  {
5505
  // this will only work if the label is one of the valid ones.
5506
  // modify this so that arrays can be passed as well.
5508
  // else assume that they are both scalar, anything else will probably error
5509
  if (is_array($label)) {
5510
  foreach ($label as $l => $v) {
5511
+ $this->o_info($this->infoObject, $l, (string) $v);
5512
  }
5513
  } else {
5514
  $this->o_info($this->infoObject, $label, $value);
vendor/dompdf/dompdf/lib/fonts/dompdf_font_family_cache.dist.php DELETED
@@ -1,97 +0,0 @@
1
- <?php
2
- return function ($rootDir) {
3
- $distFontDir = $rootDir . '/lib/fonts';
4
- return [
5
- 'sans-serif' =>
6
- [
7
- 'normal' => $distFontDir . '/Helvetica',
8
- 'bold' => $distFontDir . '/Helvetica-Bold',
9
- 'italic' => $distFontDir . '/Helvetica-Oblique',
10
- 'bold_italic' => $distFontDir . '/Helvetica-BoldOblique'
11
- ],
12
- 'times' =>
13
- [
14
- 'normal' => $distFontDir . '/Times-Roman',
15
- 'bold' => $distFontDir . '/Times-Bold',
16
- 'italic' => $distFontDir . '/Times-Italic',
17
- 'bold_italic' => $distFontDir . '/Times-BoldItalic'
18
- ],
19
- 'times-roman' =>
20
- [
21
- 'normal' => $distFontDir . '/Times-Roman',
22
- 'bold' => $distFontDir . '/Times-Bold',
23
- 'italic' => $distFontDir . '/Times-Italic',
24
- 'bold_italic' => $distFontDir . '/Times-BoldItalic'
25
- ],
26
- 'courier' =>
27
- [
28
- 'normal' => $distFontDir . '/Courier',
29
- 'bold' => $distFontDir . '/Courier-Bold',
30
- 'italic' => $distFontDir . '/Courier-Oblique',
31
- 'bold_italic' => $distFontDir . '/Courier-BoldOblique'
32
- ],
33
- 'helvetica' =>
34
- [
35
- 'normal' => $distFontDir . '/Helvetica',
36
- 'bold' => $distFontDir . '/Helvetica-Bold',
37
- 'italic' => $distFontDir . '/Helvetica-Oblique',
38
- 'bold_italic' => $distFontDir . '/Helvetica-BoldOblique'
39
- ],
40
- 'zapfdingbats' =>
41
- [
42
- 'normal' => $distFontDir . '/ZapfDingbats',
43
- 'bold' => $distFontDir . '/ZapfDingbats',
44
- 'italic' => $distFontDir . '/ZapfDingbats',
45
- 'bold_italic' => $distFontDir . '/ZapfDingbats'
46
- ],
47
- 'symbol' =>
48
- [
49
- 'normal' => $distFontDir . '/Symbol',
50
- 'bold' => $distFontDir . '/Symbol',
51
- 'italic' => $distFontDir . '/Symbol',
52
- 'bold_italic' => $distFontDir . '/Symbol'
53
- ],
54
- 'serif' =>
55
- [
56
- 'normal' => $distFontDir . '/Times-Roman',
57
- 'bold' => $distFontDir . '/Times-Bold',
58
- 'italic' => $distFontDir . '/Times-Italic',
59
- 'bold_italic' => $distFontDir . '/Times-BoldItalic'
60
- ],
61
- 'monospace' =>
62
- [
63
- 'normal' => $distFontDir . '/Courier',
64
- 'bold' => $distFontDir . '/Courier-Bold',
65
- 'italic' => $distFontDir . '/Courier-Oblique',
66
- 'bold_italic' => $distFontDir . '/Courier-BoldOblique'
67
- ],
68
- 'fixed' =>
69
- [
70
- 'normal' => $distFontDir . '/Courier',
71
- 'bold' => $distFontDir . '/Courier-Bold',
72
- 'italic' => $distFontDir . '/Courier-Oblique',
73
- 'bold_italic' => $distFontDir . '/Courier-BoldOblique'
74
- ],
75
- 'dejavu sans' =>
76
- [
77
- 'bold' => $distFontDir . '/DejaVuSans-Bold',
78
- 'bold_italic' => $distFontDir . '/DejaVuSans-BoldOblique',
79
- 'italic' => $distFontDir . '/DejaVuSans-Oblique',
80
- 'normal' => $distFontDir . '/DejaVuSans'
81
- ],
82
- 'dejavu sans mono' =>
83
- [
84
- 'bold' => $distFontDir . '/DejaVuSansMono-Bold',
85
- 'bold_italic' => $distFontDir . '/DejaVuSansMono-BoldOblique',
86
- 'italic' => $distFontDir . '/DejaVuSansMono-Oblique',
87
- 'normal' => $distFontDir . '/DejaVuSansMono'
88
- ],
89
- 'dejavu serif' =>
90
- [
91
- 'bold' => $distFontDir . '/DejaVuSerif-Bold',
92
- 'bold_italic' => $distFontDir . '/DejaVuSerif-BoldItalic',
93
- 'italic' => $distFontDir . '/DejaVuSerif-Italic',
94
- 'normal' => $distFontDir . '/DejaVuSerif'
95
- ]
96
- ];
97
- };
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
vendor/dompdf/dompdf/lib/fonts/installed-fonts.dist.json ADDED
@@ -0,0 +1,80 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "sans-serif": {
3
+ "normal": "Helvetica",
4
+ "bold": "Helvetica-Bold",
5
+ "italic": "Helvetica-Oblique",
6
+ "bold_italic": "Helvetica-BoldOblique"
7
+ },
8
+ "times": {
9
+ "normal": "Times-Roman",
10
+ "bold": "Times-Bold",
11
+ "italic": "Times-Italic",
12
+ "bold_italic": "Times-BoldItalic"
13
+ },
14
+ "times-roman": {
15
+ "normal": "Times-Roman",
16
+ "bold": "Times-Bold",
17
+ "italic": "Times-Italic",
18
+ "bold_italic": "Times-BoldItalic"
19
+ },
20
+ "courier": {
21
+ "normal": "Courier",
22
+ "bold": "Courier-Bold",
23
+ "italic": "Courier-Oblique",
24
+ "bold_italic": "Courier-BoldOblique"
25
+ },
26
+ "helvetica": {
27
+ "normal": "Helvetica",
28
+ "bold": "Helvetica-Bold",
29
+ "italic": "Helvetica-Oblique",
30
+ "bold_italic": "Helvetica-BoldOblique"
31
+ },
32
+ "zapfdingbats": {
33
+ "normal": "ZapfDingbats",
34
+ "bold": "ZapfDingbats",
35
+ "italic": "ZapfDingbats",
36
+ "bold_italic": "ZapfDingbats"
37
+ },
38
+ "symbol": {
39
+ "normal": "Symbol",
40
+ "bold": "Symbol",
41
+ "italic": "Symbol",
42
+ "bold_italic": "Symbol"
43
+ },
44
+ "serif": {
45
+ "normal": "Times-Roman",
46
+ "bold": "Times-Bold",
47
+ "italic": "Times-Italic",
48
+ "bold_italic": "Times-BoldItalic"
49
+ },
50
+ "monospace": {
51
+ "normal": "Courier",
52
+ "bold": "Courier-Bold",
53
+ "italic": "Courier-Oblique",
54
+ "bold_italic": "Courier-BoldOblique"
55
+ },
56
+ "fixed": {
57
+ "normal": "Courier",
58
+ "bold": "Courier-Bold",
59
+ "italic": "Courier-Oblique",
60
+ "bold_italic": "Courier-BoldOblique"
61
+ },
62
+ "dejavu sans": {
63
+ "bold": "DejaVuSans-Bold",
64
+ "bold_italic": "DejaVuSans-BoldOblique",
65
+ "italic": "DejaVuSans-Oblique",
66
+ "normal": "DejaVuSans"
67
+ },
68
+ "dejavu sans mono": {
69
+ "bold": "DejaVuSansMono-Bold",
70
+ "bold_italic": "DejaVuSansMono-BoldOblique",
71
+ "italic": "DejaVuSansMono-Oblique",
72
+ "normal": "DejaVuSansMono"
73
+ },
74
+ "dejavu serif": {
75
+ "bold": "DejaVuSerif-Bold",
76
+ "bold_italic": "DejaVuSerif-BoldItalic",
77
+ "italic": "DejaVuSerif-Italic",
78
+ "normal": "DejaVuSerif"
79
+ }
80
+ }
vendor/dompdf/dompdf/lib/html5lib/Data.php DELETED
@@ -1,123 +0,0 @@
1
- <?php
2
-
3
- // warning: this file is encoded in UTF-8!
4
-
5
- class HTML5_Data
6
- {
7
-
8
- // at some point this should be moved to a .ser file. Another
9
- // possible optimization is to give UTF-8 bytes, not Unicode
10
- // codepoints
11
- // XXX: Not quite sure why it's named this; this is
12
- // actually the numeric entity dereference table.
13
- protected static $realCodepointTable = [
14
- 0x00 => 0xFFFD, // REPLACEMENT CHARACTER
15
- 0x0D => 0x000A, // LINE FEED (LF)
16
- 0x80 => 0x20AC, // EURO SIGN ('€')
17
- 0x81 => 0x0081, // <control>
18
- 0x82 => 0x201A, // SINGLE LOW-9 QUOTATION MARK ('‚')
19
- 0x83 => 0x0192, // LATIN SMALL LETTER F WITH HOOK ('ƒ')
20
- 0x84 => 0x201E, // DOUBLE LOW-9 QUOTATION MARK ('„')
21
- 0x85 => 0x2026, // HORIZONTAL ELLIPSIS ('…')
22
- 0x86 => 0x2020, // DAGGER ('†')
23
- 0x87 => 0x2021, // DOUBLE DAGGER ('‡')
24
- 0x88 => 0x02C6, // MODIFIER LETTER CIRCUMFLEX ACCENT ('ˆ')
25
- 0x89 => 0x2030, // PER MILLE SIGN ('‰')
26
- 0x8A => 0x0160, // LATIN CAPITAL LETTER S WITH CARON ('Š')
27
- 0x8B => 0x2039, // SINGLE LEFT-POINTING ANGLE QUOTATION MARK ('‹')
28
- 0x8C => 0x0152, // LATIN CAPITAL LIGATURE OE ('Œ')
29
- 0x8D => 0x008D, // <control>
30
- 0x8E => 0x017D, // LATIN CAPITAL LETTER Z WITH CARON ('Ž')
31
- 0x8F => 0x008F, // <control>
32
- 0x90 => 0x0090, // <control>
33
- 0x91 => 0x2018, // LEFT SINGLE QUOTATION MARK ('‘')
34
- 0x92 => 0x2019, // RIGHT SINGLE QUOTATION MARK ('’')
35
- 0x93 => 0x201C, // LEFT DOUBLE QUOTATION MARK ('“')
36
- 0x94 => 0x201D, // RIGHT DOUBLE QUOTATION MARK ('”')
37
- 0x95 => 0x2022, // BULLET ('•')
38
- 0x96 => 0x2013, // EN DASH ('–')
39
- 0x97 => 0x2014, // EM DASH ('—')
40
- 0x98 => 0x02DC, // SMALL TILDE ('˜')
41
- 0x99 => 0x2122, // TRADE MARK SIGN ('™')
42
- 0x9A => 0x0161, // LATIN SMALL LETTER S WITH CARON ('š')
43
- 0x9B => 0x203A, // SINGLE RIGHT-POINTING ANGLE QUOTATION MARK ('›')
44
- 0x9C => 0x0153, // LATIN SMALL LIGATURE OE ('œ')
45
- 0x9D => 0x009D, // <control>
46
- 0x9E => 0x017E, // LATIN SMALL LETTER Z WITH CARON ('ž')
47
- 0x9F => 0x0178, // LATIN CAPITAL LETTER Y WITH DIAERESIS ('Ÿ')
48
- ];
49
-
50
- protected static $namedCharacterReferences;
51
-
52
- protected static $namedCharacterReferenceMaxLength;
53
-
54
- /**
55
- * Returns the "real" Unicode codepoint of a malformed character
56
- * reference.
57
- */
58
- public static function getRealCodepoint($ref) {
59
- if (!isset(self::$realCodepointTable[$ref])) {
60
- return false;
61
- } else {
62
- return self::$realCodepointTable[$ref];
63
- }
64
- }
65
-
66
- public static function getNamedCharacterReferences() {
67
- if (!self::$namedCharacterReferences) {
68
- self::$namedCharacterReferences = unserialize(
69
- file_get_contents(dirname(__FILE__) . '/named-character-references.ser'));
70
- }
71
- return self::$namedCharacterReferences;
72
- }
73
-
74
- /**
75
- * Converts a Unicode codepoint to sequence of UTF-8 bytes.
76
- * @note Shamelessly stolen from HTML Purifier, which is also
77
- * shamelessly stolen from Feyd (which is in public domain).
78
- */
79
- public static function utf8chr($code) {
80
- /* We don't care: we live dangerously
81
- * if($code > 0x10FFFF or $code < 0x0 or
82
- ($code >= 0xD800 and $code <= 0xDFFF) ) {
83
- // bits are set outside the "valid" range as defined
84
- // by UNICODE 4.1.0
85
- return "\xEF\xBF\xBD";
86
- }*/
87
-
88
- $y = $z = $w = 0;
89
- if ($code < 0x80) {
90
- // regular ASCII character
91
- $x = $code;
92
- } else {
93
- // set up bits for UTF-8
94
- $x = ($code & 0x3F) | 0x80;
95
- if ($code < 0x800) {
96
- $y = (($code & 0x7FF) >> 6) | 0xC0;
97
- } else {
98
- $y = (($code & 0xFC0) >> 6) | 0x80;
99
- if ($code < 0x10000) {
100
- $z = (($code >> 12) & 0x0F) | 0xE0;
101
- } else {
102
- $z = (($code >> 12) & 0x3F) | 0x80;
103
- $w = (($code >> 18) & 0x07) | 0xF0;
104
- }
105
- }
106
- }
107
- // set up the actual character
108
- $ret = '';
109
- if ($w) {
110
- $ret .= chr($w);
111
- }
112
- if ($z) {
113
- $ret .= chr($z);
114
- }
115
- if ($y) {
116
- $ret .= chr($y);
117
- }
118
- $ret .= chr($x);
119
-
120
- return $ret;
121
- }
122
-
123
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
vendor/dompdf/dompdf/lib/html5lib/InputStream.php DELETED
@@ -1,299 +0,0 @@
1
- <?php
2
-
3
- /*
4
-
5
- Copyright 2009 Geoffrey Sneddon <http://gsnedders.com/>
6
-
7
- Permission is hereby granted, free of charge, to any person obtaining a
8
- copy of this software and associated documentation files (the
9
- "Software"), to deal in the Software without restriction, including
10
- without limitation the rights to use, copy, modify, merge, publish,
11
- distribute, sublicense, and/or sell copies of the Software, and to
12
- permit persons to whom the Software is furnished to do so, subject to
13
- the following conditions:
14
-
15
- The above copyright notice and this permission notice shall be included
16
- in all copies or substantial portions of the Software.
17
-
18
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
19
- OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
20
- MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
21
- IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
22
- CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
23
- TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
24
- SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
25
-
26
- */
27
-
28
- // Some conventions:
29
- // /* */ indicates verbatim text from the HTML 5 specification
30
- // // indicates regular comments
31
-
32
- class HTML5_InputStream {
33
- /**
34
- * The string data we're parsing.
35
- */
36
- private $data;
37
-
38
- /**
39
- * The current integer byte position we are in $data
40
- */
41
- private $char;
42
-
43
- /**
44
- * Length of $data; when $char === $data, we are at the end-of-file.
45
- */
46
- private $EOF;
47
-
48
- /**
49
- * Parse errors.
50
- */
51
- public $errors = [];
52
-
53
- /**
54
- * @param $data | Data to parse
55
- * @throws Exception
56
- */
57
- public function __construct($data) {
58
-
59
- /* Given an encoding, the bytes in the input stream must be
60
- converted to Unicode characters for the tokeniser, as
61
- described by the rules for that encoding, except that the
62
- leading U+FEFF BYTE ORDER MARK character, if any, must not
63
- be stripped by the encoding layer (it is stripped by the rule below).
64
-
65
- Bytes or sequences of bytes in the original byte stream that
66
- could not be converted to Unicode characters must be converted
67
- to U+FFFD REPLACEMENT CHARACTER code points. */
68
-
69
- // XXX currently assuming input data is UTF-8; once we
70
- // build encoding detection this will no longer be the case
71
- //
72
- // We previously had an mbstring implementation here, but that
73
- // implementation is heavily non-conforming, so it's been
74
- // omitted.
75
- if (extension_loaded('iconv')) {
76
- // non-conforming
77
- $data = @iconv('UTF-8', 'UTF-8//IGNORE', $data);
78
- } else {
79
- // we can make a conforming native implementation
80
- throw new Exception('Not implemented, please install iconv');
81
- }
82
-
83
- /* One leading U+FEFF BYTE ORDER MARK character must be
84
- ignored if any are present. */
85
- if (substr($data, 0, 3) === "\xEF\xBB\xBF") {
86
- $data = substr($data, 3);
87
- }
88
-
89
- /* All U+0000 NULL characters in the input must be replaced
90
- by U+FFFD REPLACEMENT CHARACTERs. Any occurrences of such
91
- characters is a parse error. */
92
- for ($i = 0, $count = substr_count($data, "\0"); $i < $count; $i++) {
93
- $this->errors[] = [
94
- 'type' => HTML5_Tokenizer::PARSEERROR,
95
- 'data' => 'null-character'
96
- ];
97
- }
98
- /* U+000D CARRIAGE RETURN (CR) characters and U+000A LINE FEED
99
- (LF) characters are treated specially. Any CR characters
100
- that are followed by LF characters must be removed, and any
101
- CR characters not followed by LF characters must be converted
102
- to LF characters. Thus, newlines in HTML DOMs are represented
103
- by LF characters, and there are never any CR characters in the
104
- input to the tokenization stage. */
105
- $data = str_replace(
106
- [
107
- "\0",
108
- "\r\n",
109
- "\r"
110
- ],
111
- [
112
- "\xEF\xBF\xBD",
113
- "\n",
114
- "\n"
115
- ],
116
- $data
117
- );
118
-
119
- /* Any occurrences of any characters in the ranges U+0001 to
120
- U+0008, U+000B, U+000E to U+001F, U+007F to U+009F,
121
- U+D800 to U+DFFF , U+FDD0 to U+FDEF, and
122
- characters U+FFFE, U+FFFF, U+1FFFE, U+1FFFF, U+2FFFE, U+2FFFF,
123
- U+3FFFE, U+3FFFF, U+4FFFE, U+4FFFF, U+5FFFE, U+5FFFF, U+6FFFE,
124
- U+6FFFF, U+7FFFE, U+7FFFF, U+8FFFE, U+8FFFF, U+9FFFE, U+9FFFF,
125
- U+AFFFE, U+AFFFF, U+BFFFE, U+BFFFF, U+CFFFE, U+CFFFF, U+DFFFE,
126
- U+DFFFF, U+EFFFE, U+EFFFF, U+FFFFE, U+FFFFF, U+10FFFE, and
127
- U+10FFFF are parse errors. (These are all control characters
128
- or permanently undefined Unicode characters.) */
129
- // Check PCRE is loaded.
130
- if (extension_loaded('pcre')) {
131
- $count = preg_match_all(
132
- '/(?:
133
- [\x01-\x08\x0B\x0E-\x1F\x7F] # U+0001 to U+0008, U+000B, U+000E to U+001F and U+007F
134
- |
135
- \xC2[\x80-\x9F] # U+0080 to U+009F
136
- |
137
- \xED(?:\xA0[\x80-\xFF]|[\xA1-\xBE][\x00-\xFF]|\xBF[\x00-\xBF]) # U+D800 to U+DFFFF
138
- |
139
- \xEF\xB7[\x90-\xAF] # U+FDD0 to U+FDEF
140
- |
141
- \xEF\xBF[\xBE\xBF] # U+FFFE and U+FFFF
142
- |
143
- [\xF0-\xF4][\x8F-\xBF]\xBF[\xBE\xBF] # U+nFFFE and U+nFFFF (1 <= n <= 10_{16})
144
- )/x',
145
- $data,
146
- $matches
147
- );
148
- for ($i = 0; $i < $count; $i++) {
149
- $this->errors[] = [
150
- 'type' => HTML5_Tokenizer::PARSEERROR,
151
- 'data' => 'invalid-codepoint'
152
- ];
153
- }
154
- } else {
155
- // XXX: Need non-PCRE impl, probably using substr_count
156
- }
157
-
158
- $this->data = $data;
159
- $this->char = 0;
160
- $this->EOF = strlen($data);
161
- }
162
-
163
- /**
164
- * Returns the current line that the tokenizer is at.
165
- *
166
- * @return int
167
- */
168
- public function getCurrentLine() {
169
- // Check the string isn't empty
170
- if ($this->EOF) {
171
- // Add one to $this->char because we want the number for the next
172
- // byte to be processed.
173
- return substr_count($this->data, "\n", 0, min($this->char, $this->EOF)) + 1;
174
- } else {
175
- // If the string is empty, we are on the first line (sorta).
176
- return 1;
177
- }
178
- }
179
-
180
- /**
181
- * Returns the current column of the current line that the tokenizer is at.
182
- *
183
- * @return int
184
- */
185
- public function getColumnOffset() {
186
- // strrpos is weird, and the offset needs to be negative for what we
187
- // want (i.e., the last \n before $this->char). This needs to not have
188
- // one (to make it point to the next character, the one we want the
189
- // position of) added to it because strrpos's behaviour includes the
190
- // final offset byte.
191
- $lastLine = strrpos($this->data, "\n", $this->char - 1 - strlen($this->data));
192
-
193
- // However, for here we want the length up until the next byte to be
194
- // processed, so add one to the current byte ($this->char).
195
- if ($lastLine !== false) {
196
- $findLengthOf = substr($this->data, $lastLine + 1, $this->char - 1 - $lastLine);
197
- } else {
198
- $findLengthOf = substr($this->data, 0, $this->char);
199
- }
200
-
201
- // Get the length for the string we need.
202
- if (extension_loaded('iconv')) {
203
- return iconv_strlen($findLengthOf, 'utf-8');
204
- } elseif (extension_loaded('mbstring')) {
205
- return mb_strlen($findLengthOf, 'utf-8');
206
- } elseif (extension_loaded('xml')) {
207
- return strlen(utf8_decode($findLengthOf));
208
- } else {
209
- $count = count_chars($findLengthOf);
210
- // 0x80 = 0x7F - 0 + 1 (one added to get inclusive range)
211
- // 0x33 = 0xF4 - 0x2C + 1 (one added to get inclusive range)
212
- return array_sum(array_slice($count, 0, 0x80)) +
213
- array_sum(array_slice($count, 0xC2, 0x33));
214
- }
215
- }
216
-
217
- /**
218
- * Retrieve the currently consume character.
219
- * @note This performs bounds checking
220
- *
221
- * @return bool|string
222
- */
223
- public function char() {
224
- return ($this->char++ < $this->EOF)
225
- ? $this->data[$this->char - 1]
226
- : false;
227
- }
228
-
229
- /**
230
- * Get all characters until EOF.
231
- * @note This performs bounds checking
232
- *
233
- * @return string|bool
234
- */
235
- public function remainingChars() {
236
- if ($this->char < $this->EOF) {
237
- $data = substr($this->data, $this->char);
238
- $this->char = $this->EOF;
239
- return $data;
240
- } else {
241
- return false;
242
- }
243
- }
244
-
245
- /**
246
- * Matches as far as possible until we reach a certain set of bytes
247
- * and returns the matched substring.
248
- *
249
- * @param $bytes | Bytes to match.
250
- * @param null $max
251
- * @return bool|string
252
- */
253
- public function charsUntil($bytes, $max = null) {
254
- if ($this->char < $this->EOF) {
255
- if ($max === 0 || $max) {
256
- $len = strcspn($this->data, $bytes, $this->char, $max);
257
- } else {
258
- $len = strcspn($this->data, $bytes, $this->char);
259
- }
260
- $string = (string) substr($this->data, $this->char, $len);
261
- $this->char += $len;
262
- return $string;
263
- } else {
264
- return false;
265
- }
266
- }
267
-
268
- /**
269
- * Matches as far as possible with a certain set of bytes
270
- * and returns the matched substring.
271
- *
272
- * @param $bytes | Bytes to match.
273
- * @param null $max
274
- * @return bool|string
275
- */
276
- public function charsWhile($bytes, $max = null) {
277
- if ($this->char < $this->EOF) {
278
- if ($max === 0 || $max) {
279
- $len = strspn($this->data, $bytes, $this->char, $max);
280
- } else {
281
- $len = strspn($this->data, $bytes, $this->char);
282
- }
283
- $string = (string) substr($this->data, $this->char, $len);
284
- $this->char += $len;
285
- return $string;
286
- } else {
287
- return false;
288
- }
289
- }
290
-
291
- /**
292
- * Unconsume one character.
293
- */
294
- public function unget() {
295
- if ($this->char <= $this->EOF) {
296
- $this->char--;
297
- }
298
- }
299
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
vendor/dompdf/dompdf/lib/html5lib/Parser.php DELETED
@@ -1,37 +0,0 @@
1
- <?php
2
-
3
- require_once dirname(__FILE__) . '/Data.php';
4
- require_once dirname(__FILE__) . '/InputStream.php';
5
- require_once dirname(__FILE__) . '/TreeBuilder.php';
6
- require_once dirname(__FILE__) . '/Tokenizer.php';
7
-
8
- /**
9
- * Outwards facing interface for HTML5.
10
- */
11
- class HTML5_Parser
12
- {
13
- /**
14
- * Parses a full HTML document.
15
- * @param $text | HTML text to parse
16
- * @param $builder | Custom builder implementation
17
- * @return DOMDocument|DOMNodeList Parsed HTML as DOMDocument
18
- */
19
- public static function parse($text, $builder = null) {
20
- $tokenizer = new HTML5_Tokenizer($text, $builder);
21
- $tokenizer->parse();
22
- return $tokenizer->save();
23
- }
24
-
25
- /**
26
- * Parses an HTML fragment.
27
- * @param $text | HTML text to parse
28
- * @param $context String name of context element to pretend parsing is in.
29
- * @param $builder | Custom builder implementation
30
- * @return DOMDocument|DOMNodeList Parsed HTML as DOMDocument
31
- */
32
- public static function parseFragment($text, $context = null, $builder = null) {
33
- $tokenizer = new HTML5_Tokenizer($text, $builder);
34
- $tokenizer->parseFragment($context);
35
- return $tokenizer->save();
36
- }
37
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
vendor/dompdf/dompdf/lib/html5lib/Tokenizer.php DELETED
@@ -1,2470 +0,0 @@
1
- <?php
2
-
3
- /*
4
-
5
- Copyright 2007 Jeroen van der Meer <http://jero.net/>
6
- Copyright 2008 Edward Z. Yang <http://htmlpurifier.org/>
7
- Copyright 2009 Geoffrey Sneddon <http://gsnedders.com/>
8
-
9
- Permission is hereby granted, free of charge, to any person obtaining a
10
- copy of this software and associated documentation files (the
11
- "Software"), to deal in the Software without restriction, including
12
- without limitation the rights to use, copy, modify, merge, publish,
13
- distribute, sublicense, and/or sell copies of the Software, and to
14
- permit persons to whom the Software is furnished to do so, subject to
15
- the following conditions:
16
-
17
- The above copyright notice and this permission notice shall be included
18
- in all copies or substantial portions of the Software.
19
-
20
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
21
- OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
22
- MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
23
- IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
24
- CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
25
- TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
26
- SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
27
-
28
- */
29
-
30
- // Some conventions:
31
- // /* */ indicates verbatim text from the HTML 5 specification
32
- // // indicates regular comments
33
-
34
- // all flags are in hyphenated form
35
-
36
- class HTML5_Tokenizer {
37
- /**
38
- * @var HTML5_InputStream
39
- *
40
- * Points to an InputStream object.
41
- */
42
- protected $stream;
43
-
44
- /**
45
- * @var HTML5_TreeBuilder
46
- *
47
- * Tree builder that the tokenizer emits token to.
48
- */
49
- private $tree;
50
-
51
- /**
52
- * @var int
53
- *
54
- * Current content model we are parsing as.
55
- */
56
- protected $content_model;
57
-
58
- /**
59
- * Current token that is being built, but not yet emitted. Also
60
- * is the last token emitted, if applicable.
61
- */
62
- protected $token;
63
-
64
- // These are constants describing the content model
65
- const PCDATA = 0;
66
- const RCDATA = 1;
67
- const CDATA = 2;
68
- const PLAINTEXT = 3;
69
-
70
- // These are constants describing tokens
71
- // XXX should probably be moved somewhere else, probably the
72
- // HTML5 class.
73
- const DOCTYPE = 0;
74
- const STARTTAG = 1;
75
- const ENDTAG = 2;
76
- const COMMENT = 3;
77
- const CHARACTER = 4;
78
- const SPACECHARACTER = 5;
79
- const EOF = 6;
80
- const PARSEERROR = 7;
81
-
82
- // These are constants representing bunches of characters.
83
- const ALPHA = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz';
84
- const UPPER_ALPHA = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ';
85
- const LOWER_ALPHA = 'abcdefghijklmnopqrstuvwxyz';
86
- const DIGIT = '0123456789';
87
- const HEX = '0123456789ABCDEFabcdef';
88
- const WHITESPACE = "\t\n\x0c ";
89
-
90
- /**
91
- * @param $data | Data to parse
92
- * @param HTML5_TreeBuilder|null $builder
93
- */
94
- public function __construct($data, $builder = null) {
95
- $this->stream = new HTML5_InputStream($data);
96
- if (!$builder) {
97
- $this->tree = new HTML5_TreeBuilder;
98
- } else {
99
- $this->tree = $builder;
100
- }
101
- $this->content_model = self::PCDATA;
102
- }
103
-
104
- /**
105
- * @param null $context
106
- */
107
- public function parseFragment($context = null) {
108
- $this->tree->setupContext($context);
109
- if ($this->tree->content_model) {
110
- $this->content_model = $this->tree->content_model;
111
- $this->tree->content_model = null;
112
- }
113
- $this->parse();
114
- }
115
-
116
- // XXX maybe convert this into an iterator? regardless, this function
117
- // and the save function should go into a Parser facade of some sort
118
- /**
119
- * Performs the actual parsing of the document.
120
- */
121
- public function parse() {
122
- // Current state
123
- $state = 'data';
124
- // This is used to avoid having to have look-behind in the data state.
125
- $lastFourChars = '';
126
- /**
127
- * Escape flag as specified by the HTML5 specification: "used to
128
- * control the behavior of the tokeniser. It is either true or
129
- * false, and initially must be set to the false state."
130
- */
131
- $escape = false;
132
- //echo "\n\n";
133
- while($state !== null) {
134
-
135
- /*echo $state . ' ';
136
- switch ($this->content_model) {
137
- case self::PCDATA: echo 'PCDATA'; break;
138
- case self::RCDATA: echo 'RCDATA'; break;
139
- case self::CDATA: echo 'CDATA'; break;
140
- case self::PLAINTEXT: echo 'PLAINTEXT'; break;
141
- }
142
- if ($escape) echo " escape";
143
- echo "\n";*/
144
-
145
- switch($state) {
146
- case 'data':
147
-
148
- /* Consume the next input character */
149
- $char = $this->stream->char();
150
- $lastFourChars .= $char;
151
- if (strlen($lastFourChars) > 4) {
152
- $lastFourChars = substr($lastFourChars, -4);
153
- }
154
-
155
- // see below for meaning
156
- $hyp_cond =
157
- !$escape &&
158
- (
159
- $this->content_model === self::RCDATA ||
160
- $this->content_model === self::CDATA
161
- );
162
- $amp_cond =
163
- !$escape &&
164
- (
165
- $this->content_model === self::PCDATA ||
166
- $this->content_model === self::RCDATA
167
- );
168
- $lt_cond =
169
- $this->content_model === self::PCDATA ||
170
- (
171
- (
172
- $this->content_model === self::RCDATA ||
173
- $this->content_model === self::CDATA
174
- ) &&
175
- !$escape
176
- );
177
- $gt_cond =
178
- $escape &&
179
- (
180
- $this->content_model === self::RCDATA ||
181
- $this->content_model === self::CDATA
182
- );
183
-
184
- if ($char === '&' && $amp_cond === true) {
185
- /* U+0026 AMPERSAND (&)
186
- When the content model flag is set to one of the PCDATA or RCDATA
187
- states and the escape flag is false: switch to the
188
- character reference data state. Otherwise: treat it as per
189
- the "anything else" entry below. */
190
- $state = 'character reference data';
191
-
192
- } elseif (
193
- $char === '-' &&
194
- $hyp_cond === true &&
195
- $lastFourChars === '<!--'
196
- ) {
197
- /*
198
- U+002D HYPHEN-MINUS (-)
199
- If the content model flag is set to either the RCDATA state or
200
- the CDATA state, and the escape flag is false, and there are at
201
- least three characters before this one in the input stream, and the
202
- last four characters in the input stream, including this one, are
203
- U+003C LESS-THAN SIGN, U+0021 EXCLAMATION MARK, U+002D HYPHEN-MINUS,
204
- and U+002D HYPHEN-MINUS ("<!--"), then set the escape flag to true. */
205
- $escape = true;
206
-
207
- /* In any case, emit the input character as a character token. Stay
208
- in the data state. */
209
- $this->emitToken([
210
- 'type' => self::CHARACTER,
211
- 'data' => '-'
212
- ]);
213
- // We do the "any case" part as part of "anything else".
214
-
215
- /* U+003C LESS-THAN SIGN (<) */
216
- } elseif ($char === '<' && $lt_cond === true) {
217
- /* When the content model flag is set to the PCDATA state: switch
218
- to the tag open state.
219
-
220
- When the content model flag is set to either the RCDATA state or
221
- the CDATA state and the escape flag is false: switch to the tag
222
- open state.
223
-
224
- Otherwise: treat it as per the "anything else" entry below. */
225
- $state = 'tag open';
226
-
227
- /* U+003E GREATER-THAN SIGN (>) */
228
- } elseif (
229
- $char === '>' &&
230
- $gt_cond === true &&
231
- substr($lastFourChars, 1) === '-->'
232
- ) {
233
- /* If the content model flag is set to either the RCDATA state or
234
- the CDATA state, and the escape flag is true, and the last three
235
- characters in the input stream including this one are U+002D
236
- HYPHEN-MINUS, U+002D HYPHEN-MINUS, U+003E GREATER-THAN SIGN ("-->"),
237
- set the escape flag to false. */
238
- $escape = false;
239
-
240
- /* In any case, emit the input character as a character token.
241
- Stay in the data state. */
242
- $this->emitToken([
243
- 'type' => self::CHARACTER,
244
- 'data' => '>'
245
- ]);
246
- // We do the "any case" part as part of "anything else".
247
-
248
- } elseif ($char === false) {
249
- /* EOF
250
- Emit an end-of-file token. */
251
- $state = null;
252
- $this->tree->emitToken([
253
- 'type' => self::EOF
254
- ]);
255
-
256
- } elseif ($char === "\t" || $char === "\n" || $char === "\x0c" || $char === ' ') {
257
- // Directly after emitting a token you switch back to the "data
258
- // state". At that point spaceCharacters are important so they are
259
- // emitted separately.
260
- $chars = $this->stream->charsWhile(self::WHITESPACE);
261
- $this->emitToken([
262
- 'type' => self::SPACECHARACTER,
263
- 'data' => $char . $chars
264
- ]);
265
- $lastFourChars .= $chars;
266
- if (strlen($lastFourChars) > 4) {
267
- $lastFourChars = substr($lastFourChars, -4);
268
- }
269
- } else {
270
- /* Anything else
271
- THIS IS AN OPTIMIZATION: Get as many character that
272
- otherwise would also be treated as a character token and emit it
273
- as a single character token. Stay in the data state. */
274
-
275
- $mask = '';
276
- if ($hyp_cond === true) {
277
- $mask .= '-';
278
- }
279
- if ($amp_cond === true) {
280
- $mask .= '&';
281
- }
282
- if ($lt_cond === true) {
283
- $mask .= '<';
284
- }
285
- if ($gt_cond === true) {
286
- $mask .= '>';
287
- }
288
-
289
- if ($mask === '') {
290
- $chars = $this->stream->remainingChars();
291
- } else {
292
- $chars = $this->stream->charsUntil($mask);
293
- }
294
-
295
- $this->emitToken([
296
- 'type' => self::CHARACTER,
297
- 'data' => $char . $chars
298
- ]);
299
-
300
- $lastFourChars .= $chars;
301
- if (strlen($lastFourChars) > 4) {
302
- $lastFourChars = substr($lastFourChars, -4);
303
- }
304
-
305
- $state = 'data';
306
- }
307
- break;
308
-
309
- case 'character reference data':
310
- /* (This cannot happen if the content model flag
311
- is set to the CDATA state.) */
312
-
313
- /* Attempt to consume a character reference, with no
314
- additional allowed character. */
315
- $entity = $this->consumeCharacterReference();
316
-
317
- /* If nothing is returned, emit a U+0026 AMPERSAND
318
- character token. Otherwise, emit the character token that
319
- was returned. */
320
- // This is all done when consuming the character reference.
321
- $this->emitToken([
322
- 'type' => self::CHARACTER,
323
- 'data' => $entity
324
- ]);
325
-
326
- /* Finally, switch to the data state. */
327
- $state = 'data';
328
- break;
329
-
330
- case 'tag open':
331
- $char = $this->stream->char();
332
-
333
- switch ($this->content_model) {
334
- case self::RCDATA:
335
- case self::CDATA:
336
- /* Consume the next input character. If it is a
337
- U+002F SOLIDUS (/) character, switch to the close
338
- tag open state. Otherwise, emit a U+003C LESS-THAN
339
- SIGN character token and reconsume the current input
340
- character in the data state. */
341
- // We consumed above.
342
-
343
- if ($char === '/') {
344
- $state = 'close tag open';
345
- } else {
346
- $this->emitToken([
347
- 'type' => self::CHARACTER,
348
- 'data' => '<'
349
- ]);
350
-
351
- $this->stream->unget();
352
-
353
- $state = 'data';
354
- }
355
- break;
356
-
357
- case self::PCDATA:
358
- /* If the content model flag is set to the PCDATA state
359
- Consume the next input character: */
360
- // We consumed above.
361
-
362
- if ($char === '!') {
363
- /* U+0021 EXCLAMATION MARK (!)
364
- Switch to the markup declaration open state. */
365
- $state = 'markup declaration open';
366
-
367
- } elseif ($char === '/') {
368
- /* U+002F SOLIDUS (/)
369
- Switch to the close tag open state. */
370
- $state = 'close tag open';
371
-
372
- } elseif ('A' <= $char && $char <= 'Z') {
373
- /* U+0041 LATIN LETTER A through to U+005A LATIN LETTER Z
374
- Create a new start tag token, set its tag name to the lowercase
375
- version of the input character (add 0x0020 to the character's code
376
- point), then switch to the tag name state. (Don't emit the token
377
- yet; further details will be filled in before it is emitted.) */
378
- $this->token = [
379
- 'name' => strtolower($char),
380
- 'type' => self::STARTTAG,
381
- 'attr' => []
382
- ];
383
-
384
- $state = 'tag name';
385
-
386
- } elseif ('a' <= $char && $char <= 'z') {
387
- /* U+0061 LATIN SMALL LETTER A through to U+007A LATIN SMALL LETTER Z
388
- Create a new start tag token, set its tag name to the input
389
- character, then switch to the tag name state. (Don't emit
390
- the token yet; further details will be filled in before it
391
- is emitted.) */
392
- $this->token = [
393
- 'name' => $char,
394
- 'type' => self::STARTTAG,
395
- 'attr' => []
396
- ];
397
-
398
- $state = 'tag name';
399
-
400
- } elseif ($char === '>') {
401
- /* U+003E GREATER-THAN SIGN (>)
402
- Parse error. Emit a U+003C LESS-THAN SIGN character token and a
403
- U+003E GREATER-THAN SIGN character token. Switch to the data state. */
404
- $this->emitToken([
405
- 'type' => self::PARSEERROR,
406
- 'data' => 'expected-tag-name-but-got-right-bracket'
407
- ]);
408
- $this->emitToken([
409
- 'type' => self::CHARACTER,
410
- 'data' => '<>'
411
- ]);
412
-
413
- $state = 'data';
414
-
415
- } elseif ($char === '?') {
416
- /* U+003F QUESTION MARK (?)
417
- Parse error. Switch to the bogus comment state. */
418
- $this->emitToken([
419
- 'type' => self::PARSEERROR,
420
- 'data' => 'expected-tag-name-but-got-question-mark'
421
- ]);
422
- $this->token = [
423
- 'data' => '?',
424
- 'type' => self::COMMENT
425
- ];
426
- $state = 'bogus comment';
427
-
428
- } else {
429
- /* Anything else
430
- Parse error. Emit a U+003C LESS-THAN SIGN character token and
431
- reconsume the current input character in the data state. */
432
- $this->emitToken([
433
- 'type' => self::PARSEERROR,
434
- 'data' => 'expected-tag-name'
435
- ]);
436
- $this->emitToken([
437
- 'type' => self::CHARACTER,
438
- 'data' => '<'
439
- ]);
440
-
441
- $state = 'data';
442
- $this->stream->unget();
443
- }
444
- break;
445
- }
446
- break;
447
-
448
- case 'close tag open':
449
- if (
450
- $this->content_model === self::RCDATA ||
451
- $this->content_model === self::CDATA
452
- ) {
453
- /* If the content model flag is set to the RCDATA or CDATA
454
- states... */
455
- $name = strtolower($this->stream->charsWhile(self::ALPHA));
456
- $following = $this->stream->char();
457
- $this->stream->unget();
458
- if (
459
- !$this->token ||
460
- $this->token['name'] !== $name ||
461
- $this->token['name'] === $name && !in_array($following, ["\x09", "\x0A", "\x0C", "\x20", "\x3E", "\x2F", false])
462
- ) {
463
- /* if no start tag token has ever been emitted by this instance
464
- of the tokenizer (fragment case), or, if the next few
465
- characters do not match the tag name of the last start tag
466
- token emitted (compared in an ASCII case-insensitive manner),
467
- or if they do but they are not immediately followed by one of
468
- the following characters:
469
-
470
- * U+0009 CHARACTER TABULATION
471
- * U+000A LINE FEED (LF)
472
- * U+000C FORM FEED (FF)
473
- * U+0020 SPACE
474
- * U+003E GREATER-THAN SIGN (>)
475
- * U+002F SOLIDUS (/)
476
- * EOF
477
-
478
- ...then emit a U+003C LESS-THAN SIGN character token, a
479
- U+002F SOLIDUS character token, and switch to the data
480
- state to process the next input character. */
481
- // XXX: Probably ought to replace in_array with $following === x ||...
482
-
483
- // We also need to emit $name now we've consumed that, as we
484
- // know it'll just be emitted as a character token.
485
- $this->emitToken([
486
- 'type' => self::CHARACTER,
487
- 'data' => '</' . $name
488
- ]);
489
-
490
- $state = 'data';
491
- } else {
492
- // This matches what would happen if we actually did the
493
- // otherwise below (but we can't because we've consumed too
494
- // much).
495
-
496
- // Start the end tag token with the name we already have.
497
- $this->token = [
498
- 'name' => $name,
499
- 'type' => self::ENDTAG
500
- ];
501
-
502
- // Change to tag name state.
503
- $state = 'tag name';
504
- }
505
- } elseif ($this->content_model === self::PCDATA) {
506
- /* Otherwise, if the content model flag is set to the PCDATA
507
- state [...]: */
508
- $char = $this->stream->char();
509
-
510
- if ('A' <= $char && $char <= 'Z') {
511
- /* U+0041 LATIN LETTER A through to U+005A LATIN LETTER Z
512
- Create a new end tag token, set its tag name to the lowercase version
513
- of the input character (add 0x0020 to the character's code point), then
514
- switch to the tag name state. (Don't emit the token yet; further details
515
- will be filled in before it is emitted.) */
516
- $this->token = [
517
- 'name' => strtolower($char),
518
- 'type' => self::ENDTAG
519
- ];
520
-
521
- $state = 'tag name';
522
-
523
- } elseif ('a' <= $char && $char <= 'z') {
524
- /* U+0061 LATIN SMALL LETTER A through to U+007A LATIN SMALL LETTER Z
525
- Create a new end tag token, set its tag name to the
526
- input character, then switch to the tag name state.
527
- (Don't emit the token yet; further details will be
528
- filled in before it is emitted.) */
529
- $this->token = [
530
- 'name' => $char,
531
- 'type' => self::ENDTAG
532
- ];
533
-
534
- $state = 'tag name';
535
-
536
- } elseif ($char === '>') {
537
- /* U+003E GREATER-THAN SIGN (>)
538
- Parse error. Switch to the data state. */
539
- $this->emitToken([
540
- 'type' => self::PARSEERROR,
541
- 'data' => 'expected-closing-tag-but-got-right-bracket'
542
- ]);
543
- $state = 'data';
544
-
545
- } elseif ($char === false) {
546
- /* EOF
547
- Parse error. Emit a U+003C LESS-THAN SIGN character token and a U+002F
548
- SOLIDUS character token. Reconsume the EOF character in the data state. */
549
- $this->emitToken([
550
- 'type' => self::PARSEERROR,
551
- 'data' => 'expected-closing-tag-but-got-eof'
552
- ]);
553
- $this->emitToken([
554
- 'type' => self::CHARACTER,
555
- 'data' => '</'
556
- ]);
557
-
558
- $this->stream->unget();
559
- $state = 'data';
560
-
561
- } else {
562
- /* Parse error. Switch to the bogus comment state. */
563
- $this->emitToken([
564
- 'type' => self::PARSEERROR,
565
- 'data' => 'expected-closing-tag-but-got-char'
566
- ]);
567
- $this->token = [
568
- 'data' => $char,
569
- 'type' => self::COMMENT
570
- ];
571
- $state = 'bogus comment';
572
- }
573
- }
574
- break;
575
-
576
- case 'tag name':
577
- /* Consume the next input character: */
578
- $char = $this->stream->char();
579
-
580
- if ($char === "\t" || $char === "\n" || $char === "\x0c" || $char === ' ') {
581
- /* U+0009 CHARACTER TABULATION
582
- U+000A LINE FEED (LF)
583
- U+000C FORM FEED (FF)
584
- U+0020 SPACE
585
- Switch to the before attribute name state. */
586
- $state = 'before attribute name';
587
-
588
- } elseif ($char === '/') {
589
- /* U+002F SOLIDUS (/)
590
- Switch to the self-closing start tag state. */
591
- $state = 'self-closing start tag';
592
-
593
- } elseif ($char === '>') {
594
- /* U+003E GREATER-THAN SIGN (>)
595
- Emit the current tag token. Switch to the data state. */
596
- $this->emitToken($this->token);
597
- $state = 'data';
598
-
599
- } elseif ('A' <= $char && $char <= 'Z') {
600
- /* U+0041 LATIN CAPITAL LETTER A through to U+005A LATIN CAPITAL LETTER Z
601
- Append the lowercase version of the current input
602
- character (add 0x0020 to the character's code point) to
603
- the current tag token's tag name. Stay in the tag name state. */
604
- $chars = $this->stream->charsWhile(self::UPPER_ALPHA);
605
-
606
- $this->token['name'] .= strtolower($char . $chars);
607
- $state = 'tag name';
608
-
609
- } elseif ($char === false) {
610
- /* EOF
611
- Parse error. Reconsume the EOF character in the data state. */
612
- $this->emitToken([
613
- 'type' => self::PARSEERROR,
614
- 'data' => 'eof-in-tag-name'
615
- ]);
616
-
617
- $this->stream->unget();
618
- $state = 'data';
619
-
620
- } else {
621
- /* Anything else
622
- Append the current input character to the current tag token's tag name.
623
- Stay in the tag name state. */
624
- $chars = $this->stream->charsUntil("\t\n\x0C />" . self::UPPER_ALPHA);
625
-
626
- $this->token['name'] .= $char . $chars;
627
- $state = 'tag name';
628
- }
629
- break;
630
-
631
- case 'before attribute name':
632
- /* Consume the next input character: */
633
- $char = $this->stream->char();
634
-
635
- // this conditional is optimized, check bottom
636
- if ($char === "\t" || $char === "\n" || $char === "\x0c" || $char === ' ') {
637
- /* U+0009 CHARACTER TABULATION
638
- U+000A LINE FEED (LF)
639
- U+000C FORM FEED (FF)
640
- U+0020 SPACE
641
- Stay in the before attribute name state. */
642
- $state = 'before attribute name';
643
-
644
- } elseif ($char === '/') {
645
- /* U+002F SOLIDUS (/)
646
- Switch to the self-closing start tag state. */
647
- $state = 'self-closing start tag';
648
-
649
- } elseif ($char === '>') {
650
- /* U+003E GREATER-THAN SIGN (>)
651
- Emit the current tag token. Switch to the data state. */
652
- $this->emitToken($this->token);
653
- $state = 'data';
654
-
655
- } elseif ('A' <= $char && $char <= 'Z') {
656
- /* U+0041 LATIN CAPITAL LETTER A through to U+005A LATIN CAPITAL LETTER Z
657
- Start a new attribute in the current tag token. Set that
658
- attribute's name to the lowercase version of the current
659
- input character (add 0x0020 to the character's code
660
- point), and its value to the empty string. Switch to the
661
- attribute name state.*/
662
- $this->token['attr'][] = [
663
- 'name' => strtolower($char),
664
- 'value' => ''
665
- ];
666
-
667
- $state = 'attribute name';
668
-
669
- } elseif ($char === false) {
670
- /* EOF
671
- Parse error. Reconsume the EOF character in the data state. */
672
- $this->emitToken([
673
- 'type' => self::PARSEERROR,
674
- 'data' => 'expected-attribute-name-but-got-eof'
675
- ]);
676
-
677
- $this->stream->unget();
678
- $state = 'data';
679
-
680
- } else {
681
- /* U+0022 QUOTATION MARK (")
682
- U+0027 APOSTROPHE (')
683
- U+003C LESS-THAN SIGN (<)
684
- U+003D EQUALS SIGN (=)
685
- Parse error. Treat it as per the "anything else" entry
686
- below. */
687
- if ($char === '"' || $char === "'" || $char === '<' || $char === '=') {
688
- $this->emitToken([
689
- 'type' => self::PARSEERROR,
690
- 'data' => 'invalid-character-in-attribute-name'
691
- ]);
692
- }
693
-
694
- /* Anything else
695
- Start a new attribute in the current tag token. Set that attribute's
696
- name to the current input character, and its value to the empty string.
697
- Switch to the attribute name state. */
698
- $this->token['attr'][] = [
699
- 'name' => $char,
700
- 'value' => ''
701
- ];
702
-
703
- $state = 'attribute name';
704
- }
705
- break;
706
-
707
- case 'attribute name':
708
- // Consume the next input character:
709
- $char = $this->stream->char();
710
-
711
- // this conditional is optimized, check bottom
712
- if ($char === "\t" || $char === "\n" || $char === "\x0c" || $char === ' ') {
713
- /* U+0009 CHARACTER TABULATION
714
- U+000A LINE FEED (LF)
715
- U+000C FORM FEED (FF)
716
- U+0020 SPACE
717
- Switch to the after attribute name state. */
718
- $state = 'after attribute name';
719
-
720
- } elseif ($char === '/') {
721
- /* U+002F SOLIDUS (/)
722
- Switch to the self-closing start tag state. */
723
- $state = 'self-closing start tag';
724
-
725
- } elseif ($char === '=') {
726
- /* U+003D EQUALS SIGN (=)
727
- Switch to the before attribute value state. */
728
- $state = 'before attribute value';
729
-
730
- } elseif ($char === '>') {
731
- /* U+003E GREATER-THAN SIGN (>)
732
- Emit the current tag token. Switch to the data state. */
733
- $this->emitToken($this->token);
734
- $state = 'data';
735
-
736
- } elseif ('A' <= $char && $char <= 'Z') {
737
- /* U+0041 LATIN CAPITAL LETTER A through to U+005A LATIN CAPITAL LETTER Z
738
- Append the lowercase version of the current input
739
- character (add 0x0020 to the character's code point) to
740
- the current attribute's name. Stay in the attribute name
741
- state. */
742
- $chars = $this->stream->charsWhile(self::UPPER_ALPHA);
743
-
744
- $last = count($this->token['attr']) - 1;
745
- $this->token['attr'][$last]['name'] .= strtolower($char . $chars);
746
-
747
- $state = 'attribute name';
748
-
749
- } elseif ($char === false) {
750
- /* EOF
751
- Parse error. Reconsume the EOF character in the data state. */
752
- $this->emitToken([
753
- 'type' => self::PARSEERROR,
754
- 'data' => 'eof-in-attribute-name'
755
- ]);
756
-
757
- $this->stream->unget();
758
- $state = 'data';
759
-
760
- } else {
761
- /* U+0022 QUOTATION MARK (")
762
- U+0027 APOSTROPHE (')
763
- U+003C LESS-THAN SIGN (<)
764
- Parse error. Treat it as per the "anything else"
765
- entry below. */
766
- if ($char === '"' || $char === "'" || $char === '<') {
767
- $this->emitToken([
768
- 'type' => self::PARSEERROR,
769
- 'data' => 'invalid-character-in-attribute-name'
770
- ]);
771
- }
772
-
773
- /* Anything else
774
- Append the current input character to the current attribute's name.
775
- Stay in the attribute name state. */
776
- $chars = $this->stream->charsUntil("\t\n\x0C /=>\"'" . self::UPPER_ALPHA);
777
-
778
- $last = count($this->token['attr']) - 1;
779
- $this->token['attr'][$last]['name'] .= $char . $chars;
780
-
781
- $state = 'attribute name';
782
- }
783
-
784
- /* When the user agent leaves the attribute name state
785
- (and before emitting the tag token, if appropriate), the
786
- complete attribute's name must be compared to the other
787
- attributes on the same token; if there is already an
788
- attribute on the token with the exact same name, then this
789
- is a parse error and the new attribute must be dropped, along
790
- with the value that gets associated with it (if any). */
791
- // this might be implemented in the emitToken method
792
- break;
793
-
794
- case 'after attribute name':
795
- // Consume the next input character:
796
- $char = $this->stream->char();
797
-
798
- // this is an optimized conditional, check the bottom
799
- if ($char === "\t" || $char === "\n" || $char === "\x0c" || $char === ' ') {
800
- /* U+0009 CHARACTER TABULATION
801
- U+000A LINE FEED (LF)
802
- U+000C FORM FEED (FF)
803
- U+0020 SPACE
804
- Stay in the after attribute name state. */
805
- $state = 'after attribute name';
806
-
807
- } elseif ($char === '/') {
808
- /* U+002F SOLIDUS (/)
809
- Switch to the self-closing start tag state. */
810
- $state = 'self-closing start tag';
811
-
812
- } elseif ($char === '=') {
813
- /* U+003D EQUALS SIGN (=)
814
- Switch to the before attribute value state. */
815
- $state = 'before attribute value';
816
-
817
- } elseif ($char === '>') {
818
- /* U+003E GREATER-THAN SIGN (>)
819
- Emit the current tag token. Switch to the data state. */
820
- $this->emitToken($this->token);
821
- $state = 'data';
822
-
823
- } elseif ('A' <= $char && $char <= 'Z') {
824
- /* U+0041 LATIN CAPITAL LETTER A through to U+005A LATIN CAPITAL LETTER Z
825
- Start a new attribute in the current tag token. Set that
826
- attribute's name to the lowercase version of the current
827
- input character (add 0x0020 to the character's code
828
- point), and its value to the empty string. Switch to the
829
- attribute name state. */
830
- $this->token['attr'][] = [
831
- 'name' => strtolower($char),
832
- 'value' => ''
833
- ];
834
-
835
- $state = 'attribute name';
836
-
837
- } elseif ($char === false) {
838
- /* EOF
839
- Parse error. Reconsume the EOF character in the data state. */
840
- $this->emitToken([
841
- 'type' => self::PARSEERROR,
842
- 'data' => 'expected-end-of-tag-but-got-eof'
843
- ]);
844
-
845
- $this->stream->unget();
846
- $state = 'data';
847
-
848
- } else {
849
- /* U+0022 QUOTATION MARK (")
850
- U+0027 APOSTROPHE (')
851
- U+003C LESS-THAN SIGN(<)
852
- Parse error. Treat it as per the "anything else"
853
- entry below. */
854
- if ($char === '"' || $char === "'" || $char === "<") {
855
- $this->emitToken([
856
- 'type' => self::PARSEERROR,
857
- 'data' => 'invalid-character-after-attribute-name'
858
- ]);
859
- }
860
-
861
- /* Anything else
862
- Start a new attribute in the current tag token. Set that attribute's
863
- name to the current input character, and its value to the empty string.
864
- Switch to the attribute name state. */
865
- $this->token['attr'][] = [
866
- 'name' => $char,
867
- 'value' => ''
868
- ];
869
-
870
- $state = 'attribute name';
871
- }
872
- break;
873
-
874
- case 'before attribute value':
875
- // Consume the next input character:
876
- $char = $this->stream->char();
877
-
878
- // this is an optimized conditional
879
- if ($char === "\t" || $char === "\n" || $char === "\x0c" || $char === ' ') {
880
- /* U+0009 CHARACTER TABULATION
881
- U+000A LINE FEED (LF)
882
- U+000C FORM FEED (FF)
883
- U+0020 SPACE
884
- Stay in the before attribute value state. */
885
- $state = 'before attribute value';
886
-
887
- } elseif ($char === '"') {
888
- /* U+0022 QUOTATION MARK (")
889
- Switch to the attribute value (double-quoted) state. */
890
- $state = 'attribute value (double-quoted)';
891
-
892
- } elseif ($char === '&') {
893
- /* U+0026 AMPERSAND (&)
894
- Switch to the attribute value (unquoted) state and reconsume
895
- this input character. */
896
- $this->stream->unget();
897
- $state = 'attribute value (unquoted)';
898
-
899
- } elseif ($char === '\'') {
900
- /* U+0027 APOSTROPHE (')
901
- Switch to the attribute value (single-quoted) state. */
902
- $state = 'attribute value (single-quoted)';
903
-
904
- } elseif ($char === '>') {
905
- /* U+003E GREATER-THAN SIGN (>)
906
- Parse error. Emit the current tag token. Switch to the data state. */
907
- $this->emitToken([
908
- 'type' => self::PARSEERROR,
909
- 'data' => 'expected-attribute-value-but-got-right-bracket'
910
- ]);
911
- $this->emitToken($this->token);
912
- $state = 'data';
913
-
914
- } elseif ($char === false) {
915
- /* EOF
916
- Parse error. Reconsume the EOF character in the data state. */
917
- $this->emitToken([
918
- 'type' => self::PARSEERROR,
919
- 'data' => 'expected-attribute-value-but-got-eof'
920
- ]);
921
- $this->stream->unget();
922
- $state = 'data';
923
-
924
- } else {
925
- /* U+003D EQUALS SIGN (=)
926
- * U+003C LESS-THAN SIGN (<)
927
- Parse error. Treat it as per the "anything else" entry below. */
928
- if ($char === '=' || $char === '<') {
929
- $this->emitToken([
930
- 'type' => self::PARSEERROR,
931
- 'data' => 'equals-in-unquoted-attribute-value'
932
- ]);
933
- }
934
-
935
- /* Anything else
936
- Append the current input character to the current attribute's value.
937
- Switch to the attribute value (unquoted) state. */
938
- $last = count($this->token['attr']) - 1;
939
- $this->token['attr'][$last]['value'] .= $char;
940
-
941
- $state = 'attribute value (unquoted)';
942
- }
943
- break;
944
-
945
- case 'attribute value (double-quoted)':
946
- // Consume the next input character:
947
- $char = $this->stream->char();
948
-
949
- if ($char === '"') {
950
- /* U+0022 QUOTATION MARK (")
951
- Switch to the after attribute value (quoted) state. */
952
- $state = 'after attribute value (quoted)';
953
-
954
- } elseif ($char === '&') {
955
- /* U+0026 AMPERSAND (&)
956
- Switch to the character reference in attribute value
957
- state, with the additional allowed character
958
- being U+0022 QUOTATION MARK ("). */
959
- $this->characterReferenceInAttributeValue('"');
960
-
961
- } elseif ($char === false) {
962
- /* EOF
963
- Parse error. Reconsume the EOF character in the data state. */
964
- $this->emitToken([
965
- 'type' => self::PARSEERROR,
966
- 'data' => 'eof-in-attribute-value-double-quote'
967
- ]);
968
-
969
- $this->stream->unget();
970
- $state = 'data';
971
-
972
- } else {
973
- /* Anything else
974
- Append the current input character to the current attribute's value.
975
- Stay in the attribute value (double-quoted) state. */
976
- $chars = $this->stream->charsUntil('"&');
977
-
978
- $last = count($this->token['attr']) - 1;
979
- $this->token['attr'][$last]['value'] .= $char . $chars;
980
-
981
- $state = 'attribute value (double-quoted)';
982
- }
983
- break;
984
-
985
- case 'attribute value (single-quoted)':
986
- // Consume the next input character:
987
- $char = $this->stream->char();
988
-
989
- if ($char === "'") {
990
- /* U+0022 QUOTATION MARK (')
991
- Switch to the after attribute value state. */
992
- $state = 'after attribute value (quoted)';
993
-
994
- } elseif ($char === '&') {
995
- /* U+0026 AMPERSAND (&)
996
- Switch to the entity in attribute value state. */
997
- $this->characterReferenceInAttributeValue("'");
998
-
999
- } elseif ($char === false) {
1000
- /* EOF
1001
- Parse error. Reconsume the EOF character in the data state. */
1002
- $this->emitToken([
1003
- 'type' => self::PARSEERROR,
1004
- 'data' => 'eof-in-attribute-value-single-quote'
1005
- ]);
1006
-
1007
- $this->stream->unget();
1008
- $state = 'data';
1009
-
1010
- } else {
1011
- /* Anything else
1012
- Append the current input character to the current attribute's value.
1013
- Stay in the attribute value (single-quoted) state. */
1014
- $chars = $this->stream->charsUntil("'&");
1015
-
1016
- $last = count($this->token['attr']) - 1;
1017
- $this->token['attr'][$last]['value'] .= $char . $chars;
1018
-
1019
- $state = 'attribute value (single-quoted)';
1020
- }
1021
- break;
1022
-
1023
- case 'attribute value (unquoted)':
1024
- // Consume the next input character:
1025
- $char = $this->stream->char();
1026
-
1027
- if ($char === "\t" || $char === "\n" || $char === "\x0c" || $char === ' ') {
1028
- /* U+0009 CHARACTER TABULATION
1029
- U+000A LINE FEED (LF)
1030
- U+000C FORM FEED (FF)
1031
- U+0020 SPACE
1032
- Switch to the before attribute name state. */
1033
- $state = 'before attribute name';
1034
-
1035
- } elseif ($char === '&') {
1036
- /* U+0026 AMPERSAND (&)
1037
- Switch to the entity in attribute value state, with the
1038
- additional allowed character being U+003E
1039
- GREATER-THAN SIGN (>). */
1040
- $this->characterReferenceInAttributeValue('>');
1041
-
1042
- } elseif ($char === '>') {
1043
- /* U+003E GREATER-THAN SIGN (>)
1044
- Emit the current tag token. Switch to the data state. */
1045
- $this->emitToken($this->token);
1046
- $state = 'data';
1047
-
1048
- } elseif ($char === false) {
1049
- /* EOF
1050
- Parse error. Reconsume the EOF character in the data state. */
1051
- $this->emitToken([
1052
- 'type' => self::PARSEERROR,
1053
- 'data' => 'eof-in-attribute-value-no-quotes'
1054
- ]);
1055
- $this->stream->unget();
1056
- $state = 'data';
1057
-
1058
- } else {
1059
- /* U+0022 QUOTATION MARK (")
1060
- U+0027 APOSTROPHE (')
1061
- U+003C LESS-THAN SIGN (<)
1062
- U+003D EQUALS SIGN (=)
1063
- Parse error. Treat it as per the "anything else"
1064
- entry below. */
1065
- if ($char === '"' || $char === "'" || $char === '=' || $char == '<') {
1066
- $this->emitToken([
1067
- 'type' => self::PARSEERROR,
1068
- 'data' => 'unexpected-character-in-unquoted-attribute-value'
1069
- ]);
1070
- }
1071
-
1072
- /* Anything else
1073
- Append the current input character to the current attribute's value.
1074
- Stay in the attribute value (unquoted) state. */
1075
- $chars = $this->stream->charsUntil("\t\n\x0c &>\"'=");
1076
-
1077
- $last = count($this->token['attr']) - 1;
1078
- $this->token['attr'][$last]['value'] .= $char . $chars;
1079
-
1080
- $state = 'attribute value (unquoted)';
1081
- }
1082
- break;
1083
-
1084
- case 'after attribute value (quoted)':
1085
- /* Consume the next input character: */
1086
- $char = $this->stream->char();
1087
-
1088
- if ($char === "\t" || $char === "\n" || $char === "\x0c" || $char === ' ') {
1089
- /* U+0009 CHARACTER TABULATION
1090
- U+000A LINE FEED (LF)
1091
- U+000C FORM FEED (FF)
1092
- U+0020 SPACE
1093
- Switch to the before attribute name state. */
1094
- $state = 'before attribute name';
1095
-
1096
- } elseif ($char === '/') {
1097
- /* U+002F SOLIDUS (/)
1098
- Switch to the self-closing start tag state. */
1099
- $state = 'self-closing start tag';
1100
-
1101
- } elseif ($char === '>') {
1102
- /* U+003E GREATER-THAN SIGN (>)
1103
- Emit the current tag token. Switch to the data state. */
1104
- $this->emitToken($this->token);
1105
- $state = 'data';
1106
-
1107
- } elseif ($char === false) {
1108
- /* EOF
1109
- Parse error. Reconsume the EOF character in the data state. */
1110
- $this->emitToken([
1111
- 'type' => self::PARSEERROR,
1112
- 'data' => 'unexpected-EOF-after-attribute-value'
1113
- ]);
1114
- $this->stream->unget();
1115
- $state = 'data';
1116
-
1117
- } else {
1118
- /* Anything else
1119
- Parse error. Reconsume the character in the before attribute
1120
- name state. */
1121
- $this->emitToken([
1122
- 'type' => self::PARSEERROR,
1123
- 'data' => 'unexpected-character-after-attribute-value'
1124
- ]);
1125
- $this->stream->unget();
1126
- $state = 'before attribute name';
1127
- }
1128
- break;
1129
-
1130
- case 'self-closing start tag':
1131
- /* Consume the next input character: */
1132
- $char = $this->stream->char();
1133
-
1134
- if ($char === '>') {
1135
- /* U+003E GREATER-THAN SIGN (>)
1136
- Set the self-closing flag of the current tag token.
1137
- Emit the current tag token. Switch to the data state. */
1138
- // not sure if this is the name we want
1139
- $this->token['self-closing'] = true;
1140
- $this->emitToken($this->token);
1141
- $state = 'data';
1142
-
1143
- } elseif ($char === false) {
1144
- /* EOF
1145
- Parse error. Reconsume the EOF character in the data state. */
1146
- $this->emitToken([
1147
- 'type' => self::PARSEERROR,
1148
- 'data' => 'unexpected-eof-after-self-closing'
1149
- ]);
1150
- $this->stream->unget();
1151
- $state = 'data';
1152
-
1153
- } else {
1154
- /* Anything else
1155
- Parse error. Reconsume the character in the before attribute name state. */
1156
- $this->emitToken([
1157
- 'type' => self::PARSEERROR,
1158
- 'data' => 'unexpected-character-after-self-closing'
1159
- ]);
1160
- $this->stream->unget();
1161
- $state = 'before attribute name';
1162
- }
1163
- break;
1164
-
1165
- case 'bogus comment':
1166
- /* (This can only happen if the content model flag is set to the PCDATA state.) */
1167
- /* Consume every character up to the first U+003E GREATER-THAN SIGN
1168
- character (>) or the end of the file (EOF), whichever comes first. Emit
1169
- a comment token whose data is the concatenation of all the characters
1170
- starting from and including the character that caused the state machine
1171
- to switch into the bogus comment state, up to and including the last
1172
- consumed character before the U+003E character, if any, or up to the
1173
- end of the file otherwise. (If the comment was started by the end of
1174
- the file (EOF), the token is empty.) */
1175
- $this->token['data'] .= (string) $this->stream->charsUntil('>');
1176
- $this->stream->char();
1177
-
1178
- $this->emitToken($this->token);
1179
-
1180
- /* Switch to the data state. */
1181
- $state = 'data';
1182
- break;
1183
-
1184
- case 'markup declaration open':
1185
- // Consume for below
1186
- $hyphens = $this->stream->charsWhile('-', 2);
1187
- if ($hyphens === '-') {
1188
- $this->stream->unget();
1189
- }
1190
- if ($hyphens !== '--') {
1191
- $alpha = $this->stream->charsWhile(self::ALPHA, 7);
1192
- }
1193
-
1194
- /* If the next two characters are both U+002D HYPHEN-MINUS (-)
1195
- characters, consume those two characters, create a comment token whose
1196
- data is the empty string, and switch to the comment state. */
1197
- if ($hyphens === '--') {
1198
- $state = 'comment start';
1199
- $this->token = [
1200
- 'data' => '',
1201
- 'type' => self::COMMENT
1202
- ];
1203
-
1204
- /* Otherwise if the next seven characters are a case-insensitive match
1205
- for the word "DOCTYPE", then consume those characters and switch to the
1206
- DOCTYPE state. */
1207
- } elseif (strtoupper($alpha) === 'DOCTYPE') {
1208
- $state = 'DOCTYPE';
1209
-
1210
- // XXX not implemented
1211
- /* Otherwise, if the insertion mode is "in foreign content"
1212
- and the current node is not an element in the HTML namespace
1213
- and the next seven characters are an ASCII case-sensitive
1214
- match for the string "[CDATA[" (the five uppercase letters
1215
- "CDATA" with a U+005B LEFT SQUARE BRACKET character before
1216
- and after), then consume those characters and switch to the
1217
- CDATA section state (which is unrelated to the content model
1218
- flag's CDATA state). */
1219
-
1220
- /* Otherwise, is is a parse error. Switch to the bogus comment state.
1221
- The next character that is consumed, if any, is the first character
1222
- that will be in the comment. */
1223
- } else {
1224
- $this->emitToken([
1225
- 'type' => self::PARSEERROR,
1226
- 'data' => 'expected-dashes-or-doctype'
1227
- ]);
1228
- $this->token = [
1229
- 'data' => (string) $alpha,
1230
- 'type' => self::COMMENT
1231
- ];
1232
- $state = 'bogus comment';
1233
- }
1234
- break;
1235
-
1236
- case 'comment start':
1237
- /* Consume the next input character: */
1238
- $char = $this->stream->char();
1239
-
1240
- if ($char === '-') {
1241
- /* U+002D HYPHEN-MINUS (-)
1242
- Switch to the comment start dash state. */
1243
- $state = 'comment start dash';
1244
- } elseif ($char === '>') {
1245
- /* U+003E GREATER-THAN SIGN (>)
1246
- Parse error. Emit the comment token. Switch to the
1247
- data state. */
1248
- $this->emitToken([
1249
- 'type' => self::PARSEERROR,
1250
- 'data' => 'incorrect-comment'
1251
- ]);
1252
- $this->emitToken($this->token);
1253
- $state = 'data';
1254
- } elseif ($char === false) {
1255
- /* EOF
1256
- Parse error. Emit the comment token. Reconsume the
1257
- EOF character in the data state. */
1258
- $this->emitToken([
1259
- 'type' => self::PARSEERROR,
1260
- 'data' => 'eof-in-comment'
1261
- ]);
1262
- $this->emitToken($this->token);
1263
- $this->stream->unget();
1264
- $state = 'data';
1265
- } else {
1266
- /* Anything else
1267
- Append the input character to the comment token's
1268
- data. Switch to the comment state. */
1269
- $this->token['data'] .= $char;
1270
- $state = 'comment';
1271
- }
1272
- break;
1273
-
1274
- case 'comment start dash':
1275
- /* Consume the next input character: */
1276
- $char = $this->stream->char();
1277
- if ($char === '-') {
1278
- /* U+002D HYPHEN-MINUS (-)
1279
- Switch to the comment end state */
1280
- $state = 'comment end';
1281
- } elseif ($char === '>') {
1282
- /* U+003E GREATER-THAN SIGN (>)
1283
- Parse error. Emit the comment token. Switch to the
1284
- data state. */
1285
- $this->emitToken([
1286
- 'type' => self::PARSEERROR,
1287
- 'data' => 'incorrect-comment'
1288
- ]);
1289
- $this->emitToken($this->token);
1290
- $state = 'data';
1291
- } elseif ($char === false) {
1292
- /* Parse error. Emit the comment token. Reconsume the
1293
- EOF character in the data state. */
1294
- $this->emitToken([
1295
- 'type' => self::PARSEERROR,
1296
- 'data' => 'eof-in-comment'
1297
- ]);
1298
- $this->emitToken($this->token);
1299
- $this->stream->unget();
1300
- $state = 'data';
1301
- } else {
1302
- $this->token['data'] .= '-' . $char;
1303
- $state = 'comment';
1304
- }
1305
- break;
1306
-
1307
- case 'comment':
1308
- /* Consume the next input character: */
1309
- $char = $this->stream->char();
1310
-
1311
- if ($char === '-') {
1312
- /* U+002D HYPHEN-MINUS (-)
1313
- Switch to the comment end dash state */
1314
- $state = 'comment end dash';
1315
-
1316
- } elseif ($char === false) {
1317
- /* EOF
1318
- Parse error. Emit the comment token. Reconsume the EOF character
1319
- in the data state. */
1320
- $this->emitToken([
1321
- 'type' => self::PARSEERROR,
1322
- 'data' => 'eof-in-comment'
1323
- ]);
1324
- $this->emitToken($this->token);
1325
- $this->stream->unget();
1326
- $state = 'data';
1327
-
1328
- } else {
1329
- /* Anything else
1330
- Append the input character to the comment token's data. Stay in
1331
- the comment state. */
1332
- $chars = $this->stream->charsUntil('-');
1333
-
1334
- $this->token['data'] .= $char . $chars;
1335
- }
1336
- break;
1337
-
1338
- case 'comment end dash':
1339
- /* Consume the next input character: */
1340
- $char = $this->stream->char();
1341
-
1342
- if ($char === '-') {
1343
- /* U+002D HYPHEN-MINUS (-)
1344
- Switch to the comment end state */
1345
- $state = 'comment end';
1346
-
1347
- } elseif ($char === false) {
1348
- /* EOF
1349
- Parse error. Emit the comment token. Reconsume the EOF character
1350
- in the data state. */
1351
- $this->emitToken([
1352
- 'type' => self::PARSEERROR,
1353
- 'data' => 'eof-in-comment-end-dash'
1354
- ]);
1355
- $this->emitToken($this->token);
1356
- $this->stream->unget();
1357
- $state = 'data';
1358
-
1359
- } else {
1360
- /* Anything else
1361
- Append a U+002D HYPHEN-MINUS (-) character and the input
1362
- character to the comment token's data. Switch to the comment state. */
1363
- $this->token['data'] .= '-'.$char;
1364
- $state = 'comment';
1365
- }
1366
- break;
1367
-
1368
- case 'comment end':
1369
- /* Consume the next input character: */
1370
- $char = $this->stream->char();
1371
-
1372
- if ($char === '>') {
1373
- /* U+003E GREATER-THAN SIGN (>)
1374
- Emit the comment token. Switch to the data state. */
1375
- $this->emitToken($this->token);
1376
- $state = 'data';
1377
-
1378
- } elseif ($char === '-') {
1379
- /* U+002D HYPHEN-MINUS (-)
1380
- Parse error. Append a U+002D HYPHEN-MINUS (-) character
1381
- to the comment token's data. Stay in the comment end
1382
- state. */
1383
- $this->emitToken([
1384
- 'type' => self::PARSEERROR,
1385
- 'data' => 'unexpected-dash-after-double-dash-in-comment'
1386
- ]);
1387
- $this->token['data'] .= '-';
1388
-
1389
- } elseif ($char === "\t" || $char === "\n" || $char === "\x0a" || $char === ' ') {
1390
- $this->emitToken([
1391
- 'type' => self::PARSEERROR,
1392
- 'data' => 'unexpected-space-after-double-dash-in-comment'
1393
- ]);
1394
- $this->token['data'] .= '--' . $char;
1395
- $state = 'comment end space';
1396
-
1397
- } elseif ($char === '!') {
1398
- $this->emitToken([
1399
- 'type' => self::PARSEERROR,
1400
- 'data' => 'unexpected-bang-after-double-dash-in-comment'
1401
- ]);
1402
- $state = 'comment end bang';
1403
-
1404
- } elseif ($char === false) {
1405
- /* EOF
1406
- Parse error. Emit the comment token. Reconsume the
1407
- EOF character in the data state. */
1408
- $this->emitToken([
1409
- 'type' => self::PARSEERROR,
1410
- 'data' => 'eof-in-comment-double-dash'
1411
- ]);
1412
- $this->emitToken($this->token);
1413
- $this->stream->unget();
1414
- $state = 'data';
1415
-
1416
- } else {
1417
- /* Anything else
1418
- Parse error. Append two U+002D HYPHEN-MINUS (-)
1419
- characters and the input character to the comment token's
1420
- data. Switch to the comment state. */
1421
- $this->emitToken([
1422
- 'type' => self::PARSEERROR,
1423
- 'data' => 'unexpected-char-in-comment'
1424
- ]);
1425
- $this->token['data'] .= '--'.$char;
1426
- $state = 'comment';
1427
- }
1428
- break;
1429
-
1430
- case 'comment end bang':
1431
- $char = $this->stream->char();
1432
- if ($char === '>') {
1433
- $this->emitToken($this->token);
1434
- $state = 'data';
1435
- } elseif ($char === "-") {
1436
- $this->token['data'] .= '--!';
1437
- $state = 'comment end dash';
1438
- } elseif ($char === false) {
1439
- $this->emitToken([
1440
- 'type' => self::PARSEERROR,
1441
- 'data' => 'eof-in-comment-end-bang'
1442
- ]);
1443
- $this->emitToken($this->token);
1444
- $this->stream->unget();
1445
- $state = 'data';
1446
- } else {
1447
- $this->token['data'] .= '--!' . $char;
1448
- $state = 'comment';
1449
- }
1450
- break;
1451
-
1452
- case 'comment end space':
1453
- $char = $this->stream->char();
1454
- if ($char === '>') {
1455
- $this->emitToken($this->token);
1456
- $state = 'data';
1457
- } elseif ($char === '-') {
1458
- $state = 'comment end dash';
1459
- } elseif ($char === "\t" || $char === "\n" || $char === "\x0c" || $char === ' ') {
1460
- $this->token['data'] .= $char;
1461
- } elseif ($char === false) {
1462
- $this->emitToken([
1463
- 'type' => self::PARSEERROR,
1464
- 'data' => 'unexpected-eof-in-comment-end-space',
1465
- ]);
1466
- $this->emitToken($this->token);
1467
- $this->stream->unget();
1468
- $state = 'data';
1469
- } else {
1470
- $this->token['data'] .= $char;
1471
- $state = 'comment';
1472
- }
1473
- break;
1474
-
1475
- case 'DOCTYPE':
1476
- /* Consume the next input character: */
1477
- $char = $this->stream->char();
1478
-
1479
- if ($char === "\t" || $char === "\n" || $char === "\x0c" || $char === ' ') {
1480
- /* U+0009 CHARACTER TABULATION
1481
- U+000A LINE FEED (LF)
1482
- U+000C FORM FEED (FF)
1483
- U+0020 SPACE
1484
- Switch to the before DOCTYPE name state. */
1485
- $state = 'before DOCTYPE name';
1486
-
1487
- } elseif ($char === false) {
1488
- /* EOF
1489
- Parse error. Create a new DOCTYPE token. Set its
1490
- force-quirks flag to on. Emit the token. Reconsume the
1491
- EOF character in the data state. */
1492
- $this->emitToken([
1493
- 'type' => self::PARSEERROR,
1494
- 'data' => 'need-space-after-doctype-but-got-eof'
1495
- ]);
1496
- $this->emitToken([
1497
- 'name' => '',
1498
- 'type' => self::DOCTYPE,
1499
- 'force-quirks' => true,
1500
- 'error' => true
1501
- ]);
1502
- $this->stream->unget();
1503
- $state = 'data';
1504
-
1505
- } else {
1506
- /* Anything else
1507
- Parse error. Reconsume the current character in the
1508
- before DOCTYPE name state. */
1509
- $this->emitToken([
1510
- 'type' => self::PARSEERROR,
1511
- 'data' => 'need-space-after-doctype'
1512
- ]);
1513
- $this->stream->unget();
1514
- $state = 'before DOCTYPE name';
1515
- }
1516
- break;
1517
-
1518
- case 'before DOCTYPE name':
1519
- /* Consume the next input character: */
1520
- $char = $this->stream->char();
1521
-
1522
- if ($char === "\t" || $char === "\n" || $char === "\x0c" || $char === ' ') {
1523
- /* U+0009 CHARACTER TABULATION
1524
- U+000A LINE FEED (LF)
1525
- U+000C FORM FEED (FF)
1526
- U+0020 SPACE
1527
- Stay in the before DOCTYPE name state. */
1528
-
1529
- } elseif ($char === '>') {
1530
- /* U+003E GREATER-THAN SIGN (>)
1531
- Parse error. Create a new DOCTYPE token. Set its
1532
- force-quirks flag to on. Emit the token. Switch to the
1533
- data state. */
1534
- $this->emitToken([
1535
- 'type' => self::PARSEERROR,
1536
- 'data' => 'expected-doctype-name-but-got-right-bracket'
1537
- ]);
1538
- $this->emitToken([
1539
- 'name' => '',
1540
- 'type' => self::DOCTYPE,
1541
- 'force-quirks' => true,
1542
- 'error' => true
1543
- ]);
1544
-
1545
- $state = 'data';
1546
-
1547
- } elseif ('A' <= $char && $char <= 'Z') {
1548
- /* U+0041 LATIN CAPITAL LETTER A through to U+005A LATIN CAPITAL LETTER Z
1549
- Create a new DOCTYPE token. Set the token's name to the
1550
- lowercase version of the input character (add 0x0020 to
1551
- the character's code point). Switch to the DOCTYPE name
1552
- state. */
1553
- $this->token = [
1554
- 'name' => strtolower($char),
1555
- 'type' => self::DOCTYPE,
1556
- 'error' => true
1557
- ];
1558
-
1559
- $state = 'DOCTYPE name';
1560
-
1561
- } elseif ($char === false) {
1562
- /* EOF
1563
- Parse error. Create a new DOCTYPE token. Set its
1564
- force-quirks flag to on. Emit the token. Reconsume the
1565
- EOF character in the data state. */
1566
- $this->emitToken([
1567
- 'type' => self::PARSEERROR,
1568
- 'data' => 'expected-doctype-name-but-got-eof'
1569
- ]);
1570
- $this->emitToken([
1571
- 'name' => '',
1572
- 'type' => self::DOCTYPE,
1573
- 'force-quirks' => true,
1574
- 'error' => true
1575
- ]);
1576
-
1577
- $this->stream->unget();
1578
- $state = 'data';
1579
-
1580
- } else {
1581
- /* Anything else
1582
- Create a new DOCTYPE token. Set the token's name to the
1583
- current input character. Switch to the DOCTYPE name state. */
1584
- $this->token = [
1585
- 'name' => $char,
1586
- 'type' => self::DOCTYPE,
1587
- 'error' => true
1588
- ];
1589
-
1590
- $state = 'DOCTYPE name';
1591
- }
1592
- break;
1593
-
1594
- case 'DOCTYPE name':
1595
- /* Consume the next input character: */
1596
- $char = $this->stream->char();
1597
-
1598
- if ($char === "\t" || $char === "\n" || $char === "\x0c" || $char === ' ') {
1599
- /* U+0009 CHARACTER TABULATION
1600
- U+000A LINE FEED (LF)
1601
- U+000C FORM FEED (FF)
1602
- U+0020 SPACE
1603
- Switch to the after DOCTYPE name state. */
1604
- $state = 'after DOCTYPE name';
1605
-
1606
- } elseif ($char === '>') {
1607
- /* U+003E GREATER-THAN SIGN (>)
1608
- Emit the current DOCTYPE token. Switch to the data state. */
1609
- $this->emitToken($this->token);
1610
- $state = 'data';
1611
-
1612
- } elseif ('A' <= $char && $char <= 'Z') {
1613
- /* U+0041 LATIN CAPITAL LETTER A through to U+005A LATIN CAPITAL LETTER Z
1614
- Append the lowercase version of the input character
1615
- (add 0x0020 to the character's code point) to the current
1616
- DOCTYPE token's name. Stay in the DOCTYPE name state. */
1617
- $this->token['name'] .= strtolower($char);
1618
-
1619
- } elseif ($char === false) {
1620
- /* EOF
1621
- Parse error. Set the DOCTYPE token's force-quirks flag
1622
- to on. Emit that DOCTYPE token. Reconsume the EOF
1623
- character in the data state. */
1624
- $this->emitToken([
1625
- 'type' => self::PARSEERROR,
1626
- 'data' => 'eof-in-doctype-name'
1627
- ]);
1628
- $this->token['force-quirks'] = true;
1629
- $this->emitToken($this->token);
1630
- $this->stream->unget();
1631
- $state = 'data';
1632
-
1633
- } else {
1634
- /* Anything else
1635
- Append the current input character to the current
1636
- DOCTYPE token's name. Stay in the DOCTYPE name state. */
1637
- $this->token['name'] .= $char;
1638
- }
1639
-
1640
- // XXX this is probably some sort of quirks mode designation,
1641
- // check tree-builder to be sure. In general 'error' needs
1642
- // to be specc'ified, this probably means removing it at the end
1643
- $this->token['error'] = ($this->token['name'] === 'HTML')
1644
- ? false
1645
- : true;
1646
- break;
1647
-
1648
- case 'after DOCTYPE name':
1649
- /* Consume the next input character: */
1650
- $char = $this->stream->char();
1651
-
1652
- if ($char === "\t" || $char === "\n" || $char === "\x0c" || $char === ' ') {
1653
- /* U+0009 CHARACTER TABULATION
1654
- U+000A LINE FEED (LF)
1655
- U+000C FORM FEED (FF)
1656
- U+0020 SPACE
1657
- Stay in the after DOCTYPE name state. */
1658
-
1659
- } elseif ($char === '>') {
1660
- /* U+003E GREATER-THAN SIGN (>)
1661
- Emit the current DOCTYPE token. Switch to the data state. */
1662
- $this->emitToken($this->token);
1663
- $state = 'data';
1664
-
1665
- } elseif ($char === false) {
1666
- /* EOF
1667
- Parse error. Set the DOCTYPE token's force-quirks flag
1668
- to on. Emit that DOCTYPE token. Reconsume the EOF
1669
- character in the data state. */
1670
- $this->emitToken([
1671
- 'type' => self::PARSEERROR,
1672
- 'data' => 'eof-in-doctype'
1673
- ]);
1674
- $this->token['force-quirks'] = true;
1675
- $this->emitToken($this->token);
1676
- $this->stream->unget();
1677
- $state = 'data';
1678
-
1679
- } else {
1680
- /* Anything else */
1681
-
1682
- $nextSix = strtoupper($char . $this->stream->charsWhile(self::ALPHA, 5));
1683
- if ($nextSix === 'PUBLIC') {
1684
- /* If the next six characters are an ASCII
1685
- case-insensitive match for the word "PUBLIC", then
1686
- consume those characters and switch to the before
1687
- DOCTYPE public identifier state. */
1688
- $state = 'before DOCTYPE public identifier';
1689
-
1690
- } elseif ($nextSix === 'SYSTEM') {
1691
- /* Otherwise, if the next six characters are an ASCII
1692
- case-insensitive match for the word "SYSTEM", then
1693
- consume those characters and switch to the before
1694
- DOCTYPE system identifier state. */
1695
- $state = 'before DOCTYPE system identifier';
1696
-
1697
- } else {
1698
- /* Otherwise, this is the parse error. Set the DOCTYPE
1699
- token's force-quirks flag to on. Switch to the bogus
1700
- DOCTYPE state. */
1701
- $this->emitToken([
1702
- 'type' => self::PARSEERROR,
1703
- 'data' => 'expected-space-or-right-bracket-in-doctype'
1704
- ]);
1705
- $this->token['force-quirks'] = true;
1706
- $this->token['error'] = true;
1707
- $state = 'bogus DOCTYPE';
1708
- }
1709
- }
1710
- break;
1711
-
1712
- case 'before DOCTYPE public identifier':
1713
- /* Consume the next input character: */
1714
- $char = $this->stream->char();
1715
-
1716
- if ($char === "\t" || $char === "\n" || $char === "\x0c" || $char === ' ') {
1717
- /* U+0009 CHARACTER TABULATION
1718
- U+000A LINE FEED (LF)
1719
- U+000C FORM FEED (FF)
1720
- U+0020 SPACE
1721
- Stay in the before DOCTYPE public identifier state. */
1722
- } elseif ($char === '"') {
1723
- /* U+0022 QUOTATION MARK (")
1724
- Set the DOCTYPE token's public identifier to the empty
1725
- string (not missing), then switch to the DOCTYPE public
1726
- identifier (double-quoted) state. */
1727
- $this->token['public'] = '';
1728
- $state = 'DOCTYPE public identifier (double-quoted)';
1729
- } elseif ($char === "'") {
1730
- /* U+0027 APOSTROPHE (')
1731
- Set the DOCTYPE token's public identifier to the empty
1732
- string (not missing), then switch to the DOCTYPE public
1733
- identifier (single-quoted) state. */
1734
- $this->token['public'] = '';
1735
- $state = 'DOCTYPE public identifier (single-quoted)';
1736
- } elseif ($char === '>') {
1737
- /* Parse error. Set the DOCTYPE token's force-quirks flag
1738
- to on. Emit that DOCTYPE token. Switch to the data state. */
1739
- $this->emitToken([
1740
- 'type' => self::PARSEERROR,
1741
- 'data' => 'unexpected-end-of-doctype'
1742
- ]);
1743
- $this->token['force-quirks'] = true;
1744
- $this->emitToken($this->token);
1745
- $state = 'data';
1746
- } elseif ($char === false) {
1747
- /* Parse error. Set the DOCTYPE token's force-quirks
1748
- flag to on. Emit that DOCTYPE token. Reconsume the EOF
1749
- character in the data state. */
1750
- $this->emitToken([
1751
- 'type' => self::PARSEERROR,
1752
- 'data' => 'eof-in-doctype'
1753
- ]);
1754
- $this->token['force-quirks'] = true;
1755
- $this->emitToken($this->token);
1756
- $this->stream->unget();
1757
- $state = 'data';
1758
- } else {
1759
- /* Parse error. Set the DOCTYPE token's force-quirks flag
1760
- to on. Switch to the bogus DOCTYPE state. */
1761
- $this->emitToken([
1762
- 'type' => self::PARSEERROR,
1763
- 'data' => 'unexpected-char-in-doctype'
1764
- ]);
1765
- $this->token['force-quirks'] = true;
1766
- $state = 'bogus DOCTYPE';
1767
- }
1768
- break;
1769
-
1770
- case 'DOCTYPE public identifier (double-quoted)':
1771
- /* Consume the next input character: */
1772
- $char = $this->stream->char();
1773
-
1774
- if ($char === '"') {
1775
- /* U+0022 QUOTATION MARK (")
1776
- Switch to the after DOCTYPE public identifier state. */
1777
- $state = 'after DOCTYPE public identifier';
1778
- } elseif ($char === '>') {
1779
- /* U+003E GREATER-THAN SIGN (>)
1780
- Parse error. Set the DOCTYPE token's force-quirks flag
1781
- to on. Emit that DOCTYPE token. Switch to the data state. */
1782
- $this->emitToken([
1783
- 'type' => self::PARSEERROR,
1784
- 'data' => 'unexpected-end-of-doctype'
1785
- ]);
1786
- $this->token['force-quirks'] = true;
1787
- $this->emitToken($this->token);
1788
- $state = 'data';
1789
- } elseif ($char === false) {
1790
- /* EOF
1791
- Parse error. Set the DOCTYPE token's force-quirks flag
1792
- to on. Emit that DOCTYPE token. Reconsume the EOF
1793
- character in the data state. */
1794
- $this->emitToken([
1795
- 'type' => self::PARSEERROR,
1796
- 'data' => 'eof-in-doctype'
1797
- ]);
1798
- $this->token['force-quirks'] = true;
1799
- $this->emitToken($this->token);
1800
- $this->stream->unget();
1801
- $state = 'data';
1802
- } else {
1803
- /* Anything else
1804
- Append the current input character to the current
1805
- DOCTYPE token's public identifier. Stay in the DOCTYPE
1806
- public identifier (double-quoted) state. */
1807
- $this->token['public'] .= $char;
1808
- }
1809
- break;
1810
-
1811
- case 'DOCTYPE public identifier (single-quoted)':
1812
- /* Consume the next input character: */
1813
- $char = $this->stream->char();
1814
-
1815
- if ($char === "'") {
1816
- /* U+0027 APOSTROPHE (')
1817
- Switch to the after DOCTYPE public identifier state. */
1818
- $state = 'after DOCTYPE public identifier';
1819
- } elseif ($char === '>') {
1820
- /* U+003E GREATER-THAN SIGN (>)
1821
- Parse error. Set the DOCTYPE token's force-quirks flag
1822
- to on. Emit that DOCTYPE token. Switch to the data state. */
1823
- $this->emitToken([
1824
- 'type' => self::PARSEERROR,
1825
- 'data' => 'unexpected-end-of-doctype'
1826
- ]);
1827
- $this->token['force-quirks'] = true;
1828
- $this->emitToken($this->token);
1829
- $state = 'data';
1830
- } elseif ($char === false) {
1831
- /* EOF
1832
- Parse error. Set the DOCTYPE token's force-quirks flag
1833
- to on. Emit that DOCTYPE token. Reconsume the EOF
1834
- character in the data state. */
1835
- $this->emitToken([
1836
- 'type' => self::PARSEERROR,
1837
- 'data' => 'eof-in-doctype'
1838
- ]);
1839
- $this->token['force-quirks'] = true;
1840
- $this->emitToken($this->token);
1841
- $this->stream->unget();
1842
- $state = 'data';
1843
- } else {
1844
- /* Anything else
1845
- Append the current input character to the current
1846
- DOCTYPE token's public identifier. Stay in the DOCTYPE
1847
- public identifier (double-quoted) state. */
1848
- $this->token['public'] .= $char;
1849
- }
1850
- break;
1851
-
1852
- case 'after DOCTYPE public identifier':
1853
- /* Consume the next input character: */
1854
- $char = $this->stream->char();
1855
-
1856
- if ($char === "\t" || $char === "\n" || $char === "\x0c" || $char === ' ') {
1857
- /* U+0009 CHARACTER TABULATION
1858
- U+000A LINE FEED (LF)
1859
- U+000C FORM FEED (FF)
1860
- U+0020 SPACE
1861
- Stay in the after DOCTYPE public identifier state. */
1862
- } elseif ($char === '"') {
1863
- /* U+0022 QUOTATION MARK (")
1864
- Set the DOCTYPE token's system identifier to the
1865
- empty string (not missing), then switch to the DOCTYPE
1866
- system identifier (double-quoted) state. */
1867
- $this->token['system'] = '';
1868
- $state = 'DOCTYPE system identifier (double-quoted)';
1869
- } elseif ($char === "'") {
1870
- /* U+0027 APOSTROPHE (')
1871
- Set the DOCTYPE token's system identifier to the
1872
- empty string (not missing), then switch to the DOCTYPE
1873
- system identifier (single-quoted) state. */
1874
- $this->token['system'] = '';
1875
- $state = 'DOCTYPE system identifier (single-quoted)';
1876
- } elseif ($char === '>') {
1877
- /* U+003E GREATER-THAN SIGN (>)
1878
- Emit the current DOCTYPE token. Switch to the data state. */
1879
- $this->emitToken($this->token);
1880
- $state = 'data';
1881
- } elseif ($char === false) {
1882
- /* Parse error. Set the DOCTYPE token's force-quirks
1883
- flag to on. Emit that DOCTYPE token. Reconsume the EOF
1884
- character in the data state. */
1885
- $this->emitToken([
1886
- 'type' => self::PARSEERROR,
1887
- 'data' => 'eof-in-doctype'
1888
- ]);
1889
- $this->token['force-quirks'] = true;
1890
- $this->emitToken($this->token);
1891
- $this->stream->unget();
1892
- $state = 'data';
1893
- } else {
1894
- /* Anything else
1895
- Parse error. Set the DOCTYPE token's force-quirks flag
1896
- to on. Switch to the bogus DOCTYPE state. */
1897
- $this->emitToken([
1898
- 'type' => self::PARSEERROR,
1899
- 'data' => 'unexpected-char-in-doctype'
1900
- ]);
1901
- $this->token['force-quirks'] = true;
1902
- $state = 'bogus DOCTYPE';
1903
- }
1904
- break;
1905
-
1906
- case 'before DOCTYPE system identifier':
1907
- /* Consume the next input character: */
1908
- $char = $this->stream->char();
1909
-
1910
- if ($char === "\t" || $char === "\n" || $char === "\x0c" || $char === ' ') {
1911
- /* U+0009 CHARACTER TABULATION
1912
- U+000A LINE FEED (LF)
1913
- U+000C FORM FEED (FF)
1914
- U+0020 SPACE
1915
- Stay in the before DOCTYPE system identifier state. */
1916
- } elseif ($char === '"') {
1917
- /* U+0022 QUOTATION MARK (")
1918
- Set the DOCTYPE token's system identifier to the empty
1919
- string (not missing), then switch to the DOCTYPE system
1920
- identifier (double-quoted) state. */
1921
- $this->token['system'] = '';
1922
- $state = 'DOCTYPE system identifier (double-quoted)';
1923
- } elseif ($char === "'") {
1924
- /* U+0027 APOSTROPHE (')
1925
- Set the DOCTYPE token's system identifier to the empty
1926
- string (not missing), then switch to the DOCTYPE system
1927
- identifier (single-quoted) state. */
1928
- $this->token['system'] = '';
1929
- $state = 'DOCTYPE system identifier (single-quoted)';
1930
- } elseif ($char === '>') {
1931
- /* Parse error. Set the DOCTYPE token's force-quirks flag
1932
- to on. Emit that DOCTYPE token. Switch to the data state. */
1933
- $this->emitToken([
1934
- 'type' => self::PARSEERROR,
1935
- 'data' => 'unexpected-char-in-doctype'
1936
- ]);
1937
- $this->token['force-quirks'] = true;
1938
- $this->emitToken($this->token);
1939
- $state = 'data';
1940
- } elseif ($char === false) {
1941
- /* Parse error. Set the DOCTYPE token's force-quirks
1942
- flag to on. Emit that DOCTYPE token. Reconsume the EOF
1943
- character in the data state. */
1944
- $this->emitToken([
1945
- 'type' => self::PARSEERROR,
1946
- 'data' => 'eof-in-doctype'
1947
- ]);
1948
- $this->token['force-quirks'] = true;
1949
- $this->emitToken($this->token);
1950
- $this->stream->unget();
1951
- $state = 'data';
1952
- } else {
1953
- /* Parse error. Set the DOCTYPE token's force-quirks flag
1954
- to on. Switch to the bogus DOCTYPE state. */
1955
- $this->emitToken([
1956
- 'type' => self::PARSEERROR,
1957
- 'data' => 'unexpected-char-in-doctype'
1958
- ]);
1959
- $this->token['force-quirks'] = true;
1960
- $state = 'bogus DOCTYPE';
1961
- }
1962
- break;
1963
-
1964
- case 'DOCTYPE system identifier (double-quoted)':
1965
- /* Consume the next input character: */
1966
- $char = $this->stream->char();
1967
-
1968
- if ($char === '"') {
1969
- /* U+0022 QUOTATION MARK (")
1970
- Switch to the after DOCTYPE system identifier state. */
1971
- $state = 'after DOCTYPE system identifier';
1972
- } elseif ($char === '>') {
1973
- /* U+003E GREATER-THAN SIGN (>)
1974
- Parse error. Set the DOCTYPE token's force-quirks flag
1975
- to on. Emit that DOCTYPE token. Switch to the data state. */
1976
- $this->emitToken([
1977
- 'type' => self::PARSEERROR,
1978
- 'data' => 'unexpected-end-of-doctype'
1979
- ]);
1980
- $this->token['force-quirks'] = true;
1981
- $this->emitToken($this->token);
1982
- $state = 'data';
1983
- } elseif ($char === false) {
1984
- /* EOF
1985
- Parse error. Set the DOCTYPE token's force-quirks flag
1986
- to on. Emit that DOCTYPE token. Reconsume the EOF
1987
- character in the data state. */
1988
- $this->emitToken([
1989
- 'type' => self::PARSEERROR,
1990
- 'data' => 'eof-in-doctype'
1991
- ]);
1992
- $this->token['force-quirks'] = true;
1993
- $this->emitToken($this->token);
1994
- $this->stream->unget();
1995
- $state = 'data';
1996
- } else {
1997
- /* Anything else
1998
- Append the current input character to the current
1999
- DOCTYPE token's system identifier. Stay in the DOCTYPE
2000
- system identifier (double-quoted) state. */
2001
- $this->token['system'] .= $char;
2002
- }
2003
- break;
2004
-
2005
- case 'DOCTYPE system identifier (single-quoted)':
2006
- /* Consume the next input character: */
2007
- $char = $this->stream->char();
2008
-
2009
- if ($char === "'") {
2010
- /* U+0027 APOSTROPHE (')
2011
- Switch to the after DOCTYPE system identifier state. */
2012
- $state = 'after DOCTYPE system identifier';
2013
- } elseif ($char === '>') {
2014
- /* U+003E GREATER-THAN SIGN (>)
2015
- Parse error. Set the DOCTYPE token's force-quirks flag
2016
- to on. Emit that DOCTYPE token. Switch to the data state. */
2017
- $this->emitToken([
2018
- 'type' => self::PARSEERROR,
2019
- 'data' => 'unexpected-end-of-doctype'
2020
- ]);
2021
- $this->token['force-quirks'] = true;
2022
- $this->emitToken($this->token);
2023
- $state = 'data';
2024
- } elseif ($char === false) {
2025
- /* EOF
2026
- Parse error. Set the DOCTYPE token's force-quirks flag
2027
- to on. Emit that DOCTYPE token. Reconsume the EOF
2028
- character in the data state. */
2029
- $this->emitToken([
2030
- 'type' => self::PARSEERROR,
2031
- 'data' => 'eof-in-doctype'
2032
- ]);
2033
- $this->token['force-quirks'] = true;
2034
- $this->emitToken($this->token);
2035
- $this->stream->unget();
2036
- $state = 'data';
2037
- } else {
2038
- /* Anything else
2039
- Append the current input character to the current
2040
- DOCTYPE token's system identifier. Stay in the DOCTYPE
2041
- system identifier (double-quoted) state. */
2042
- $this->token['system'] .= $char;
2043
- }
2044
- break;
2045
-
2046
- case 'after DOCTYPE system identifier':
2047
- /* Consume the next input character: */
2048
- $char = $this->stream->char();
2049
-
2050
- if ($char === "\t" || $char === "\n" || $char === "\x0c" || $char === ' ') {
2051
- /* U+0009 CHARACTER TABULATION
2052
- U+000A LINE FEED (LF)
2053
- U+000C FORM FEED (FF)
2054
- U+0020 SPACE
2055
- Stay in the after DOCTYPE system identifier state. */
2056
- } elseif ($char === '>') {
2057
- /* U+003E GREATER-THAN SIGN (>)
2058
- Emit the current DOCTYPE token. Switch to the data state. */
2059
- $this->emitToken($this->token);
2060
- $state = 'data';
2061
- } elseif ($char === false) {
2062
- /* Parse error. Set the DOCTYPE token's force-quirks
2063
- flag to on. Emit that DOCTYPE token. Reconsume the EOF
2064
- character in the data state. */
2065
- $this->emitToken([
2066
- 'type' => self::PARSEERROR,
2067
- 'data' => 'eof-in-doctype'
2068
- ]);
2069
- $this->token['force-quirks'] = true;
2070
- $this->emitToken($this->token);
2071
- $this->stream->unget();
2072
- $state = 'data';
2073
- } else {
2074
- /* Anything else
2075
- Parse error. Switch to the bogus DOCTYPE state.
2076
- (This does not set the DOCTYPE token's force-quirks
2077
- flag to on.) */
2078
- $this->emitToken([
2079
- 'type' => self::PARSEERROR,
2080
- 'data' => 'unexpected-char-in-doctype'
2081
- ]);
2082
- $state = 'bogus DOCTYPE';
2083
- }
2084
- break;
2085
-
2086
- case 'bogus DOCTYPE':
2087
- /* Consume the next input character: */
2088
- $char = $this->stream->char();
2089
-
2090
- if ($char === '>') {
2091
- /* U+003E GREATER-THAN SIGN (>)
2092
- Emit the DOCTYPE token. Switch to the data state. */
2093
- $this->emitToken($this->token);
2094
- $state = 'data';
2095
-
2096
- } elseif ($char === false) {
2097
- /* EOF
2098
- Emit the DOCTYPE token. Reconsume the EOF character in
2099
- the data state. */
2100
- $this->emitToken($this->token);
2101
- $this->stream->unget();
2102
- $state = 'data';
2103
-
2104
- } else {
2105
- /* Anything else
2106
- Stay in the bogus DOCTYPE state. */
2107
- }
2108
- break;
2109
-
2110
- // case 'cdataSection':
2111
- }
2112
- }
2113
- }
2114
-
2115
- /**
2116
- * Returns a serialized representation of the tree.
2117
- *
2118
- * @return DOMDocument|DOMNodeList
2119
- */
2120
- public function save() {
2121
- return $this->tree->save();
2122
- }
2123
-
2124
- /**
2125
- * @return HTML5_TreeBuilder The tree
2126
- */
2127
- public function getTree()
2128
- {
2129
- return $this->tree;
2130
- }
2131
-
2132
-
2133
- /**
2134
- * Returns the input stream.
2135
- *
2136
- * @return HTML5_InputStream
2137
- */
2138
- public function stream() {
2139
- return $this->stream;
2140
- }
2141
-
2142
- /**
2143
- * @param bool $allowed
2144
- * @param bool $inattr
2145
- * @return string
2146
- */
2147
- private function consumeCharacterReference($allowed = false, $inattr = false) {
2148
- // This goes quite far against spec, and is far closer to the Python
2149
- // impl., mainly because we don't do the large unconsuming the spec
2150
- // requires.
2151
-
2152
- // All consumed characters.
2153
- $chars = $this->stream->char();
2154
-
2155
- /* This section defines how to consume a character
2156
- reference. This definition is used when parsing character
2157
- references in text and in attributes.
2158
-
2159
- The behavior depends on the identity of the next character
2160
- (the one immediately after the U+0026 AMPERSAND character): */
2161
-
2162
- if (
2163
- $chars[0] === "\x09" ||
2164
- $chars[0] === "\x0A" ||
2165
- $chars[0] === "\x0C" ||
2166
- $chars[0] === "\x20" ||
2167
- $chars[0] === '<' ||
2168
- $chars[0] === '&' ||
2169
- $chars === false ||
2170
- $chars[0] === $allowed
2171
- ) {
2172
- /* U+0009 CHARACTER TABULATION
2173
- U+000A LINE FEED (LF)
2174
- U+000C FORM FEED (FF)
2175
- U+0020 SPACE
2176
- U+003C LESS-THAN SIGN
2177
- U+0026 AMPERSAND
2178
- EOF
2179
- The additional allowed character, if there is one
2180
- Not a character reference. No characters are consumed,
2181
- and nothing is returned. (This is not an error, either.) */
2182
- // We already consumed, so unconsume.
2183
- $this->stream->unget();
2184
- return '&';
2185
- } elseif ($chars[0] === '#') {
2186
- /* Consume the U+0023 NUMBER SIGN. */
2187
- // Um, yeah, we already did that.
2188
- /* The behavior further depends on the character after
2189
- the U+0023 NUMBER SIGN: */
2190
- $chars .= $this->stream->char();
2191
- if (isset($chars[1]) && ($chars[1] === 'x' || $chars[1] === 'X')) {
2192
- /* U+0078 LATIN SMALL LETTER X
2193
- U+0058 LATIN CAPITAL LETTER X */
2194
- /* Consume the X. */
2195
- // Um, yeah, we already did that.
2196
- /* Follow the steps below, but using the range of
2197
- characters U+0030 DIGIT ZERO through to U+0039 DIGIT
2198
- NINE, U+0061 LATIN SMALL LETTER A through to U+0066
2199
- LATIN SMALL LETTER F, and U+0041 LATIN CAPITAL LETTER
2200
- A, through to U+0046 LATIN CAPITAL LETTER F (in other
2201
- words, 0123456789, ABCDEF, abcdef). */
2202
- $char_class = self::HEX;
2203
- /* When it comes to interpreting the
2204
- number, interpret it as a hexadecimal number. */
2205
- $hex = true;
2206
- } else {
2207
- /* Anything else */
2208
- // Unconsume because we shouldn't have consumed this.
2209
- $chars = $chars[0];
2210
- $this->stream->unget();
2211
- /* Follow the steps below, but using the range of
2212
- characters U+0030 DIGIT ZERO through to U+0039 DIGIT
2213
- NINE (i.e. just 0123456789). */
2214
- $char_class = self::DIGIT;
2215
- /* When it comes to interpreting the number,
2216
- interpret it as a decimal number. */
2217
- $hex = false;
2218
- }
2219
-
2220
- /* Consume as many characters as match the range of characters given above. */
2221
- $consumed = $this->stream->charsWhile($char_class);
2222
- if ($consumed === '' || $consumed === false) {
2223
- /* If no characters match the range, then don't consume
2224
- any characters (and unconsume the U+0023 NUMBER SIGN
2225
- character and, if appropriate, the X character). This
2226
- is a parse error; nothing is returned. */
2227
- $this->emitToken([
2228
- 'type' => self::PARSEERROR,
2229
- 'data' => 'expected-numeric-entity'
2230
- ]);
2231
- return '&' . $chars;
2232
- } else {
2233
- /* Otherwise, if the next character is a U+003B SEMICOLON,
2234
- consume that too. If it isn't, there is a parse error. */
2235
- if ($this->stream->char() !== ';') {
2236
- $this->stream->unget();
2237
- $this->emitToken([
2238
- 'type' => self::PARSEERROR,
2239
- 'data' => 'numeric-entity-without-semicolon'
2240
- ]);
2241
- }
2242
-
2243
- /* If one or more characters match the range, then take
2244
- them all and interpret the string of characters as a number
2245
- (either hexadecimal or decimal as appropriate). */
2246
- $codepoint = $hex ? hexdec($consumed) : (int) $consumed;
2247
-
2248
- /* If that number is one of the numbers in the first column
2249
- of the following table, then this is a parse error. Find the
2250
- row with that number in the first column, and return a
2251
- character token for the Unicode character given in the
2252
- second column of that row. */
2253
- $new_codepoint = HTML5_Data::getRealCodepoint($codepoint);
2254
- if ($new_codepoint) {
2255
- $this->emitToken([
2256
- 'type' => self::PARSEERROR,
2257
- 'data' => 'illegal-windows-1252-entity'
2258
- ]);
2259
- return HTML5_Data::utf8chr($new_codepoint);
2260
- } else {
2261
- /* Otherwise, if the number is greater than 0x10FFFF, then
2262
- * this is a parse error. Return a U+FFFD REPLACEMENT
2263
- * CHARACTER. */
2264
- if ($codepoint > 0x10FFFF) {
2265
- $this->emitToken([
2266
- 'type' => self::PARSEERROR,
2267
- 'data' => 'overlong-character-entity' // XXX probably not correct
2268
- ]);
2269
- return "\xEF\xBF\xBD";
2270
- }
2271
- /* Otherwise, return a character token for the Unicode
2272
- * character whose code point is that number. If the
2273
- * number is in the range 0x0001 to 0x0008, 0x000E to
2274
- * 0x001F, 0x007F to 0x009F, 0xD800 to 0xDFFF, 0xFDD0 to
2275
- * 0xFDEF, or is one of 0x000B, 0xFFFE, 0xFFFF, 0x1FFFE,
2276
- * 0x1FFFF, 0x2FFFE, 0x2FFFF, 0x3FFFE, 0x3FFFF, 0x4FFFE,
2277
- * 0x4FFFF, 0x5FFFE, 0x5FFFF, 0x6FFFE, 0x6FFFF, 0x7FFFE,
2278
- * 0x7FFFF, 0x8FFFE, 0x8FFFF, 0x9FFFE, 0x9FFFF, 0xAFFFE,
2279
- * 0xAFFFF, 0xBFFFE, 0xBFFFF, 0xCFFFE, 0xCFFFF, 0xDFFFE,
2280
- * 0xDFFFF, 0xEFFFE, 0xEFFFF, 0xFFFFE, 0xFFFFF, 0x10FFFE,
2281
- * or 0x10FFFF, then this is a parse error. */
2282
- // && has higher precedence than ||
2283
- if (
2284
- $codepoint >= 0x0000 && $codepoint <= 0x0008 ||
2285
- $codepoint === 0x000B ||
2286
- $codepoint >= 0x000E && $codepoint <= 0x001F ||
2287
- $codepoint >= 0x007F && $codepoint <= 0x009F ||
2288
- $codepoint >= 0xD800 && $codepoint <= 0xDFFF ||
2289
- $codepoint >= 0xFDD0 && $codepoint <= 0xFDEF ||
2290
- ($codepoint & 0xFFFE) === 0xFFFE ||
2291
- $codepoint == 0x10FFFF || $codepoint == 0x10FFFE
2292
- ) {
2293
- $this->emitToken([
2294
- 'type' => self::PARSEERROR,
2295
- 'data' => 'illegal-codepoint-for-numeric-entity'
2296
- ]);
2297
- }
2298
- return HTML5_Data::utf8chr($codepoint);
2299
- }
2300
- }
2301
- } else {
2302
- /* Anything else */
2303
-
2304
- /* Consume the maximum number of characters possible,
2305
- with the consumed characters matching one of the
2306
- identifiers in the first column of the named character
2307
- references table (in a case-sensitive manner). */
2308
- // What we actually do here is consume as much as we can while it
2309
- // matches the start of one of the identifiers in the first column.
2310
-
2311
- $refs = HTML5_Data::getNamedCharacterReferences();
2312
-
2313
- // Get the longest string which is the start of an identifier
2314
- // ($chars) as well as the longest identifier which matches ($id)
2315
- // and its codepoint ($codepoint).
2316
- $codepoint = false;
2317
- $char = $chars;
2318
- while ($char !== false && isset($refs[$char])) {
2319
- $refs = $refs[$char];
2320
- if (isset($refs['codepoint'])) {
2321
- $id = $chars;
2322
- $codepoint = $refs['codepoint'];
2323
- }
2324
- $chars .= $char = $this->stream->char();
2325
- }
2326
-
2327
- // Unconsume the one character we just took which caused the while
2328
- // statement to fail. This could be anything and could cause state
2329
- // changes (as if it matches the while loop it must be
2330
- // alphanumeric so we can just concat it to whatever we get later).
2331
- $this->stream->unget();
2332
- if ($char !== false) {
2333
- $chars = substr($chars, 0, -1);
2334
- }
2335
-
2336
- /* If no match can be made, then this is a parse error.
2337
- No characters are consumed, and nothing is returned. */
2338
- if (!$codepoint) {
2339
- $this->emitToken([
2340
- 'type' => self::PARSEERROR,
2341
- 'data' => 'expected-named-entity'
2342
- ]);
2343
- return '&' . $chars;
2344
- }
2345
-
2346
- /* If the last character matched is not a U+003B SEMICOLON
2347
- (;), there is a parse error. */
2348
- $semicolon = true;
2349
- if (substr($id, -1) !== ';') {
2350
- $this->emitToken([
2351
- 'type' => self::PARSEERROR,
2352
- 'data' => 'named-entity-without-semicolon'
2353
- ]);
2354
- $semicolon = false;
2355
- }
2356
-
2357
- /* If the character reference is being consumed as part of
2358
- an attribute, and the last character matched is not a
2359
- U+003B SEMICOLON (;), and the next character is in the
2360
- range U+0030 DIGIT ZERO to U+0039 DIGIT NINE, U+0041
2361
- LATIN CAPITAL LETTER A to U+005A LATIN CAPITAL LETTER Z,
2362
- or U+0061 LATIN SMALL LETTER A to U+007A LATIN SMALL LETTER Z,
2363
- then, for historical reasons, all the characters that were
2364
- matched after the U+0026 AMPERSAND (&) must be unconsumed,
2365
- and nothing is returned. */
2366
- if ($inattr && !$semicolon) {
2367
- // The next character is either the next character in $chars or in the stream.
2368
- if (strlen($chars) > strlen($id)) {
2369
- $next = substr($chars, strlen($id), 1);
2370
- } else {
2371
- $next = $this->stream->char();
2372
- $this->stream->unget();
2373
- }
2374
- if (
2375
- '0' <= $next && $next <= '9' ||
2376
- 'A' <= $next && $next <= 'Z' ||
2377
- 'a' <= $next && $next <= 'z'
2378
- ) {
2379
- return '&' . $chars;
2380
- }
2381
- }
2382
-
2383
- /* Otherwise, return a character token for the character
2384
- corresponding to the character reference name (as given
2385
- by the second column of the named character references table). */
2386
- return HTML5_Data::utf8chr($codepoint) . substr($chars, strlen($id));
2387
- }
2388
- }
2389
-
2390
- /**
2391
- * @param bool $allowed
2392
- */
2393
- private function characterReferenceInAttributeValue($allowed = false) {
2394
- /* Attempt to consume a character reference. */
2395
- $entity = $this->consumeCharacterReference($allowed, true);
2396
-
2397
- /* If nothing is returned, append a U+0026 AMPERSAND
2398
- character to the current attribute's value.
2399
-
2400
- Otherwise, append the returned character token to the
2401
- current attribute's value. */
2402
- $char = (!$entity)
2403
- ? '&'
2404
- : $entity;
2405
-
2406
- $last = count($this->token['attr']) - 1;
2407
- $this->token['attr'][$last]['value'] .= $char;
2408
-
2409
- /* Finally, switch back to the attribute value state that you
2410
- were in when were switched into this state. */
2411
- }
2412
-
2413
- /**
2414
- * Emits a token, passing it on to the tree builder.
2415
- *
2416
- * @param $token
2417
- * @param bool $checkStream
2418
- * @param bool $dry
2419
- */
2420
- protected function emitToken($token, $checkStream = true, $dry = false) {
2421
- if ($checkStream === true) {
2422
- // Emit errors from input stream.
2423
- while ($this->stream->errors) {
2424
- $this->emitToken(array_shift($this->stream->errors), false);
2425
- }
2426
- }
2427
- if ($token['type'] === self::ENDTAG && !empty($token['attr'])) {
2428
- for ($i = 0; $i < count($token['attr']); $i++) {
2429
- $this->emitToken([
2430
- 'type' => self::PARSEERROR,
2431
- 'data' => 'attributes-in-end-tag'
2432
- ]);
2433
- }
2434
- }
2435
- if ($token['type'] === self::ENDTAG && !empty($token['self-closing'])) {
2436
- $this->emitToken([
2437
- 'type' => self::PARSEERROR,
2438
- 'data' => 'self-closing-flag-on-end-tag',
2439
- ]);
2440
- }
2441
- if ($token['type'] === self::STARTTAG) {
2442
- // This could be changed to actually pass the tree-builder a hash
2443
- $hash = [];
2444
- foreach ($token['attr'] as $keypair) {
2445
- if (isset($hash[$keypair['name']])) {
2446
- $this->emitToken([
2447
- 'type' => self::PARSEERROR,
2448
- 'data' => 'duplicate-attribute',
2449
- ]);
2450
- } else {
2451
- $hash[$keypair['name']] = $keypair['value'];
2452
- }
2453
- }
2454
- }
2455
-
2456
- if ($dry === false) {
2457
- // the current structure of attributes is not a terribly good one
2458
- $this->tree->emitToken($token);
2459
- }
2460
-
2461
- if ($dry === false && is_int($this->tree->content_model)) {
2462
- $this->content_model = $this->tree->content_model;
2463
- $this->tree->content_model = null;
2464
-
2465
- } elseif ($token['type'] === self::ENDTAG) {
2466
- $this->content_model = self::PCDATA;
2467
- }
2468
- }
2469
- }
2470
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
vendor/dompdf/dompdf/lib/html5lib/TreeBuilder.php DELETED
@@ -1,3989 +0,0 @@
1
- <?php
2
-
3
- /*
4
-
5
- Copyright 2007 Jeroen van der Meer <http://jero.net/>
6
- Copyright 2009 Edward Z. Yang <edwardzyang@thewritingpot.com>
7
-
8
- Permission is hereby granted, free of charge, to any person obtaining a
9
- copy of this software and associated documentation files (the
10
- "Software"), to deal in the Software without restriction, including
11
- without limitation the rights to use, copy, modify, merge, publish,
12
- distribute, sublicense, and/or sell copies of the Software, and to
13
- permit persons to whom the Software is furnished to do so, subject to
14
- the following conditions:
15
-
16
- The above copyright notice and this permission notice shall be included
17
- in all copies or substantial portions of the Software.
18
-
19
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
20
- OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
21
- MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
22
- IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
23
- CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
24
- TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
25
- SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
26
-
27
- */
28
-
29
- // Tags for FIX ME!!!: (in order of priority)
30
- // XXX - should be fixed NAO!
31
- // XERROR - with regards to parse errors
32
- // XSCRIPT - with regards to scripting mode
33
- // XENCODING - with regards to encoding (for reparsing tests)
34
- // XDOM - DOM specific code (tagName is explicitly not marked).
35
- // this is not (yet) in helper functions.
36
-
37
- class HTML5_TreeBuilder {
38
- public $stack = [];
39
- public $content_model;
40
-
41
- private $mode;
42
- private $original_mode;
43
- private $secondary_mode;
44
- private $dom;
45
- // Whether or not normal insertion of nodes should actually foster
46
- // parent (used in one case in spec)
47
- private $foster_parent = false;
48
- private $a_formatting = [];
49
-
50
- private $head_pointer = null;
51
- private $form_pointer = null;
52
-
53
- private $flag_frameset_ok = true;
54
- private $flag_force_quirks = false;
55
- private $ignored = false;
56
- private $quirks_mode = null;
57
- // this gets to 2 when we want to ignore the next lf character, and
58
- // is decrement at the beginning of each processed token (this way,
59
- // code can check for (bool)$ignore_lf_token, but it phases out
60
- // appropriately)
61
- private $ignore_lf_token = 0;
62
- private $fragment = false;
63
- private $root;
64
-
65
- private $scoping = ['applet','button','caption','html','marquee','object','table','td','th', 'svg:foreignObject'];
66
- private $formatting = ['a','b','big','code','em','font','i','nobr','s','small','strike','strong','tt','u'];
67
- // dl and ds are speculative
68
- private $special = ['address','area','article','aside','base','basefont','bgsound',
69
- 'blockquote','body','br','center','col','colgroup','command','dc','dd','details','dir','div','dl','ds',
70
- 'dt','embed','fieldset','figure','footer','form','frame','frameset','h1','h2','h3','h4','h5',
71
- 'h6','head','header','hgroup','hr','iframe','img','input','isindex','li','link',
72
- 'listing','menu','meta','nav','noembed','noframes','noscript','ol',
73
- 'p','param','plaintext','pre','script','select','spacer','style',
74
- 'tbody','textarea','tfoot','thead','title','tr','ul','wbr'];
75
-
76
- private $pendingTableCharacters;
77
- private $pendingTableCharactersDirty;
78
-
79
- // Tree construction modes
80
- const INITIAL = 0;
81
- const BEFORE_HTML = 1;
82
- const BEFORE_HEAD = 2;
83
- const IN_HEAD = 3;
84
- const IN_HEAD_NOSCRIPT = 4;
85
- const AFTER_HEAD = 5;
86
- const IN_BODY = 6;
87
- const IN_CDATA_RCDATA = 7;
88
- const IN_TABLE = 8;
89
- const IN_TABLE_TEXT = 9;
90
- const IN_CAPTION = 10;
91
- const IN_COLUMN_GROUP = 11;
92
- const IN_TABLE_BODY = 12;
93
- const IN_ROW = 13;
94
- const IN_CELL = 14;
95
- const IN_SELECT = 15;
96
- const IN_SELECT_IN_TABLE= 16;
97
- const IN_FOREIGN_CONTENT= 17;
98
- const AFTER_BODY = 18;
99
- const IN_FRAMESET = 19;
100
- const AFTER_FRAMESET = 20;
101
- const AFTER_AFTER_BODY = 21;
102
- const AFTER_AFTER_FRAMESET = 22;
103
-
104
- /**
105
- * Converts a magic number to a readable name. Use for debugging.
106
- */
107
- private function strConst($number) {
108
- static $lookup;
109
- if (!$lookup) {
110
- $lookup = [];
111
- $r = new ReflectionClass('HTML5_TreeBuilder');
112
- $consts = $r->getConstants();
113
- foreach ($consts as $const => $num) {
114
- if (!is_int($num)) {
115
- continue;
116
- }
117
- $lookup[$num] = $const;
118
- }
119
- }
120
- return $lookup[$number];
121
- }
122
-
123
- // The different types of elements.
124
- const SPECIAL = 100;
125
- const SCOPING = 101;
126
- const FORMATTING = 102;
127
- const PHRASING = 103;
128
-
129
- // Quirks modes in $quirks_mode
130
- const NO_QUIRKS = 200;
131
- const QUIRKS_MODE = 201;
132
- const LIMITED_QUIRKS_MODE = 202;
133
-
134
- // Marker to be placed in $a_formatting
135
- const MARKER = 300;
136
-
137
- // Namespaces for foreign content
138
- const NS_HTML = null; // to prevent DOM from requiring NS on everything
139
- const NS_MATHML = 'http://www.w3.org/1998/Math/MathML';
140
- const NS_SVG = 'http://www.w3.org/2000/svg';
141
- const NS_XLINK = 'http://www.w3.org/1999/xlink';
142
- const NS_XML = 'http://www.w3.org/XML/1998/namespace';
143
- const NS_XMLNS = 'http://www.w3.org/2000/xmlns/';
144
-
145
- // Different types of scopes to test for elements
146
- const SCOPE = 0;
147
- const SCOPE_LISTITEM = 1;
148
- const SCOPE_TABLE = 2;
149
-
150
- /**
151
- * HTML5_TreeBuilder constructor.
152
- */
153
- public function __construct() {
154
- $this->mode = self::INITIAL;
155
- $this->dom = new DOMDocument;
156
-
157
- $this->dom->encoding = 'UTF-8';
158
- $this->dom->preserveWhiteSpace = true;
159
- $this->dom->substituteEntities = true;
160
- $this->dom->strictErrorChecking = false;
161
- }
162
-
163
- public function getQuirksMode(){
164
- return $this->quirks_mode;
165
- }
166
-
167
- /**
168
- * Process tag tokens
169
- *
170
- * @param $token
171
- * @param null $mode
172
- */
173
- public function emitToken($token, $mode = null) {
174
- // XXX: ignore parse errors... why are we emitting them, again?
175
- if ($token['type'] === HTML5_Tokenizer::PARSEERROR) {
176
- return;
177
- }
178
- if ($mode === null) {
179
- $mode = $this->mode;
180
- }
181
-
182
- /*
183
- $backtrace = debug_backtrace();
184
- if ($backtrace[1]['class'] !== 'HTML5_TreeBuilder') echo "--\n";
185
- echo $this->strConst($mode);
186
- if ($this->original_mode) echo " (originally ".$this->strConst($this->original_mode).")";
187
- echo "\n ";
188
- token_dump($token);
189
- $this->printStack();
190
- $this->printActiveFormattingElements();
191
- if ($this->foster_parent) echo " -> this is a foster parent mode\n";
192
- if ($this->flag_frameset_ok) echo " -> frameset ok\n";
193
- */
194
-
195
- if ($this->ignore_lf_token) {
196
- $this->ignore_lf_token--;
197
- }
198
- $this->ignored = false;
199
-
200
- switch ($mode) {
201
- case self::INITIAL:
202
-
203
- /* A character token that is one of U+0009 CHARACTER TABULATION,
204
- * U+000A LINE FEED (LF), U+000C FORM FEED (FF), or U+0020 SPACE */
205
- if ($token['type'] === HTML5_Tokenizer::SPACECHARACTER) {
206
- /* Ignore the token. */
207
- $this->ignored = true;
208
- } elseif ($token['type'] === HTML5_Tokenizer::DOCTYPE) {
209
- if (
210
- $token['name'] !== 'html' || !empty($token['public']) ||
211
- !empty($token['system']) || $token !== 'about:legacy-compat'
212
- ) {
213
- /* If the DOCTYPE token's name is not a case-sensitive match
214
- * for the string "html", or if the token's public identifier
215
- * is not missing, or if the token's system identifier is
216
- * neither missing nor a case-sensitive match for the string
217
- * "about:legacy-compat", then there is a parse error (this
218
- * is the DOCTYPE parse error). */
219
- // DOCTYPE parse error
220
- }
221
- /* Append a DocumentType node to the Document node, with the name
222
- * attribute set to the name given in the DOCTYPE token, or the
223
- * empty string if the name was missing; the publicId attribute
224
- * set to the public identifier given in the DOCTYPE token, or
225
- * the empty string if the public identifier was missing; the
226
- * systemId attribute set to the system identifier given in the
227
- * DOCTYPE token, or the empty string if the system identifier
228
- * was missing; and the other attributes specific to
229
- * DocumentType objects set to null and empty lists as
230
- * appropriate. Associate the DocumentType node with the
231
- * Document object so that it is returned as the value of the
232
- * doctype attribute of the Document object. */
233
- if (!isset($token['public'])) {
234
- $token['public'] = "";
235
- }
236
- if (!isset($token['system'])) {
237
- $token['system'] = "";
238
- }
239
- // XDOM
240
- // Yes this is hacky. I'm kind of annoyed that I can't appendChild
241
- // a doctype to DOMDocument. Maybe I haven't chanted the right
242
- // syllables.
243
- $impl = new DOMImplementation();
244
- // This call can fail for particularly pathological cases (namely,
245
- // the qualifiedName parameter ($token['name']) could be missing.
246
- if ($token['name']) {
247
- $doctype = $impl->createDocumentType($token['name'], $token['public'], $token['system']);
248
- $this->dom->appendChild($doctype);
249
- } else {
250
- // It looks like libxml's not actually *able* to express this case.
251
- // So... don't.
252
- $this->dom->emptyDoctype = true;
253
- }
254
- $public = strtolower($token['public']);
255
- $system = $token['system'] === "" ? false : strtolower($token['system']);
256
- $publicStartsWithForQuirks = [
257
- "+//silmaril//dtd html pro v0r11 19970101//",
258
- "-//advasoft ltd//dtd html 3.0 aswedit + extensions//",
259
- "-//as//dtd html 3.0 aswedit + extensions//",
260
- "-//ietf//dtd html 2.0 level 1//",
261
- "-//ietf//dtd html 2.0 level 2//",
262
- "-//ietf//dtd html 2.0 strict level 1//",
263
- "-//ietf//dtd html 2.0 strict level 2//",
264
- "-//ietf//dtd html 2.0 strict//",
265
- "-//ietf//dtd html 2.0//",
266
- "-//ietf//dtd html 2.1e//",
267
- "-//ietf//dtd html 3.0//",
268
- "-//ietf//dtd html 3.2 final//",
269
- "-//ietf//dtd html 3.2//",
270
- "-//ietf//dtd html 3//",
271
- "-//ietf//dtd html level 0//",
272
- "-//ietf//dtd html level 1//",
273
- "-//ietf//dtd html level 2//",
274
- "-//ietf//dtd html level 3//",
275
- "-//ietf//dtd html strict level 0//",
276
- "-//ietf//dtd html strict level 1//",
277
- "-//ietf//dtd html strict level 2//",
278
- "-//ietf//dtd html strict level 3//",
279
- "-//ietf//dtd html strict//",
280
- "-//ietf//dtd html//",
281
- "-//metrius//dtd metrius presentational//",
282
- "-//microsoft//dtd internet explorer 2.0 html strict//",
283
- "-//microsoft//dtd internet explorer 2.0 html//",
284
- "-//microsoft//dtd internet explorer 2.0 tables//",
285
- "-//microsoft//dtd internet explorer 3.0 html strict//",
286
- "-//microsoft//dtd internet explorer 3.0 html//",
287
- "-//microsoft//dtd internet explorer 3.0 tables//",
288
- "-//netscape comm. corp.//dtd html//",
289
- "-//netscape comm. corp.//dtd strict html//",
290
- "-//o'reilly and associates//dtd html 2.0//",
291
- "-//o'reilly and associates//dtd html extended 1.0//",
292
- "-//o'reilly and associates//dtd html extended relaxed 1.0//",
293
- "-//spyglass//dtd html 2.0 extended//",
294
- "-//sq//dtd html 2.0 hotmetal + extensions//",
295
- "-//sun microsystems corp.//dtd hotjava html//",
296
- "-//sun microsystems corp.//dtd hotjava strict html//",
297
- "-//w3c//dtd html 3 1995-03-24//",
298
- "-//w3c//dtd html 3.2 draft//",
299
- "-//w3c//dtd html 3.2 final//",
300
- "-//w3c//dtd html 3.2//",
301
- "-//w3c//dtd html 3.2s draft//",
302
- "-//w3c//dtd html 4.0 frameset//",
303
- "-//w3c//dtd html 4.0 transitional//",
304
- "-//w3c//dtd html experimental 19960712//",
305
- "-//w3c//dtd html experimental 970421//",
306
- "-//w3c//dtd w3 html//",
307
- "-//w3o//dtd w3 html 3.0//",
308
- "-//webtechs//dtd mozilla html 2.0//",
309
- "-//webtechs//dtd mozilla html//",
310
- ];
311
- $publicSetToForQuirks = [
312
- "-//w3o//dtd w3 html strict 3.0//",
313
- "-/w3c/dtd html 4.0 transitional/en",
314
- "html",
315
- ];
316
- $publicStartsWithAndSystemForQuirks = [
317
- "-//w3c//dtd html 4.01 frameset//",
318
- "-//w3c//dtd html 4.01 transitional//",
319
- ];
320
- $publicStartsWithForLimitedQuirks = [
321
- "-//w3c//dtd xhtml 1.0 frameset//",
322
- "-//w3c//dtd xhtml 1.0 transitional//",
323
- ];
324
- $publicStartsWithAndSystemForLimitedQuirks = [
325
- "-//w3c//dtd html 4.01 frameset//",
326
- "-//w3c//dtd html 4.01 transitional//",
327
- ];
328
- // first, do easy checks
329
- if (
330
- !empty($token['force-quirks']) ||
331
- strtolower($token['name']) !== 'html'
332
- ) {
333
- $this->quirks_mode = self::QUIRKS_MODE;
334
- } else {
335
- do {
336
- if ($system) {
337
- foreach ($publicStartsWithAndSystemForQuirks as $x) {
338
- if (strncmp($public, $x, strlen($x)) === 0) {
339
- $this->quirks_mode = self::QUIRKS_MODE;
340
- break;
341
- }
342
- }
343
- if (!is_null($this->quirks_mode)) {
344
- break;
345
- }
346
- foreach ($publicStartsWithAndSystemForLimitedQuirks as $x) {
347
- if (strncmp($public, $x, strlen($x)) === 0) {
348
- $this->quirks_mode = self::LIMITED_QUIRKS_MODE;
349
- break;
350
- }
351
- }
352
- if (!is_null($this->quirks_mode)) {
353
- break;
354
- }
355
- }
356
- foreach ($publicSetToForQuirks as $x) {
357
- if ($public === $x) {
358
- $this->quirks_mode = self::QUIRKS_MODE;
359
- break;
360
- }
361
- }
362
- if (!is_null($this->quirks_mode)) {
363
- break;
364
- }
365
- foreach ($publicStartsWithForLimitedQuirks as $x) {
366
- if (strncmp($public, $x, strlen($x)) === 0) {
367
- $this->quirks_mode = self::LIMITED_QUIRKS_MODE;
368
- }
369
- }
370
- if (!is_null($this->quirks_mode)) {
371
- break;
372
- }
373
- if ($system === "http://www.ibm.com/data/dtd/v11/ibmxhtml1-transitional.dtd") {
374
- $this->quirks_mode = self::QUIRKS_MODE;
375
- break;
376
- }
377
- foreach ($publicStartsWithForQuirks as $x) {
378
- if (strncmp($public, $x, strlen($x)) === 0) {
379
- $this->quirks_mode = self::QUIRKS_MODE;
380
- break;
381
- }
382
- }
383
- if (is_null($this->quirks_mode)) {
384
- $this->quirks_mode = self::NO_QUIRKS;
385
- }
386
- } while (false);
387
- }
388
- $this->mode = self::BEFORE_HTML;
389
- } else {
390
- // parse error
391
- /* Switch the insertion mode to "before html", then reprocess the
392
- * current token. */
393
- $this->mode = self::BEFORE_HTML;
394
- $this->quirks_mode = self::QUIRKS_MODE;
395
- $this->emitToken($token);
396
- }
397
- break;
398
-
399
- case self::BEFORE_HTML:
400
- /* A DOCTYPE token */
401
- if ($token['type'] === HTML5_Tokenizer::DOCTYPE) {
402
- // Parse error. Ignore the token.
403
- $this->ignored = true;
404
-
405
- /* A comment token */
406
- } elseif ($token['type'] === HTML5_Tokenizer::COMMENT) {
407
- /* Append a Comment node to the Document object with the data
408
- attribute set to the data given in the comment token. */
409
- // XDOM
410
- $comment = $this->dom->createComment($token['data']);
411
- $this->dom->appendChild($comment);
412
-
413
- /* A character token that is one of one of U+0009 CHARACTER TABULATION,
414
- U+000A LINE FEED (LF), U+000B LINE TABULATION, U+000C FORM FEED (FF),
415
- or U+0020 SPACE */
416
- } elseif ($token['type'] === HTML5_Tokenizer::SPACECHARACTER) {
417
- /* Ignore the token. */
418
- $this->ignored = true;
419
-
420
- /* A start tag whose tag name is "html" */
421
- } elseif ($token['type'] === HTML5_Tokenizer::STARTTAG && $token['name'] == 'html') {
422
- /* Create an element for the token in the HTML namespace. Append it
423
- * to the Document object. Put this element in the stack of open
424
- * elements. */
425
- // XDOM
426
- $html = $this->insertElement($token, false);
427
- $this->dom->appendChild($html);
428
- $this->stack[] = $html;
429
-
430
- $this->mode = self::BEFORE_HEAD;
431
-
432
- } else {
433
- /* Create an html element. Append it to the Document object. Put
434
- * this element in the stack of open elements. */
435
- // XDOM
436
- $html = $this->dom->createElementNS(self::NS_HTML, 'html');
437
- $this->dom->appendChild($html);
438
- $this->stack[] = $html;
439
-
440
- /* Switch the insertion mode to "before head", then reprocess the
441
- * current token. */
442
- $this->mode = self::BEFORE_HEAD;
443
- $this->emitToken($token);
444
- }
445
- break;
446
-
447
- case self::BEFORE_HEAD:
448
- /* A character token that is one of one of U+0009 CHARACTER TABULATION,
449
- U+000A LINE FEED (LF), U+000B LINE TABULATION, U+000C FORM FEED (FF),
450
- or U+0020 SPACE */
451
- if ($token['type'] === HTML5_Tokenizer::SPACECHARACTER) {
452
- /* Ignore the token. */
453
- $this->ignored = true;
454
-
455
- /* A comment token */
456
- } elseif ($token['type'] === HTML5_Tokenizer::COMMENT) {
457
- /* Append a Comment node to the current node with the data attribute
458
- set to the data given in the comment token. */
459
- $this->insertComment($token['data']);
460
-
461
- /* A DOCTYPE token */
462
- } elseif ($token['type'] === HTML5_Tokenizer::DOCTYPE) {
463
- /* Parse error. Ignore the token */
464
- $this->ignored = true;
465
- // parse error
466
-
467
- /* A start tag token with the tag name "html" */
468
- } elseif ($token['type'] === HTML5_Tokenizer::STARTTAG && $token['name'] === 'html') {
469
- /* Process the token using the rules for the "in body"
470
- * insertion mode. */
471
- $this->processWithRulesFor($token, self::IN_BODY);
472
-
473
- /* A start tag token with the tag name "head" */
474
- } elseif ($token['type'] === HTML5_Tokenizer::STARTTAG && $token['name'] === 'head') {
475
- /* Insert an HTML element for the token. */
476
- $element = $this->insertElement($token);
477
-
478
- /* Set the head element pointer to this new element node. */
479
- $this->head_pointer = $element;
480
-
481
- /* Change the insertion mode to "in head". */
482
- $this->mode = self::IN_HEAD;
483
-
484
- /* An end tag whose tag name is one of: "head", "body", "html", "br" */
485
- } elseif (
486
- $token['type'] === HTML5_Tokenizer::ENDTAG && (
487
- $token['name'] === 'head' || $token['name'] === 'body' ||
488
- $token['name'] === 'html' || $token['name'] === 'br'
489
- )) {
490
- /* Act as if a start tag token with the tag name "head" and no
491
- * attributes had been seen, then reprocess the current token. */
492
- $this->emitToken([
493
- 'name' => 'head',
494
- 'type' => HTML5_Tokenizer::STARTTAG,
495
- 'attr' => []
496
- ]);
497
- $this->emitToken($token);
498
-
499
- /* Any other end tag */
500
- } elseif ($token['type'] === HTML5_Tokenizer::ENDTAG) {
501
- /* Parse error. Ignore the token. */
502
- $this->ignored = true;
503
-
504
- } else {
505
- /* Act as if a start tag token with the tag name "head" and no
506
- * attributes had been seen, then reprocess the current token.
507
- * Note: This will result in an empty head element being
508
- * generated, with the current token being reprocessed in the
509
- * "after head" insertion mode. */
510
- $this->emitToken([
511
- 'name' => 'head',
512
- 'type' => HTML5_Tokenizer::STARTTAG,
513
- 'attr' => []
514
- ]);
515
- $this->emitToken($token);
516
- }
517
- break;
518
-
519
- case self::IN_HEAD:
520
- /* A character token that is one of one of U+0009 CHARACTER TABULATION,
521
- U+000A LINE FEED (LF), U+000B LINE TABULATION, U+000C FORM FEED (FF),
522
- or U+0020 SPACE. */
523
- if ($token['type'] === HTML5_Tokenizer::SPACECHARACTER) {
524
- /* Insert the character into the current node. */
525
- $this->insertText($token['data']);
526
-
527
- /* A comment token */
528
- } elseif ($token['type'] === HTML5_Tokenizer::COMMENT) {
529
- /* Append a Comment node to the current node with the data attribute
530
- set to the data given in the comment token. */
531
- $this->insertComment($token['data']);
532
-
533
- /* A DOCTYPE token */
534
- } elseif ($token['type'] === HTML5_Tokenizer::DOCTYPE) {
535
- /* Parse error. Ignore the token. */
536
- $this->ignored = true;
537
- // parse error
538
-
539
- /* A start tag whose tag name is "html" */
540
- } elseif ($token['type'] === HTML5_Tokenizer::STARTTAG &&
541
- $token['name'] === 'html') {
542
- $this->processWithRulesFor($token, self::IN_BODY);
543
-
544
- /* A start tag whose tag name is one of: "base", "command", "link" */
545
- } elseif ($token['type'] === HTML5_Tokenizer::STARTTAG &&
546
- ($token['name'] === 'base' || $token['name'] === 'command' ||
547
- $token['name'] === 'link')) {
548
- /* Insert an HTML element for the token. Immediately pop the
549
- * current node off the stack of open elements. */
550
- $this->insertElement($token);
551
- array_pop($this->stack);
552
-
553
- // YYY: Acknowledge the token's self-closing flag, if it is set.
554
-
555
- /* A start tag whose tag name is "meta" */
556
- } elseif ($token['type'] === HTML5_Tokenizer::STARTTAG && $token['name'] === 'meta') {
557
- /* Insert an HTML element for the token. Immediately pop the
558
- * current node off the stack of open elements. */
559
- $this->insertElement($token);
560
- array_pop($this->stack);
561
-
562
- // XERROR: Acknowledge the token's self-closing flag, if it is set.
563
-
564
- // XENCODING: If the element has a charset attribute, and its value is a
565
- // supported encoding, and the confidence is currently tentative,
566
- // then change the encoding to the encoding given by the value of
567
- // the charset attribute.
568
- //
569
- // Otherwise, if the element has a content attribute, and applying
570
- // the algorithm for extracting an encoding from a Content-Type to
571
- // its value returns a supported encoding encoding, and the
572
- // confidence is currently tentative, then change the encoding to
573
- // the encoding encoding.
574
-
575
- /* A start tag with the tag name "title" */
576
- } elseif ($token['type'] === HTML5_Tokenizer::STARTTAG && $token['name'] === 'title') {
577
- $this->insertRCDATAElement($token);
578
-
579
- /* A start tag whose tag name is "noscript", if the scripting flag is enabled, or
580
- * A start tag whose tag name is one of: "noframes", "style" */
581
- } elseif ($token['type'] === HTML5_Tokenizer::STARTTAG &&
582
- ($token['name'] === 'noscript' || $token['name'] === 'noframes' || $token['name'] === 'style')) {
583
- // XSCRIPT: Scripting flag not respected
584
- $this->insertCDATAElement($token);
585
-
586
- // XSCRIPT: Scripting flag disable not implemented
587
-
588
- /* A start tag with the tag name "script" */
589
- } elseif ($token['type'] === HTML5_Tokenizer::STARTTAG && $token['name'] === 'script') {
590
- /* 1. Create an element for the token in the HTML namespace. */
591
- $node = $this->insertElement($token, false);
592
-
593
- /* 2. Mark the element as being "parser-inserted" */
594
- // Uhhh... XSCRIPT
595
-
596
- /* 3. If the parser was originally created for the HTML
597
- * fragment parsing algorithm, then mark the script element as
598
- * "already executed". (fragment case) */
599
- // ditto... XSCRIPT
600
-
601
- /* 4. Append the new element to the current node and push it onto
602
- * the stack of open elements. */
603
- end($this->stack)->appendChild($node);
604
- $this->stack[] = $node;
605
- // I guess we could squash these together
606
-
607
- /* 6. Let the original insertion mode be the current insertion mode. */
608
- $this->original_mode = $this->mode;
609
- /* 7. Switch the insertion mode to "in CDATA/RCDATA" */
610
- $this->mode = self::IN_CDATA_RCDATA;
611
- /* 5. Switch the tokeniser's content model flag to the CDATA state. */
612
- $this->content_model = HTML5_Tokenizer::CDATA;
613
-
614
- /* An end tag with the tag name "head" */
615
- } elseif ($token['type'] === HTML5_Tokenizer::ENDTAG && $token['name'] === 'head') {
616
- /* Pop the current node (which will be the head element) off the stack of open elements. */
617
- array_pop($this->stack);
618
-
619
- /* Change the insertion mode to "after head". */
620
- $this->mode = self::AFTER_HEAD;
621
-
622
- // Slight logic inversion here to minimize duplication
623
- /* A start tag with the tag name "head". */
624
- /* An end tag whose tag name is not one of: "body", "html", "br" */
625
- } elseif (($token['type'] === HTML5_Tokenizer::STARTTAG && $token['name'] === 'head') ||
626
- ($token['type'] === HTML5_Tokenizer::ENDTAG && $token['name'] !== 'html' &&
627
- $token['name'] !== 'body' && $token['name'] !== 'br')) {
628
- // Parse error. Ignore the token.
629
- $this->ignored = true;
630
-
631
- /* Anything else */
632
- } else {
633
- /* Act as if an end tag token with the tag name "head" had been
634
- * seen, and reprocess the current token. */
635
- $this->emitToken([
636
- 'name' => 'head',
637
- 'type' => HTML5_Tokenizer::ENDTAG
638
- ]);
639
-
640
- /* Then, reprocess the current token. */
641
- $this->emitToken($token);
642
- }
643
- break;
644
-
645
- case self::IN_HEAD_NOSCRIPT:
646
- if ($token['type'] === HTML5_Tokenizer::DOCTYPE) {
647
- // parse error
648
- } elseif ($token['type'] === HTML5_Tokenizer::STARTTAG && $token['name'] === 'html') {
649
- $this->processWithRulesFor($token, self::IN_BODY);
650
- } elseif ($token['type'] === HTML5_Tokenizer::ENDTAG && $token['name'] === 'noscript') {
651
- /* Pop the current node (which will be a noscript element) from the
652
- * stack of open elements; the new current node will be a head
653
- * element. */
654
- array_pop($this->stack);
655
- $this->mode = self::IN_HEAD;
656
- } elseif (
657
- ($token['type'] === HTML5_Tokenizer::SPACECHARACTER) ||
658
- ($token['type'] === HTML5_Tokenizer::COMMENT) ||
659
- ($token['type'] === HTML5_Tokenizer::STARTTAG && (
660
- $token['name'] === 'link' || $token['name'] === 'meta' ||
661
- $token['name'] === 'noframes' || $token['name'] === 'style'))) {
662
- $this->processWithRulesFor($token, self::IN_HEAD);
663
- // inverted logic
664
- } elseif (
665
- ($token['type'] === HTML5_Tokenizer::STARTTAG && (
666
- $token['name'] === 'head' || $token['name'] === 'noscript')) ||
667
- ($token['type'] === HTML5_Tokenizer::ENDTAG &&
668
- $token['name'] !== 'br')) {
669
- // parse error
670
- } else {
671
- // parse error
672
- $this->emitToken([
673
- 'type' => HTML5_Tokenizer::ENDTAG,
674
- 'name' => 'noscript',
675
- ]);
676
- $this->emitToken($token);
677
- }
678
- break;
679
-
680
- case self::AFTER_HEAD:
681
- /* Handle the token as follows: */
682
-
683
- /* A character token that is one of one of U+0009 CHARACTER TABULATION,
684
- U+000A LINE FEED (LF), U+000B LINE TABULATION, U+000C FORM FEED (FF),
685
- or U+0020 SPACE */
686
- if ($token['type'] === HTML5_Tokenizer::SPACECHARACTER) {
687
- /* Append the character to the current node. */
688
- $this->insertText($token['data']);
689
-
690
- /* A comment token */
691
- } elseif ($token['type'] === HTML5_Tokenizer::COMMENT) {
692
- /* Append a Comment node to the current node with the data attribute
693
- set to the data given in the comment token. */
694
- $this->insertComment($token['data']);
695
-
696
- } elseif ($token['type'] === HTML5_Tokenizer::DOCTYPE) {
697
- // parse error
698
-
699
- } elseif ($token['type'] === HTML5_Tokenizer::STARTTAG && $token['name'] === 'html') {
700
- $this->processWithRulesFor($token, self::IN_BODY);
701
-
702
- /* A start tag token with the tag name "body" */
703
- } elseif ($token['type'] === HTML5_Tokenizer::STARTTAG && $token['name'] === 'body') {
704
- $this->insertElement($token);
705
-
706
- /* Set the frameset-ok flag to "not ok". */
707
- $this->flag_frameset_ok = false;
708
-
709
- /* Change the insertion mode to "in body". */
710
- $this->mode = self::IN_BODY;
711
-
712
- /* A start tag token with the tag name "frameset" */
713
- } elseif ($token['type'] === HTML5_Tokenizer::STARTTAG && $token['name'] === 'frameset') {
714
- /* Insert a frameset element for the token. */
715
- $this->insertElement($token);
716
-
717
- /* Change the insertion mode to "in frameset". */
718
- $this->mode = self::IN_FRAMESET;
719
-
720
- /* A start tag token whose tag name is one of: "base", "link", "meta",
721
- "script", "style", "title" */
722
- } elseif ($token['type'] === HTML5_Tokenizer::STARTTAG && in_array($token['name'],
723
- ['base', 'link', 'meta', 'noframes', 'script', 'style', 'title'])) {
724
- // parse error
725
- /* Push the node pointed to by the head element pointer onto the
726
- * stack of open elements. */
727
- $this->stack[] = $this->head_pointer;
728
- $this->processWithRulesFor($token, self::IN_HEAD);
729
- array_splice($this->stack, array_search($this->head_pointer, $this->stack, true), 1);
730
-
731
- // inversion of specification
732
- } elseif (
733
- ($token['type'] === HTML5_Tokenizer::STARTTAG && $token['name'] === 'head') ||
734
- ($token['type'] === HTML5_Tokenizer::ENDTAG &&
735
- $token['name'] !== 'body' && $token['name'] !== 'html' &&
736
- $token['name'] !== 'br')) {
737
- // parse error
738
-
739
- /* Anything else */
740
- } else {
741
- $this->emitToken([
742
- 'name' => 'body',
743
- 'type' => HTML5_Tokenizer::STARTTAG,
744
- 'attr' => []
745
- ]);
746
- $this->flag_frameset_ok = true;
747
- $this->emitToken($token);
748
- }
749
- break;
750
-
751
- case self::IN_BODY:
752
- /* Handle the token as follows: */
753
-
754
- switch($token['type']) {
755
- /* A character token */
756
- case HTML5_Tokenizer::CHARACTER:
757
- case HTML5_Tokenizer::SPACECHARACTER:
758
- /* Reconstruct the active formatting elements, if any. */
759
- $this->reconstructActiveFormattingElements();
760
-
761
- /* Append the token's character to the current node. */
762
- $this->insertText($token['data']);
763
-
764
- /* If the token is not one of U+0009 CHARACTER TABULATION,
765
- * U+000A LINE FEED (LF), U+000C FORM FEED (FF), or U+0020
766
- * SPACE, then set the frameset-ok flag to "not ok". */
767
- // i.e., if any of the characters is not whitespace
768
- if (strlen($token['data']) !== strspn($token['data'], HTML5_Tokenizer::WHITESPACE)) {
769
- $this->flag_frameset_ok = false;
770
- }
771
- break;
772
-
773
- /* A comment token */
774
- case HTML5_Tokenizer::COMMENT:
775
- /* Append a Comment node to the current node with the data
776
- attribute set to the data given in the comment token. */
777
- $this->insertComment($token['data']);
778
- break;
779
-
780
- case HTML5_Tokenizer::DOCTYPE:
781
- // parse error
782
- break;
783
-
784
- case HTML5_Tokenizer::EOF:
785
- // parse error
786
- break;
787
-
788
- case HTML5_Tokenizer::STARTTAG:
789
- switch($token['name']) {
790
- case 'html':
791
- // parse error
792
- /* For each attribute on the token, check to see if the
793
- * attribute is already present on the top element of the
794
- * stack of open elements. If it is not, add the attribute
795
- * and its corresponding value to that element. */
796
- foreach($token['attr'] as $attr) {
797
- if (!$this->stack[0]->hasAttribute($attr['name'])) {
798
- $this->stack[0]->setAttribute($attr['name'], $attr['value']);
799
- }
800
- }
801
- break;
802
-
803
- case 'base': case 'command': case 'link': case 'meta': case 'noframes':
804
- case 'script': case 'style': case 'title':
805
- /* Process the token as if the insertion mode had been "in
806
- head". */
807
- $this->processWithRulesFor($token, self::IN_HEAD);
808
- break;
809
-
810
- /* A start tag token with the tag name "body" */
811
- case 'body':
812
- /* Parse error. If the second element on the stack of open
813
- elements is not a body element, or, if the stack of open
814
- elements has only one node on it, then ignore the token.
815
- (fragment case) */
816
- if (count($this->stack) === 1 || $this->stack[1]->tagName !== 'body') {
817
- $this->ignored = true;
818
- // Ignore
819
-
820
- /* Otherwise, for each attribute on the token, check to see
821
- if the attribute is already present on the body element (the
822
- second element) on the stack of open elements. If it is not,
823
- add the attribute and its corresponding value to that
824
- element. */
825
- } else {
826
- foreach($token['attr'] as $attr) {
827
- if (!$this->stack[1]->hasAttribute($attr['name'])) {
828
- $this->stack[1]->setAttribute($attr['name'], $attr['value']);
829
- }
830
- }
831
- }
832
- break;
833
-
834
- case 'frameset':
835
- // parse error
836
- /* If the second element on the stack of open elements is
837
- * not a body element, or, if the stack of open elements
838
- * has only one node on it, then ignore the token.
839
- * (fragment case) */
840
- if (count($this->stack) === 1 || $this->stack[1]->tagName !== 'body') {
841
- $this->ignored = true;
842
- // Ignore
843
- } elseif (!$this->flag_frameset_ok) {
844
- $this->ignored = true;
845
- // Ignore
846
- } else {
847
- /* 1. Remove the second element on the stack of open
848
- * elements from its parent node, if it has one. */
849
- if ($this->stack[1]->parentNode) {
850
- $this->stack[1]->parentNode->removeChild($this->stack[1]);
851
- }
852
-
853
- /* 2. Pop all the nodes from the bottom of the stack of
854
- * open elements, from the current node up to the root
855
- * html element. */
856
- array_splice($this->stack, 1);
857
-
858
- $this->insertElement($token);
859
- $this->mode = self::IN_FRAMESET;
860
- }
861
- break;
862
-
863
- // in spec, there is a diversion here
864
-
865
- case 'address': case 'article': case 'aside': case 'blockquote':
866
- case 'center': case 'datagrid': case 'details': case 'dir':
867
- case 'div': case 'dl': case 'fieldset': case 'figure': case 'footer':
868
- case 'header': case 'hgroup': case 'menu': case 'nav':
869
- case 'ol': case 'p': case 'section': case 'ul':
870
- /* If the stack of open elements has a p element in scope,
871
- then act as if an end tag with the tag name p had been
872
- seen. */
873
- if ($this->elementInScope('p')) {
874
- $this->emitToken([
875
- 'name' => 'p',
876
- 'type' => HTML5_Tokenizer::ENDTAG
877
- ]);
878
- }
879
-
880
- /* Insert an HTML element for the token. */
881
- $this->insertElement($token);
882
- break;
883
-
884
- /* A start tag whose tag name is one of: "h1", "h2", "h3", "h4",
885
- "h5", "h6" */
886
- case 'h1': case 'h2': case 'h3': case 'h4': case 'h5': case 'h6':
887
- /* If the stack of open elements has a p element in scope,
888
- then act as if an end tag with the tag name p had been seen. */
889
- if ($this->elementInScope('p')) {
890
- $this->emitToken([
891
- 'name' => 'p',
892
- 'type' => HTML5_Tokenizer::ENDTAG
893
- ]);
894
- }
895
-
896
- /* If the current node is an element whose tag name is one
897
- * of "h1", "h2", "h3", "h4", "h5", or "h6", then this is a
898
- * parse error; pop the current node off the stack of open
899
- * elements. */
900
- $peek = array_pop($this->stack);
901
- if (in_array($peek->tagName, ["h1", "h2", "h3", "h4", "h5", "h6"])) {
902
- // parse error
903
- } else {
904
- $this->stack[] = $peek;
905
- }
906
-
907
- /* Insert an HTML element for the token. */
908
- $this->insertElement($token);
909
- break;
910
-
911
- case 'pre': case 'listing':
912
- /* If the stack of open elements has a p element in scope,
913
- then act as if an end tag with the tag name p had been seen. */
914
- if ($this->elementInScope('p')) {
915
- $this->emitToken([
916
- 'name' => 'p',
917
- 'type' => HTML5_Tokenizer::ENDTAG
918
- ]);
919
- }
920
- $this->insertElement($token);
921
- /* If the next token is a U+000A LINE FEED (LF) character
922
- * token, then ignore that token and move on to the next
923
- * one. (Newlines at the start of pre blocks are ignored as
924
- * an authoring convenience.) */
925
- $this->ignore_lf_token = 2;
926
- $this->flag_frameset_ok = false;
927
- break;
928
-
929
- /* A start tag whose tag name is "form" */
930
- case 'form':
931
- /* If the form element pointer is not null, ignore the
932
- token with a parse error. */
933
- if ($this->form_pointer !== null) {
934
- $this->ignored = true;
935
- // Ignore.
936
-
937
- /* Otherwise: */
938
- } else {
939
- /* If the stack of open elements has a p element in
940
- scope, then act as if an end tag with the tag name p
941
- had been seen. */
942
- if ($this->elementInScope('p')) {
943
- $this->emitToken([
944
- 'name' => 'p',
945
- 'type' => HTML5_Tokenizer::ENDTAG
946
- ]);
947
- }
948
-
949
- /* Insert an HTML element for the token, and set the
950
- form element pointer to point to the element created. */
951
- $element = $this->insertElement($token);
952
- $this->form_pointer = $element;
953
- }
954
- break;
955
-
956
- // condensed specification
957
- case 'li': case 'dc': case 'dd': case 'ds': case 'dt':
958
- /* 1. Set the frameset-ok flag to "not ok". */
959
- $this->flag_frameset_ok = false;
960
-
961
- $stack_length = count($this->stack) - 1;
962
- for($n = $stack_length; 0 <= $n; $n--) {
963
- /* 2. Initialise node to be the current node (the
964
- bottommost node of the stack). */
965
- $stop = false;
966
- $node = $this->stack[$n];
967
- $cat = $this->getElementCategory($node);
968
-
969
- // for case 'li':
970
- /* 3. If node is an li element, then act as if an end
971
- * tag with the tag name "li" had been seen, then jump
972
- * to the last step. */
973
- // for case 'dc': case 'dd': case 'ds': case 'dt':
974
- /* If node is a dc, dd, ds or dt element, then act as if an end
975
- * tag with the same tag name as node had been seen, then
976
- * jump to the last step. */
977
- if (($token['name'] === 'li' && $node->tagName === 'li') ||
978
- ($token['name'] !== 'li' && ($node->tagName == 'dc' || $node->tagName === 'dd' || $node->tagName == 'ds' || $node->tagName === 'dt'))) { // limited conditional
979
- $this->emitToken([
980
- 'type' => HTML5_Tokenizer::ENDTAG,
981
- 'name' => $node->tagName,
982
- ]);
983
- break;
984
- }
985
-
986
- /* 4. If node is not in the formatting category, and is
987
- not in the phrasing category, and is not an address,
988
- div or p element, then stop this algorithm. */
989
- if ($cat !== self::FORMATTING && $cat !== self::PHRASING &&
990
- $node->tagName !== 'address' && $node->tagName !== 'div' &&
991
- $node->tagName !== 'p') {
992
- break;
993
- }
994
-
995
- /* 5. Otherwise, set node to the previous entry in the
996
- * stack of open elements and return to step 2. */
997
- }
998
-
999
- /* 6. This is the last step. */
1000
-
1001
- /* If the stack of open elements has a p element in scope,
1002
- then act as if an end tag with the tag name p had been
1003
- seen. */
1004
- if ($this->elementInScope('p')) {
1005
- $this->emitToken([
1006
- 'name' => 'p',
1007
- 'type' => HTML5_Tokenizer::ENDTAG
1008
- ]);
1009
- }
1010
-
1011
- /* Finally, insert an HTML element with the same tag
1012
- name as the token's. */
1013
- $this->insertElement($token);
1014
- break;
1015
-
1016
- /* A start tag token whose tag name is "plaintext" */
1017
- case 'plaintext':
1018
- /* If the stack of open elements has a p element in scope,
1019
- then act as if an end tag with the tag name p had been
1020
- seen. */
1021
- if ($this->elementInScope('p')) {
1022
- $this->emitToken([
1023
- 'name' => 'p',
1024
- 'type' => HTML5_Tokenizer::ENDTAG
1025
- ]);
1026
- }
1027
-
1028
- /* Insert an HTML element for the token. */
1029
- $this->insertElement($token);
1030
-
1031
- $this->content_model = HTML5_Tokenizer::PLAINTEXT;
1032
- break;
1033
-
1034
- // more diversions
1035
-
1036
- /* A start tag whose tag name is "a" */
1037
- case 'a':
1038
- /* If the list of active formatting elements contains
1039
- an element whose tag name is "a" between the end of the
1040
- list and the last marker on the list (or the start of
1041
- the list if there is no marker on the list), then this
1042
- is a parse error; act as if an end tag with the tag name
1043
- "a" had been seen, then remove that element from the list
1044
- of active formatting elements and the stack of open
1045
- elements if the end tag didn't already remove it (it
1046
- might not have if the element is not in table scope). */
1047
- $leng = count($this->a_formatting);
1048
-
1049
- for ($n = $leng - 1; $n >= 0; $n--) {
1050
- if ($this->a_formatting[$n] === self::MARKER) {
1051
- break;
1052
-
1053
- } elseif ($this->a_formatting[$n]->tagName === 'a') {
1054
- $a = $this->a_formatting[$n];
1055
- $this->emitToken([
1056
- 'name' => 'a',
1057
- 'type' => HTML5_Tokenizer::ENDTAG
1058
- ]);
1059
- if (in_array($a, $this->a_formatting)) {
1060
- $a_i = array_search($a, $this->a_formatting, true);
1061
- if ($a_i !== false) {
1062
- array_splice($this->a_formatting, $a_i, 1);
1063
- }
1064
- }
1065
- if (in_array($a, $this->stack)) {
1066
- $a_i = array_search($a, $this->stack, true);
1067
- if ($a_i !== false) {
1068
- array_splice($this->stack, $a_i, 1);
1069
- }
1070
- }
1071
- break;
1072
- }
1073
- }
1074
-
1075
- /* Reconstruct the active formatting elements, if any. */
1076
- $this->reconstructActiveFormattingElements();
1077
-
1078
- /* Insert an HTML element for the token. */
1079
- $el = $this->insertElement($token);
1080
-
1081
- /* Add that element to the list of active formatting
1082
- elements. */
1083
- $this->a_formatting[] = $el;
1084
- break;
1085
-
1086
- case 'b': case 'big': case 'code': case 'em': case 'font': case 'i':
1087
- case 's': case 'small': case 'strike':
1088
- case 'strong': case 'tt': case 'u':
1089
- /* Reconstruct the active formatting elements, if any. */
1090
- $this->reconstructActiveFormattingElements();
1091
-
1092
- /* Insert an HTML element for the token. */
1093
- $el = $this->insertElement($token);
1094
-
1095
- /* Add that element to the list of active formatting
1096
- elements. */
1097
- $this->a_formatting[] = $el;
1098
- break;
1099
-
1100
- case 'nobr':
1101
- /* Reconstruct the active formatting elements, if any. */
1102
- $this->reconstructActiveFormattingElements();
1103
-
1104
- /* If the stack of open elements has a nobr element in
1105
- * scope, then this is a parse error; act as if an end tag
1106
- * with the tag name "nobr" had been seen, then once again
1107
- * reconstruct the active formatting elements, if any. */
1108
- if ($this->elementInScope('nobr')) {
1109
- $this->emitToken([
1110
- 'name' => 'nobr',
1111
- 'type' => HTML5_Tokenizer::ENDTAG,
1112
- ]);
1113
- $this->reconstructActiveFormattingElements();
1114
- }
1115
-
1116
- /* Insert an HTML element for the token. */
1117
- $el = $this->insertElement($token);
1118
-
1119
- /* Add that element to the list of active formatting
1120
- elements. */
1121
- $this->a_formatting[] = $el;
1122
- break;
1123
-
1124
- // another diversion
1125
-
1126
- /* A start tag token whose tag name is "button" */
1127
- case 'button':
1128
- /* If the stack of open elements has a button element in scope,
1129
- then this is a parse error; act as if an end tag with the tag
1130
- name "button" had been seen, then reprocess the token. (We don't
1131
- do that. Unnecessary.) (I hope you're right! -- ezyang) */
1132
- if ($this->elementInScope('button')) {
1133
- $this->emitToken([
1134
- 'name' => 'button',
1135
- 'type' => HTML5_Tokenizer::ENDTAG
1136
- ]);
1137
- }
1138
-
1139
- /* Reconstruct the active formatting elements, if any. */
1140
- $this->reconstructActiveFormattingElements();
1141
-
1142
- /* Insert an HTML element for the token. */
1143
- $this->insertElement($token);
1144
-
1145
- /* Insert a marker at the end of the list of active
1146
- formatting elements. */
1147
- $this->a_formatting[] = self::MARKER;
1148
-
1149
- $this->flag_frameset_ok = false;
1150
- break;
1151
-
1152
- case 'applet': case 'marquee': case 'object':
1153
- /* Reconstruct the active formatting elements, if any. */
1154
- $this->reconstructActiveFormattingElements();
1155
-
1156
- /* Insert an HTML element for the token. */
1157
- $this->insertElement($token);
1158
-
1159
- /* Insert a marker at the end of the list of active
1160
- formatting elements. */
1161
- $this->a_formatting[] = self::MARKER;
1162
-
1163
- $this->flag_frameset_ok = false;
1164
- break;
1165
-
1166
- // spec diversion
1167
-
1168
- /* A start tag whose tag name is "table" */
1169
- case 'table':
1170
- /* If the Document is not set to quirks mode, and the
1171
- * stack of open elements has a p element in scope, then
1172
- * act as if an end tag with the tag name "p" had been
1173
- * seen. */
1174
- if ($this->quirks_mode !== self::QUIRKS_MODE &&
1175
- $this->elementInScope('p')) {
1176
- $this->emitToken([
1177
- 'name' => 'p',
1178
- 'type' => HTML5_Tokenizer::ENDTAG
1179
- ]);
1180
- }
1181
-
1182
- /* Insert an HTML element for the token. */
1183
- $this->insertElement($token);
1184
-
1185
- $this->flag_frameset_ok = false;
1186
-
1187
- /* Change the insertion mode to "in table". */
1188
- $this->mode = self::IN_TABLE;
1189
- break;
1190
-
1191
- /* A start tag whose tag name is one of: "area", "basefont",
1192
- "bgsound", "br", "embed", "img", "param", "spacer", "wbr" */
1193
- case 'area': case 'basefont': case 'bgsound': case 'br':
1194
- case 'embed': case 'img': case 'input': case 'keygen': case 'spacer':
1195
- case 'wbr':
1196
- /* Reconstruct the active formatting elements, if any. */
1197
- $this->reconstructActiveFormattingElements();
1198
-
1199
- /* Insert an HTML element for the token. */
1200
- $this->insertElement($token);
1201
-
1202
- /* Immediately pop the current node off the stack of open elements. */
1203
- array_pop($this->stack);
1204
-
1205
- // YYY: Acknowledge the token's self-closing flag, if it is set.
1206
-
1207
- $this->flag_frameset_ok = false;
1208
- break;
1209
-
1210
- case 'param': case 'source':
1211
- /* Insert an HTML element for the token. */
1212
- $this->insertElement($token);
1213
-
1214
- /* Immediately pop the current node off the stack of open elements. */
1215
- array_pop($this->stack);
1216
-
1217
- // YYY: Acknowledge the token's self-closing flag, if it is set.
1218
- break;
1219
-
1220
- /* A start tag whose tag name is "hr" */
1221
- case 'hr':
1222
- /* If the stack of open elements has a p element in scope,
1223
- then act as if an end tag with the tag name p had been seen. */
1224
- if ($this->elementInScope('p')) {
1225
- $this->emitToken([
1226
- 'name' => 'p',
1227
- 'type' => HTML5_Tokenizer::ENDTAG
1228
- ]);
1229
- }
1230
-
1231
- /* Insert an HTML element for the token. */
1232
- $this->insertElement($token);
1233
-
1234
- /* Immediately pop the current node off the stack of open elements. */
1235
- array_pop($this->stack);
1236
-
1237
- // YYY: Acknowledge the token's self-closing flag, if it is set.
1238
-
1239
- $this->flag_frameset_ok = false;
1240
- break;
1241
-
1242
- /* A start tag whose tag name is "image" */
1243
- case 'image':
1244
- /* Parse error. Change the token's tag name to "img" and
1245
- reprocess it. (Don't ask.) */
1246
- $token['name'] = 'img';
1247
- $this->emitToken($token);
1248
- break;
1249
-
1250
- /* A start tag whose tag name is "isindex" */
1251
- case 'isindex':
1252
- /* Parse error. */
1253
-
1254
- /* If the form element pointer is not null,
1255
- then ignore the token. */
1256
- if ($this->form_pointer === null) {
1257
- /* Act as if a start tag token with the tag name "form" had
1258
- been seen. */
1259
- /* If the token has an attribute called "action", set
1260
- * the action attribute on the resulting form
1261
- * element to the value of the "action" attribute of
1262
- * the token. */
1263
- $attr = [];
1264
- $action = $this->getAttr($token, 'action');
1265
- if ($action !== false) {
1266
- $attr[] = ['name' => 'action', 'value' => $action];
1267
- }
1268
- $this->emitToken([
1269
- 'name' => 'form',
1270
- 'type' => HTML5_Tokenizer::STARTTAG,
1271
- 'attr' => $attr
1272
- ]);
1273
-
1274
- /* Act as if a start tag token with the tag name "hr" had
1275
- been seen. */
1276
- $this->emitToken([
1277
- 'name' => 'hr',
1278
- 'type' => HTML5_Tokenizer::STARTTAG,
1279
- 'attr' => []
1280
- ]);
1281
-
1282
- /* Act as if a start tag token with the tag name "label"
1283
- had been seen. */
1284
- $this->emitToken([
1285
- 'name' => 'label',
1286
- 'type' => HTML5_Tokenizer::STARTTAG,
1287
- 'attr' => []
1288
- ]);
1289
-
1290
- /* Act as if a stream of character tokens had been seen. */
1291
- $prompt = $this->getAttr($token, 'prompt');
1292
- if ($prompt === false) {
1293
- $prompt = 'This is a searchable index. '.
1294
- 'Insert your search keywords here: ';
1295
- }
1296
- $this->emitToken([
1297
- 'data' => $prompt,
1298
- 'type' => HTML5_Tokenizer::CHARACTER,
1299
- ]);
1300
-
1301
- /* Act as if a start tag token with the tag name "input"
1302
- had been seen, with all the attributes from the "isindex"
1303
- token, except with the "name" attribute set to the value
1304
- "isindex" (ignoring any explicit "name" attribute). */
1305
- $attr = [];
1306
- foreach ($token['attr'] as $keypair) {
1307
- if ($keypair['name'] === 'name' || $keypair['name'] === 'action' ||
1308
- $keypair['name'] === 'prompt') {
1309
- continue;
1310
- }
1311
- $attr[] = $keypair;
1312
- }
1313
- $attr[] = ['name' => 'name', 'value' => 'isindex'];
1314
-
1315
- $this->emitToken([
1316
- 'name' => 'input',
1317
- 'type' => HTML5_Tokenizer::STARTTAG,
1318
- 'attr' => $attr
1319
- ]);
1320
-
1321
- /* Act as if an end tag token with the tag name "label"
1322
- had been seen. */
1323
- $this->emitToken([
1324
- 'name' => 'label',
1325
- 'type' => HTML5_Tokenizer::ENDTAG
1326
- ]);
1327
-
1328
- /* Act as if a start tag token with the tag name "hr" had
1329
- been seen. */
1330
- $this->emitToken([
1331
- 'name' => 'hr',
1332
- 'type' => HTML5_Tokenizer::STARTTAG
1333
- ]);
1334
-
1335
- /* Act as if an end tag token with the tag name "form" had
1336
- been seen. */
1337
- $this->emitToken([
1338
- 'name' => 'form',
1339
- 'type' => HTML5_Tokenizer::ENDTAG
1340
- ]);
1341
- } else {
1342
- $this->ignored = true;
1343
- }
1344
- break;
1345
-
1346
- /* A start tag whose tag name is "textarea" */
1347
- case 'textarea':
1348
- $this->insertElement($token);
1349
-
1350
- /* If the next token is a U+000A LINE FEED (LF)
1351
- * character token, then ignore that token and move on to
1352
- * the next one. (Newlines at the start of textarea
1353
- * elements are ignored as an authoring convenience.)
1354
- * need flag, see also <pre> */
1355
- $this->ignore_lf_token = 2;
1356
-
1357
- $this->original_mode = $this->mode;
1358
- $this->flag_frameset_ok = false;
1359
- $this->mode = self::IN_CDATA_RCDATA;
1360
-
1361
- /* Switch the tokeniser's content model flag to the
1362
- RCDATA state. */
1363
- $this->content_model = HTML5_Tokenizer::RCDATA;
1364
- break;
1365
-
1366
- /* A start tag token whose tag name is "xmp" */
1367
- case 'xmp':
1368
- /* If the stack of open elements has a p element in
1369
- scope, then act as if an end tag with the tag name
1370
- "p" has been seen. */
1371
- if ($this->elementInScope('p')) {
1372
- $this->emitToken([
1373
- 'name' => 'p',
1374
- 'type' => HTML5_Tokenizer::ENDTAG
1375
- ]);
1376
- }
1377
-
1378
- /* Reconstruct the active formatting elements, if any. */
1379
- $this->reconstructActiveFormattingElements();
1380
-
1381
- $this->flag_frameset_ok = false;
1382
-
1383
- $this->insertCDATAElement($token);
1384
- break;
1385
-
1386
- case 'iframe':
1387
- $this->flag_frameset_ok = false;
1388
- $this->insertCDATAElement($token);
1389
- break;
1390
-
1391
- case 'noembed': case 'noscript':
1392
- // XSCRIPT: should check scripting flag
1393
- $this->insertCDATAElement($token);
1394
- break;
1395
-
1396
- /* A start tag whose tag name is "select" */
1397
- case 'select':
1398
- /* Reconstruct the active formatting elements, if any. */
1399
- $this->reconstructActiveFormattingElements();
1400
-
1401
- /* Insert an HTML element for the token. */
1402
- $this->insertElement($token);
1403
-
1404
- $this->flag_frameset_ok = false;
1405
-
1406
- /* If the insertion mode is one of in table", "in caption",
1407
- * "in column group", "in table body", "in row", or "in
1408
- * cell", then switch the insertion mode to "in select in
1409
- * table". Otherwise, switch the insertion mode to "in
1410
- * select". */
1411
- if (
1412
- $this->mode === self::IN_TABLE || $this->mode === self::IN_CAPTION ||
1413
- $this->mode === self::IN_COLUMN_GROUP || $this->mode ==+self::IN_TABLE_BODY ||
1414
- $this->mode === self::IN_ROW || $this->mode === self::IN_CELL
1415
- ) {
1416
- $this->mode = self::IN_SELECT_IN_TABLE;
1417
- } else {
1418
- $this->mode = self::IN_SELECT;
1419
- }
1420
- break;
1421
-
1422
- case 'option': case 'optgroup':
1423
- if ($this->elementInScope('option')) {
1424
- $this->emitToken([
1425
- 'name' => 'option',
1426
- 'type' => HTML5_Tokenizer::ENDTAG,
1427
- ]);
1428
- }
1429
- $this->reconstructActiveFormattingElements();
1430
- $this->insertElement($token);
1431
- break;
1432
-
1433
- case 'rp': case 'rt':
1434
- /* If the stack of open elements has a ruby element in scope, then generate
1435
- * implied end tags. If the current node is not then a ruby element, this is
1436
- * a parse error; pop all the nodes from the current node up to the node
1437
- * immediately before the bottommost ruby element on the stack of open elements.
1438
- */
1439
- if ($this->elementInScope('ruby')) {
1440
- $this->generateImpliedEndTags();
1441
- }
1442
- $peek = false;
1443
- do {
1444
- /*if ($peek) {
1445
- // parse error
1446
- }*/
1447
- $peek = array_pop($this->stack);
1448
- } while ($peek->tagName !== 'ruby');
1449
- $this->stack[] = $peek; // we popped one too many
1450
- $this->insertElement($token);
1451
- break;
1452
-
1453
- // spec diversion
1454
-
1455
- case 'math':
1456
- $this->reconstructActiveFormattingElements();
1457
- $token = $this->adjustMathMLAttributes($token);
1458
- $token = $this->adjustForeignAttributes($token);
1459
- $this->insertForeignElement($token, self::NS_MATHML);
1460
- if (isset($token['self-closing'])) {
1461
- // XERROR: acknowledge the token's self-closing flag
1462
- array_pop($this->stack);
1463
- }
1464
- if ($this->mode !== self::IN_FOREIGN_CONTENT) {
1465
- $this->secondary_mode = $this->mode;
1466
- $this->mode = self::IN_FOREIGN_CONTENT;
1467
- }
1468
- break;
1469
-
1470
- case 'svg':
1471
- $this->reconstructActiveFormattingElements();
1472
- $token = $this->adjustSVGAttributes($token);
1473
- $token = $this->adjustForeignAttributes($token);
1474
- $this->insertForeignElement($token, self::NS_SVG);
1475
- if (isset($token['self-closing'])) {
1476
- // XERROR: acknowledge the token's self-closing flag
1477
- array_pop($this->stack);
1478
- }
1479
- if ($this->mode !== self::IN_FOREIGN_CONTENT) {
1480
- $this->secondary_mode = $this->mode;
1481
- $this->mode = self::IN_FOREIGN_CONTENT;
1482
- }
1483
- break;
1484
-
1485
- case 'caption': case 'col': case 'colgroup': case 'frame': case 'head':
1486
- case 'tbody': case 'td': case 'tfoot': case 'th': case 'thead': case 'tr':
1487
- // parse error
1488
- break;
1489
-
1490
- /* A start tag token not covered by the previous entries */
1491
- default:
1492
- /* Reconstruct the active formatting elements, if any. */
1493
- $this->reconstructActiveFormattingElements();
1494
-
1495
- $this->insertElement($token);
1496
- /* This element will be a phrasing element. */
1497
- break;
1498
- }
1499
- break;
1500
-
1501
- case HTML5_Tokenizer::ENDTAG:
1502
- switch ($token['name']) {
1503
- /* An end tag with the tag name "body" */
1504
- case 'body':
1505
- /* If the stack of open elements does not have a body
1506
- * element in scope, this is a parse error; ignore the
1507
- * token. */
1508
- if (!$this->elementInScope('body')) {
1509
- $this->ignored = true;
1510
-
1511
- /* Otherwise, if there is a node in the stack of open
1512
- * elements that is not either a dc element, a dd element,
1513
- * a ds element, a dt element, an li element, an optgroup
1514
- * element, an option element, a p element, an rp element,
1515
- * an rt element, a tbody element, a td element, a tfoot
1516
- * element, a th element, a thead element, a tr element,
1517
- * the body element, or the html element, then this is a
1518
- * parse error.
1519
- */
1520
- } else {
1521
- // XERROR: implement this check for parse error
1522
- }
1523
-
1524
- /* Change the insertion mode to "after body". */
1525
- $this->mode = self::AFTER_BODY;
1526
- break;
1527
-
1528
- /* An end tag with the tag name "html" */
1529
- case 'html':
1530
- /* Act as if an end tag with tag name "body" had been seen,
1531
- then, if that token wasn't ignored, reprocess the current
1532
- token. */
1533
- $this->emitToken([
1534
- 'name' => 'body',
1535
- 'type' => HTML5_Tokenizer::ENDTAG
1536
- ]);
1537
-
1538
- if (!$this->ignored) {
1539
- $this->emitToken($token);
1540
- }
1541
- break;
1542
-
1543
- case 'address': case 'article': case 'aside': case 'blockquote':
1544
- case 'center': case 'datagrid': case 'details': case 'dir':
1545
- case 'div': case 'dl': case 'fieldset': case 'footer':
1546
- case 'header': case 'hgroup': case 'listing': case 'menu':
1547
- case 'nav': case 'ol': case 'pre': case 'section': case 'ul':
1548
- /* If the stack of open elements has an element in scope
1549
- with the same tag name as that of the token, then generate
1550
- implied end tags. */
1551
- if ($this->elementInScope($token['name'])) {
1552
- $this->generateImpliedEndTags();
1553
-
1554
- /* Now, if the current node is not an element with
1555
- the same tag name as that of the token, then this
1556
- is a parse error. */
1557
- // XERROR: implement parse error logic
1558
-
1559
- /* If the stack of open elements has an element in
1560
- scope with the same tag name as that of the token,
1561
- then pop elements from this stack until an element
1562
- with that tag name has been popped from the stack. */
1563
- do {
1564
- $node = array_pop($this->stack);
1565
- } while ($node->tagName !== $token['name']);
1566
- } else {
1567
- // parse error
1568
- }
1569
- break;
1570
-
1571
- /* An end tag whose tag name is "form" */
1572
- case 'form':
1573
- /* Let node be the element that the form element pointer is set to. */
1574
- $node = $this->form_pointer;
1575
- /* Set the form element pointer to null. */
1576
- $this->form_pointer = null;
1577
- /* If node is null or the stack of open elements does not
1578
- * have node in scope, then this is a parse error; ignore the token. */
1579
- if ($node === null || !in_array($node, $this->stack)) {
1580
- // parse error
1581
- $this->ignored = true;
1582
- } else {
1583
- /* 1. Generate implied end tags. */
1584
- $this->generateImpliedEndTags();
1585
- /* 2. If the current node is not node, then this is a parse error. */
1586
- if (end($this->stack) !== $node) {
1587
- // parse error
1588
- }
1589
- /* 3. Remove node from the stack of open elements. */
1590
- array_splice($this->stack, array_search($node, $this->stack, true), 1);
1591
- }
1592
-
1593
- break;
1594
-
1595
- /* An end tag whose tag name is "p" */
1596
- case 'p':
1597
- /* If the stack of open elements has a p element in scope,
1598
- then generate implied end tags, except for p elements. */
1599
- if ($this->elementInScope('p')) {
1600
- /* Generate implied end tags, except for elements with
1601
- * the same tag name as the token. */
1602
- $this->generateImpliedEndTags(['p']);
1603
-
1604
- /* If the current node is not a p element, then this is
1605
- a parse error. */
1606
- // XERROR: implement
1607
-
1608
- /* Pop elements from the stack of open elements until
1609
- * an element with the same tag name as the token has
1610
- * been popped from the stack. */
1611
- do {
1612
- $node = array_pop($this->stack);
1613
- } while ($node->tagName !== 'p');
1614
-
1615
- } else {
1616
- // parse error
1617
- $this->emitToken([
1618
- 'name' => 'p',
1619
- 'type' => HTML5_Tokenizer::STARTTAG,
1620
- ]);
1621
- $this->emitToken($token);
1622
- }
1623
- break;
1624
-
1625
- /* An end tag whose tag name is "li" */
1626
- case 'li':
1627
- /* If the stack of open elements does not have an element
1628
- * in list item scope with the same tag name as that of the
1629
- * token, then this is a parse error; ignore the token. */
1630
- if ($this->elementInScope($token['name'], self::SCOPE_LISTITEM)) {
1631
- /* Generate implied end tags, except for elements with the
1632
- * same tag name as the token. */
1633
- $this->generateImpliedEndTags([$token['name']]);
1634
- /* If the current node is not an element with the same tag
1635
- * name as that of the token, then this is a parse error. */
1636
- // XERROR: parse error
1637
- /* Pop elements from the stack of open elements until an
1638
- * element with the same tag name as the token has been
1639
- * popped from the stack. */
1640
- do {
1641
- $node = array_pop($this->stack);
1642
- } while ($node->tagName !== $token['name']);
1643
- }
1644
- /*else {
1645
- // XERROR: parse error
1646
- }*/
1647
- break;
1648
-
1649
- /* An end tag whose tag name is "dc", "dd", "ds", "dt" */
1650
- case 'dc': case 'dd': case 'ds': case 'dt':
1651
- if ($this->elementInScope($token['name'])) {
1652
- $this->generateImpliedEndTags([$token['name']]);
1653
-
1654
- /* If the current node is not an element with the same
1655
- tag name as the token, then this is a parse error. */
1656
- // XERROR: implement parse error
1657
-
1658
- /* Pop elements from the stack of open elements until
1659
- * an element with the same tag name as the token has
1660
- * been popped from the stack. */
1661
- do {
1662
- $node = array_pop($this->stack);
1663
- } while ($node->tagName !== $token['name']);
1664
- }
1665
- /*else {
1666
- // XERROR: parse error
1667
- }*/
1668
- break;
1669
-
1670
- /* An end tag whose tag name is one of: "h1", "h2", "h3", "h4",
1671
- "h5", "h6" */
1672
- case 'h1': case 'h2': case 'h3': case 'h4': case 'h5': case 'h6':
1673
- $elements = ['h1', 'h2', 'h3', 'h4', 'h5', 'h6'];
1674
-
1675
- /* If the stack of open elements has in scope an element whose
1676
- tag name is one of "h1", "h2", "h3", "h4", "h5", or "h6", then
1677
- generate implied end tags. */
1678
- if ($this->elementInScope($elements)) {
1679
- $this->generateImpliedEndTags();
1680
-
1681
- /* Now, if the current node is not an element with the same
1682
- tag name as that of the token, then this is a parse error. */
1683
- // XERROR: implement parse error
1684
-
1685
- /* If the stack of open elements has in scope an element
1686
- whose tag name is one of "h1", "h2", "h3", "h4", "h5", or
1687
- "h6", then pop elements from the stack until an element
1688
- with one of those tag names has been popped from the stack. */
1689
- do {
1690
- $node = array_pop($this->stack);
1691
- } while (!in_array($node->tagName, $elements));
1692
- }
1693
- /*else {
1694
- // parse error
1695
- }*/
1696
- break;
1697
-
1698
- /* An end tag whose tag name is one of: "a", "b", "big", "em",
1699
- "font", "i", "nobr", "s", "small", "strike", "strong", "tt", "u" */
1700
- case 'a': case 'b': case 'big': case 'code': case 'em': case 'font':
1701
- case 'i': case 'nobr': case 's': case 'small': case 'strike':
1702
- case 'strong': case 'tt': case 'u':
1703
- // XERROR: generally speaking this needs parse error logic
1704
- /* 1. Let the formatting element be the last element in
1705
- the list of active formatting elements that:
1706
- * is between the end of the list and the last scope
1707
- marker in the list, if any, or the start of the list
1708
- otherwise, and
1709
- * has the same tag name as the token.
1710
- */
1711
- while (true) {
1712
- for ($a = count($this->a_formatting) - 1; $a >= 0; $a--) {
1713
- if ($this->a_formatting[$a] === self::MARKER) {
1714
- break;
1715
- } elseif ($this->a_formatting[$a]->tagName === $token['name']) {
1716
- $formatting_element = $this->a_formatting[$a];
1717
- $in_stack = in_array($formatting_element, $this->stack, true);
1718
- $fe_af_pos = $a;
1719
- break;
1720
- }
1721
- }
1722
-
1723
- /* If there is no such node, or, if that node is
1724
- also in the stack of open elements but the element
1725
- is not in scope, then this is a parse error. Abort
1726
- these steps. The token is ignored. */
1727
- if (
1728
- !isset($formatting_element) || (
1729
- $in_stack &&
1730
- !$this->elementInScope($token['name'])
1731
- )
1732
- ) {
1733
- $this->ignored = true;
1734
- break;
1735
-
1736
- /* Otherwise, if there is such a node, but that node
1737
- is not in the stack of open elements, then this is a
1738
- parse error; remove the element from the list, and
1739
- abort these steps. */
1740
- } elseif (isset($formatting_element) && !$in_stack) {
1741
- unset($this->a_formatting[$fe_af_pos]);
1742
- $this->a_formatting = array_merge($this->a_formatting);
1743
- break;
1744
- }
1745
-
1746
- /* Otherwise, there is a formatting element and that
1747
- * element is in the stack and is in scope. If the
1748
- * element is not the current node, this is a parse
1749
- * error. In any case, proceed with the algorithm as
1750
- * written in the following steps. */
1751
- // XERROR: implement me
1752
-
1753
- /* 2. Let the furthest block be the topmost node in the
1754
- stack of open elements that is lower in the stack
1755
- than the formatting element, and is not an element in
1756
- the phrasing or formatting categories. There might
1757
- not be one. */
1758
- $fe_s_pos = array_search($formatting_element, $this->stack, true);
1759
- $length = count($this->stack);
1760
-
1761
- for ($s = $fe_s_pos + 1; $s < $length; $s++) {
1762
- $category = $this->getElementCategory($this->stack[$s]);
1763
-
1764
- if ($category !== self::PHRASING && $category !== self::FORMATTING) {
1765
- $furthest_block = $this->stack[$s];
1766
- break;
1767
- }
1768
- }
1769
-
1770
- /* 3. If there is no furthest block, then the UA must
1771
- skip the subsequent steps and instead just pop all
1772
- the nodes from the bottom of the stack of open
1773
- elements, from the current node up to the formatting
1774
- element, and remove the formatting element from the
1775
- list of active formatting elements. */
1776
- if (!isset($furthest_block)) {
1777
- for ($n = $length - 1; $n >= $fe_s_pos; $n--) {
1778
- array_pop($this->stack);
1779
- }
1780
-
1781
- unset($this->a_formatting[$fe_af_pos]);
1782
- $this->a_formatting = array_merge($this->a_formatting);
1783
- break;
1784
- }
1785
-
1786
- /* 4. Let the common ancestor be the element
1787
- immediately above the formatting element in the stack
1788
- of open elements. */
1789
- $common_ancestor = $this->stack[$fe_s_pos - 1];
1790
-
1791
- /* 5. Let a bookmark note the position of the
1792
- formatting element in the list of active formatting
1793
- elements relative to the elements on either side
1794
- of it in the list. */
1795
- $bookmark = $fe_af_pos;
1796
-
1797
- /* 6. Let node and last node be the furthest block.
1798
- Follow these steps: */
1799
- $node = $furthest_block;
1800
- $last_node = $furthest_block;
1801
-
1802
- while (true) {
1803
- for ($n = array_search($node, $this->stack, true) - 1; $n >= 0; $n--) {
1804
- /* 6.1 Let node be the element immediately
1805
- prior to node in the stack of open elements. */
1806
- $node = $this->stack[$n];
1807
-
1808
- /* 6.2 If node is not in the list of active
1809
- formatting elements, then remove node from
1810
- the stack of open elements and then go back
1811
- to step 1. */
1812
- if (!in_array($node, $this->a_formatting, true)) {
1813
- array_splice($this->stack, $n, 1);
1814
- } else {
1815
- break;
1816
- }
1817
- }
1818
-
1819
- /* 6.3 Otherwise, if node is the formatting
1820
- element, then go to the next step in the overall
1821
- algorithm. */
1822
- if ($node === $formatting_element) {
1823
- break;
1824
-
1825
- /* 6.4 Otherwise, if last node is the furthest
1826
- block, then move the aforementioned bookmark to
1827
- be immediately after the node in the list of
1828
- active formatting elements. */
1829
- } elseif ($last_node === $furthest_block) {
1830
- $bookmark = array_search($node, $this->a_formatting, true) + 1;
1831
- }
1832
-
1833
- /* 6.5 Create an element for the token for which
1834
- * the element node was created, replace the entry
1835
- * for node in the list of active formatting
1836
- * elements with an entry for the new element,
1837
- * replace the entry for node in the stack of open
1838
- * elements with an entry for the new element, and
1839
- * let node be the new element. */
1840
- // we don't know what the token is anymore
1841
- // XDOM
1842
- $clone = $node->cloneNode();
1843
- $a_pos = array_search($node, $this->a_formatting, true);
1844
- $s_pos = array_search($node, $this->stack, true);
1845
- $this->a_formatting[$a_pos] = $clone;
1846
- $this->stack[$s_pos] = $clone;
1847
- $node = $clone;
1848
-
1849
- /* 6.6 Insert last node into node, first removing
1850
- it from its previous parent node if any. */
1851
- // XDOM
1852
- if ($last_node->parentNode !== null) {
1853
- $last_node->parentNode->removeChild($last_node);
1854
- }
1855
-
1856
- // XDOM
1857
- $node->appendChild($last_node);
1858
-
1859
- /* 6.7 Let last node be node. */
1860
- $last_node = $node;
1861
-
1862
- /* 6.8 Return to step 1 of this inner set of steps. */
1863
- }
1864
-
1865
- /* 7. If the common ancestor node is a table, tbody,
1866
- * tfoot, thead, or tr element, then, foster parent
1867
- * whatever last node ended up being in the previous
1868
- * step, first removing it from its previous parent
1869
- * node if any. */
1870
- // XDOM
1871
- if ($last_node->parentNode) { // common step
1872
- $last_node->parentNode->removeChild($last_node);
1873
- }
1874
- if (in_array($common_ancestor->tagName, ['table', 'tbody', 'tfoot', 'thead', 'tr'])) {
1875
- $this->fosterParent($last_node);
1876
- /* Otherwise, append whatever last node ended up being
1877
- * in the previous step to the common ancestor node,
1878
- * first removing it from its previous parent node if
1879
- * any. */
1880
- } else {
1881
- // XDOM
1882
- $common_ancestor->appendChild($last_node);
1883
- }
1884
-
1885
- /* 8. Create an element for the token for which the
1886
- * formatting element was created. */
1887
- // XDOM
1888
- $clone = $formatting_element->cloneNode();
1889
-
1890
- /* 9. Take all of the child nodes of the furthest
1891
- block and append them to the element created in the
1892
- last step. */
1893
- // XDOM
1894
- while ($furthest_block->hasChildNodes()) {
1895
- $child = $furthest_block->firstChild;
1896
- $furthest_block->removeChild($child);
1897
- $clone->appendChild($child);
1898
- }
1899
-
1900
- /* 10. Append that clone to the furthest block. */
1901
- // XDOM
1902
- $furthest_block->appendChild($clone);
1903
-
1904
- /* 11. Remove the formatting element from the list
1905
- of active formatting elements, and insert the new element
1906
- into the list of active formatting elements at the
1907
- position of the aforementioned bookmark. */
1908
- $fe_af_pos = array_search($formatting_element, $this->a_formatting, true);
1909
- array_splice($this->a_formatting, $fe_af_pos, 1);
1910
-
1911
- $af_part1 = array_slice($this->a_formatting, 0, $bookmark - 1);
1912
- $af_part2 = array_slice($this->a_formatting, $bookmark);
1913
- $this->a_formatting = array_merge($af_part1, [$clone], $af_part2);
1914
-
1915
- /* 12. Remove the formatting element from the stack
1916
- of open elements, and insert the new element into the stack
1917
- of open elements immediately below the position of the
1918
- furthest block in that stack. */
1919
- $fe_s_pos = array_search($formatting_element, $this->stack, true);
1920
- array_splice($this->stack, $fe_s_pos, 1);
1921
-
1922
- $fb_s_pos = array_search($furthest_block, $this->stack, true);
1923
- $s_part1 = array_slice($this->stack, 0, $fb_s_pos + 1);
1924
- $s_part2 = array_slice($this->stack, $fb_s_pos + 1);
1925
- $this->stack = array_merge($s_part1, [$clone], $s_part2);
1926
-
1927
- /* 13. Jump back to step 1 in this series of steps. */
1928
- unset($formatting_element, $fe_af_pos, $fe_s_pos, $furthest_block);
1929
- }
1930
- break;
1931
-
1932
- case 'applet': case 'button': case 'marquee': case 'object':
1933
- /* If the stack of open elements has an element in scope whose
1934
- tag name matches the tag name of the token, then generate implied
1935
- tags. */
1936
- if ($this->elementInScope($token['name'])) {
1937
- $this->generateImpliedEndTags();
1938
-
1939
- /* Now, if the current node is not an element with the same
1940
- tag name as the token, then this is a parse error. */
1941
- // XERROR: implement logic
1942
-
1943
- /* Pop elements from the stack of open elements until
1944
- * an element with the same tag name as the token has
1945
- * been popped from the stack. */
1946
- do {
1947
- $node = array_pop($this->stack);
1948
- } while ($node->tagName !== $token['name']);
1949
-
1950
- /* Clear the list of active formatting elements up to the
1951
- * last marker. */
1952
- $keys = array_keys($this->a_formatting, self::MARKER, true);
1953
- $marker = end($keys);
1954
-
1955
- for ($n = count($this->a_formatting) - 1; $n > $marker; $n--) {
1956
- array_pop($this->a_formatting);
1957
- }
1958
- }
1959
- /*else {
1960
- // parse error
1961
- }*/
1962
- break;
1963
-
1964
- case 'br':
1965
- // Parse error
1966
- $this->emitToken([
1967
- 'name' => 'br',
1968
- 'type' => HTML5_Tokenizer::STARTTAG,
1969
- ]);
1970
- break;
1971
-
1972
- /* An end tag token not covered by the previous entries */
1973
- default:
1974
- for ($n = count($this->stack) - 1; $n >= 0; $n--) {
1975
- /* Initialise node to be the current node (the bottommost
1976
- node of the stack). */
1977
- $node = $this->stack[$n];
1978
-
1979
- /* If node has the same tag name as the end tag token,
1980
- then: */
1981
- if ($token['name'] === $node->tagName) {
1982
- /* Generate implied end tags. */
1983
- $this->generateImpliedEndTags();
1984
-
1985
- /* If the tag name of the end tag token does not
1986
- match the tag name of the current node, this is a
1987
- parse error. */
1988
- // XERROR: implement this
1989
-
1990
- /* Pop all the nodes from the current node up to
1991
- node, including node, then stop these steps. */
1992
- // XSKETCHY
1993
- do {
1994
- $pop = array_pop($this->stack);
1995
- } while ($pop !== $node);
1996
- break;
1997
- } else {
1998
- $category = $this->getElementCategory($node);
1999
-
2000
- if ($category !== self::FORMATTING && $category !== self::PHRASING) {
2001
- /* Otherwise, if node is in neither the formatting
2002
- category nor the phrasing category, then this is a
2003
- parse error. Stop this algorithm. The end tag token
2004
- is ignored. */
2005
- $this->ignored = true;
2006
- break;
2007
- // parse error
2008
- }
2009
- }
2010
- /* Set node to the previous entry in the stack of open elements. Loop. */
2011
- }
2012
- break;
2013
- }
2014
- break;
2015
- }
2016
- break;
2017
-
2018
- case self::IN_CDATA_RCDATA:
2019
- if (
2020
- $token['type'] === HTML5_Tokenizer::CHARACTER ||
2021
- $token['type'] === HTML5_Tokenizer::SPACECHARACTER
2022
- ) {
2023
- $this->insertText($token['data']);
2024
- } elseif ($token['type'] === HTML5_Tokenizer::EOF) {
2025
- // parse error
2026
- /* If the current node is a script element, mark the script
2027
- * element as "already executed". */
2028
- // probably not necessary
2029
- array_pop($this->stack);
2030
- $this->mode = $this->original_mode;
2031
- $this->emitToken($token);
2032
- } elseif ($token['type'] === HTML5_Tokenizer::ENDTAG && $token['name'] === 'script') {
2033
- array_pop($this->stack);
2034
- $this->mode = $this->original_mode;
2035
- // we're ignoring all of the execution stuff
2036
- } elseif ($token['type'] === HTML5_Tokenizer::ENDTAG) {
2037
- array_pop($this->stack);
2038
- $this->mode = $this->original_mode;
2039
- }
2040
- break;
2041
-
2042
- case self::IN_TABLE:
2043
- $clear = ['html', 'table'];
2044
-
2045
- /* A character token */
2046
- if ($token['type'] === HTML5_Tokenizer::CHARACTER ||
2047
- $token['type'] === HTML5_Tokenizer::SPACECHARACTER) {
2048
- /* Let the pending table character tokens
2049
- * be an empty list of tokens. */
2050
- $this->pendingTableCharacters = "";
2051
- $this->pendingTableCharactersDirty = false;
2052
- /* Let the original insertion mode be the current
2053
- * insertion mode. */
2054
- $this->original_mode = $this->mode;
2055
- /* Switch the insertion mode to
2056
- * "in table text" and
2057
- * reprocess the token. */
2058
- $this->mode = self::IN_TABLE_TEXT;
2059
- $this->emitToken($token);
2060
-
2061
- /* A comment token */
2062
- } elseif ($token['type'] === HTML5_Tokenizer::COMMENT) {
2063
- /* Append a Comment node to the current node with the data
2064
- attribute set to the data given in the comment token. */
2065
- $this->insertComment($token['data']);
2066
-
2067
- } elseif ($token['type'] === HTML5_Tokenizer::DOCTYPE) {
2068
- // parse error
2069
-
2070
- /* A start tag whose tag name is "caption" */
2071
- } elseif ($token['type'] === HTML5_Tokenizer::STARTTAG &&
2072
- $token['name'] === 'caption') {
2073
- /* Clear the stack back to a table context. */
2074
- $this->clearStackToTableContext($clear);
2075
-
2076
- /* Insert a marker at the end of the list of active
2077
- formatting elements. */
2078
- $this->a_formatting[] = self::MARKER;
2079
-
2080
- /* Insert an HTML element for the token, then switch the
2081
- insertion mode to "in caption". */
2082
- $this->insertElement($token);
2083
- $this->mode = self::IN_CAPTION;
2084
-
2085
- /* A start tag whose tag name is "colgroup" */
2086
- } elseif ($token['type'] === HTML5_Tokenizer::STARTTAG &&
2087
- $token['name'] === 'colgroup') {
2088
- /* Clear the stack back to a table context. */
2089
- $this->clearStackToTableContext($clear);
2090
-
2091
- /* Insert an HTML element for the token, then switch the
2092
- insertion mode to "in column group". */
2093
- $this->insertElement($token);
2094
- $this->mode = self::IN_COLUMN_GROUP;
2095
-
2096
- /* A start tag whose tag name is "col" */
2097
- } elseif ($token['type'] === HTML5_Tokenizer::STARTTAG &&
2098
- $token['name'] === 'col') {
2099
- $this->emitToken([
2100
- 'name' => 'colgroup',
2101
- 'type' => HTML5_Tokenizer::STARTTAG,
2102
- 'attr' => []
2103
- ]);
2104
-
2105
- $this->emitToken($token);
2106
-
2107
- /* A start tag whose tag name is one of: "tbody", "tfoot", "thead" */
2108
- } elseif ($token['type'] === HTML5_Tokenizer::STARTTAG && in_array($token['name'],
2109
- ['tbody', 'tfoot', 'thead'])) {
2110
- /* Clear the stack back to a table context. */
2111
- $this->clearStackToTableContext($clear);
2112
-
2113
- /* Insert an HTML element for the token, then switch the insertion
2114
- mode to "in table body". */
2115
- $this->insertElement($token);
2116
- $this->mode = self::IN_TABLE_BODY;
2117
-
2118
- /* A start tag whose tag name is one of: "td", "th", "tr" */
2119
- } elseif ($token['type'] === HTML5_Tokenizer::STARTTAG &&
2120
- in_array($token['name'], ['td', 'th', 'tr'])) {
2121
- /* Act as if a start tag token with the tag name "tbody" had been
2122
- seen, then reprocess the current token. */
2123
- $this->emitToken([
2124
- 'name' => 'tbody',
2125
- 'type' => HTML5_Tokenizer::STARTTAG,
2126
- 'attr' => []
2127
- ]);
2128
-
2129
- $this->emitToken($token);
2130
-
2131
- /* A start tag whose tag name is "table" */
2132
- } elseif ($token['type'] === HTML5_Tokenizer::STARTTAG &&
2133
- $token['name'] === 'table') {
2134
- /* Parse error. Act as if an end tag token with the tag name "table"
2135
- had been seen, then, if that token wasn't ignored, reprocess the
2136
- current token. */
2137
- $this->emitToken([
2138
- 'name' => 'table',
2139
- 'type' => HTML5_Tokenizer::ENDTAG
2140
- ]);
2141
-
2142
- if (!$this->ignored) {
2143
- $this->emitToken($token);
2144
- }
2145
-
2146
- /* An end tag whose tag name is "table" */
2147
- } elseif ($token['type'] === HTML5_Tokenizer::ENDTAG &&
2148
- $token['name'] === 'table') {
2149
- /* If the stack of open elements does not have an element in table
2150
- scope with the same tag name as the token, this is a parse error.
2151
- Ignore the token. (fragment case) */
2152
- if (!$this->elementInScope($token['name'], self::SCOPE_TABLE)) {
2153
- $this->ignored = true;
2154
- } else {
2155
- do {
2156
- $node = array_pop($this->stack);
2157
- } while ($node->tagName !== 'table');
2158
-
2159
- /* Reset the insertion mode appropriately. */
2160
- $this->resetInsertionMode();
2161
- }
2162
-
2163
- /* An end tag whose tag name is one of: "body", "caption", "col",
2164
- "colgroup", "html", "tbody", "td", "tfoot", "th", "thead", "tr" */
2165
- } elseif ($token['type'] === HTML5_Tokenizer::ENDTAG && in_array($token['name'],
2166
- ['body', 'caption', 'col', 'colgroup', 'html', 'tbody', 'td',
2167
- 'tfoot', 'th', 'thead', 'tr'])) {
2168
- // Parse error. Ignore the token.
2169
-
2170
- } elseif ($token['type'] === HTML5_Tokenizer::STARTTAG &&
2171
- ($token['name'] === 'style' || $token['name'] === 'script')) {
2172
- $this->processWithRulesFor($token, self::IN_HEAD);
2173
-
2174
- } elseif ($token['type'] === HTML5_Tokenizer::STARTTAG && $token['name'] === 'input' &&
2175
- // assignment is intentional
2176
- /* If the token does not have an attribute with the name "type", or
2177
- * if it does, but that attribute's value is not an ASCII
2178
- * case-insensitive match for the string "hidden", then: act as
2179
- * described in the "anything else" entry below. */
2180
- ($type = $this->getAttr($token, 'type')) && strtolower($type) === 'hidden') {
2181
- // I.e., if its an input with the type attribute == 'hidden'
2182
- /* Otherwise */
2183
- // parse error
2184
- $this->insertElement($token);
2185
- array_pop($this->stack);
2186
- } elseif ($token['type'] === HTML5_Tokenizer::EOF) {
2187
- /* If the current node is not the root html element, then this is a parse error. */
2188
- if (end($this->stack)->tagName !== 'html') {
2189
- // Note: It can only be the current node in the fragment case.
2190
- // parse error
2191
- }
2192
- /* Stop parsing. */
2193
- /* Anything else */
2194
- } else {
2195
- /* Parse error. Process the token as if the insertion mode was "in
2196
- body", with the following exception: */
2197
-
2198
- $old = $this->foster_parent;
2199
- $this->foster_parent = true;
2200
- $this->processWithRulesFor($token, self::IN_BODY);
2201
- $this->foster_parent = $old;
2202
- }
2203
- break;
2204
-
2205
- case self::IN_TABLE_TEXT:
2206
- /* A character token */
2207
- if ($token['type'] === HTML5_Tokenizer::CHARACTER) {
2208
- /* Append the character token to the pending table
2209
- * character tokens list. */
2210
- $this->pendingTableCharacters .= $token['data'];
2211
- $this->pendingTableCharactersDirty = true;
2212
- } elseif ($token['type'] === HTML5_Tokenizer::SPACECHARACTER) {
2213
- $this->pendingTableCharacters .= $token['data'];
2214
- /* Anything else */
2215
- } else {
2216
- if ($this->pendingTableCharacters !== '' && is_string($this->pendingTableCharacters)) {
2217
- /* If any of the tokens in the pending table character tokens list
2218
- * are character tokens that are not one of U+0009 CHARACTER
2219
- * TABULATION, U+000A LINE FEED (LF), U+000C FORM FEED (FF), or
2220
- * U+0020 SPACE, then reprocess those character tokens using the
2221
- * rules given in the "anything else" entry in the in table"
2222
- * insertion mode.*/
2223
- if ($this->pendingTableCharactersDirty) {
2224
- /* Parse error. Process the token using the rules for the
2225
- * "in body" insertion mode, except that if the current
2226
- * node is a table, tbody, tfoot, thead, or tr element,
2227
- * then, whenever a node would be inserted into the current
2228
- * node, it must instead be foster parented. */
2229
- // XERROR
2230
- $old = $this->foster_parent;
2231
- $this->foster_parent = true;
2232
- $text_token = [
2233
- 'type' => HTML5_Tokenizer::CHARACTER,
2234
- 'data' => $this->pendingTableCharacters,
2235
- ];
2236
- $this->processWithRulesFor($text_token, self::IN_BODY);
2237
- $this->foster_parent = $old;
2238
-
2239
- /* Otherwise, insert the characters given by the pending table
2240
- * character tokens list into the current node. */
2241
- } else {
2242
- $this->insertText($this->pendingTableCharacters);
2243
- }
2244
- $this->pendingTableCharacters = null;
2245
- $this->pendingTableCharactersNull = null;
2246
- }
2247
-
2248
- /* Switch the insertion mode to the original insertion mode and
2249
- * reprocess the token.
2250
- */
2251
- $this->mode = $this->original_mode;
2252
- $this->emitToken($token);
2253
- }
2254
- break;
2255
-
2256
- case self::IN_CAPTION:
2257
- /* An end tag whose tag name is "caption" */
2258
- if ($token['type'] === HTML5_Tokenizer::ENDTAG && $token['name'] === 'caption') {
2259
- /* If the stack of open elements does not have an element in table
2260
- scope with the same tag name as the token, this is a parse error.
2261
- Ignore the token. (fragment case) */
2262
- if (!$this->elementInScope($token['name'], self::SCOPE_TABLE)) {
2263
- $this->ignored = true;
2264
- // Ignore
2265
-
2266
- /* Otherwise: */
2267
- } else {
2268
- /* Generate implied end tags. */
2269
- $this->generateImpliedEndTags();
2270
-
2271
- /* Now, if the current node is not a caption element, then this
2272
- is a parse error. */
2273
- // XERROR: implement
2274
-
2275
- /* Pop elements from this stack until a caption element has
2276
- been popped from the stack. */
2277
- do {
2278
- $node = array_pop($this->stack);
2279
- } while ($node->tagName !== 'caption');
2280
-
2281
- /* Clear the list of active formatting elements up to the last
2282
- marker. */
2283
- $this->clearTheActiveFormattingElementsUpToTheLastMarker();
2284
-
2285
- /* Switch the insertion mode to "in table". */
2286
- $this->mode = self::IN_TABLE;
2287
- }
2288
-
2289
- /* A start tag whose tag name is one of: "caption", "col", "colgroup",
2290
- "tbody", "td", "tfoot", "th", "thead", "tr", or an end tag whose tag
2291
- name is "table" */
2292
- } elseif (($token['type'] === HTML5_Tokenizer::STARTTAG && in_array($token['name'],
2293
- ['caption', 'col', 'colgroup', 'tbody', 'td', 'tfoot', 'th',
2294
- 'thead', 'tr'])) || ($token['type'] === HTML5_Tokenizer::ENDTAG &&
2295
- $token['name'] === 'table')) {
2296
- /* Parse error. Act as if an end tag with the tag name "caption"
2297
- had been seen, then, if that token wasn't ignored, reprocess the
2298
- current token. */
2299
- $this->emitToken([
2300
- 'name' => 'caption',
2301
- 'type' => HTML5_Tokenizer::ENDTAG
2302
- ]);
2303
-
2304
- if (!$this->ignored) {
2305
- $this->emitToken($token);
2306
- }
2307
-
2308
- /* An end tag whose tag name is one of: "body", "col", "colgroup",
2309
- "html", "tbody", "td", "tfoot", "th", "thead", "tr" */
2310
- } elseif ($token['type'] === HTML5_Tokenizer::ENDTAG && in_array($token['name'],
2311
- ['body', 'col', 'colgroup', 'html', 'tbody', 'tfoot', 'th',
2312
- 'thead', 'tr'])) {
2313
- // Parse error. Ignore the token.
2314
- $this->ignored = true;
2315
- } else {
2316
- /* Process the token as if the insertion mode was "in body". */
2317
- $this->processWithRulesFor($token, self::IN_BODY);
2318
- }
2319
- break;
2320
-
2321
- case self::IN_COLUMN_GROUP:
2322
- /* A character token that is one of one of U+0009 CHARACTER TABULATION,
2323
- U+000A LINE FEED (LF), U+000B LINE TABULATION, U+000C FORM FEED (FF),
2324
- or U+0020 SPACE */
2325
- if ($token['type'] === HTML5_Tokenizer::SPACECHARACTER) {
2326
- /* Append the character to the current node. */
2327
- $this->insertText($token['data']);
2328
-
2329
- /* A comment token */
2330
- } elseif ($token['type'] === HTML5_Tokenizer::COMMENT) {
2331
- /* Append a Comment node to the current node with the data
2332
- attribute set to the data given in the comment token. */
2333
- $this->insertComment($token['data']);
2334
- } elseif ($token['type'] === HTML5_Tokenizer::DOCTYPE) {
2335
- // parse error
2336
- } elseif ($token['type'] === HTML5_Tokenizer::STARTTAG && $token['name'] === 'html') {
2337
- $this->processWithRulesFor($token, self::IN_BODY);
2338
-
2339
- /* A start tag whose tag name is "col" */
2340
- } elseif ($token['type'] === HTML5_Tokenizer::STARTTAG && $token['name'] === 'col') {
2341
- /* Insert a col element for the token. Immediately pop the current
2342
- node off the stack of open elements. */
2343
- $this->insertElement($token);
2344
- array_pop($this->stack);
2345
- // XERROR: Acknowledge the token's self-closing flag, if it is set.
2346
-
2347
- /* An end tag whose tag name is "colgroup" */
2348
- } elseif ($token['type'] === HTML5_Tokenizer::ENDTAG &&
2349
- $token['name'] === 'colgroup') {
2350
- /* If the current node is the root html element, then this is a
2351
- parse error, ignore the token. (fragment case) */
2352
- if (end($this->stack)->tagName === 'html') {
2353
- $this->ignored = true;
2354
-
2355
- /* Otherwise, pop the current node (which will be a colgroup
2356
- element) from the stack of open elements. Switch the insertion
2357
- mode to "in table". */
2358
- } else {
2359
- array_pop($this->stack);
2360
- $this->mode = self::IN_TABLE;
2361
- }
2362
-
2363
- /* An end tag whose tag name is "col" */
2364
- } elseif ($token['type'] === HTML5_Tokenizer::ENDTAG && $token['name'] === 'col') {
2365
- /* Parse error. Ignore the token. */
2366
- $this->ignored = true;
2367
-
2368
- /* An end-of-file token */
2369
- /* If the current node is the root html element */
2370
- } elseif ($token['type'] === HTML5_Tokenizer::EOF && end($this->stack)->tagName === 'html') {
2371
- /* Stop parsing */
2372
-
2373
- /* Anything else */
2374
- } else {
2375
- /* Act as if an end tag with the tag name "colgroup" had been seen,
2376
- and then, if that token wasn't ignored, reprocess the current token. */
2377
- $this->emitToken([
2378
- 'name' => 'colgroup',
2379
- 'type' => HTML5_Tokenizer::ENDTAG
2380
- ]);
2381
-
2382
- if (!$this->ignored) {
2383
- $this->emitToken($token);
2384
- }
2385
- }
2386
- break;
2387
-
2388
- case self::IN_TABLE_BODY:
2389
- $clear = ['tbody', 'tfoot', 'thead', 'html'];
2390
-
2391
- /* A start tag whose tag name is "tr" */
2392
- if ($token['type'] === HTML5_Tokenizer::STARTTAG && $token['name'] === 'tr') {
2393
- /* Clear the stack back to a table body context. */
2394
- $this->clearStackToTableContext($clear);
2395
-
2396
- /* Insert a tr element for the token, then switch the insertion
2397
- mode to "in row". */
2398
- $this->insertElement($token);
2399
- $this->mode = self::IN_ROW;
2400
-
2401
- /* A start tag whose tag name is one of: "th", "td" */
2402
- } elseif ($token['type'] === HTML5_Tokenizer::STARTTAG &&
2403
- ($token['name'] === 'th' || $token['name'] === 'td')) {
2404
- /* Parse error. Act as if a start tag with the tag name "tr" had
2405
- been seen, then reprocess the current token. */
2406
- $this->emitToken([
2407
- 'name' => 'tr',
2408
- 'type' => HTML5_Tokenizer::STARTTAG,
2409
- 'attr' => []
2410
- ]);
2411
-
2412
- $this->emitToken($token);
2413
-
2414
- /* An end tag whose tag name is one of: "tbody", "tfoot", "thead" */
2415
- } elseif ($token['type'] === HTML5_Tokenizer::ENDTAG &&
2416
- in_array($token['name'], ['tbody', 'tfoot', 'thead'])) {
2417
- /* If the stack of open elements does not have an element in table
2418
- scope with the same tag name as the token, this is a parse error.
2419
- Ignore the token. */
2420
- if (!$this->elementInScope($token['name'], self::SCOPE_TABLE)) {
2421
- // Parse error
2422
- $this->ignored = true;
2423
-
2424
- /* Otherwise: */
2425
- } else {
2426
- /* Clear the stack back to a table body context. */
2427
- $this->clearStackToTableContext($clear);
2428
-
2429
- /* Pop the current node from the stack of open elements. Switch
2430
- the insertion mode to "in table". */
2431
- array_pop($this->stack);
2432
- $this->mode = self::IN_TABLE;
2433
- }
2434
-
2435
- /* A start tag whose tag name is one of: "caption", "col", "colgroup",
2436
- "tbody", "tfoot", "thead", or an end tag whose tag name is "table" */
2437
- } elseif (($token['type'] === HTML5_Tokenizer::STARTTAG && in_array($token['name'],
2438
- ['caption', 'col', 'colgroup', 'tbody', 'tfoot', 'thead'])) ||
2439
- ($token['type'] === HTML5_Tokenizer::ENDTAG && $token['name'] === 'table')) {
2440
- /* If the stack of open elements does not have a tbody, thead, or
2441
- tfoot element in table scope, this is a parse error. Ignore the
2442
- token. (fragment case) */
2443
- if (!$this->elementInScope(['tbody', 'thead', 'tfoot'], self::SCOPE_TABLE)) {
2444
- // parse error
2445
- $this->ignored = true;
2446
-
2447
- /* Otherwise: */
2448
- } else {
2449
- /* Clear the stack back to a table body context. */
2450
- $this->clearStackToTableContext($clear);
2451
-
2452
- /* Act as if an end tag with the same tag name as the current
2453
- node ("tbody", "tfoot", or "thead") had been seen, then
2454
- reprocess the current token. */
2455
- $this->emitToken([
2456
- 'name' => end($this->stack)->tagName,
2457
- 'type' => HTML5_Tokenizer::ENDTAG
2458
- ]);
2459
-
2460
- $this->emitToken($token);
2461
- }
2462
-
2463
- /* An end tag whose tag name is one of: "body", "caption", "col",
2464
- "colgroup", "html", "td", "th", "tr" */
2465
- } elseif ($token['type'] === HTML5_Tokenizer::ENDTAG && in_array($token['name'],
2466
- ['body', 'caption', 'col', 'colgroup', 'html', 'td', 'th', 'tr'])) {
2467
- /* Parse error. Ignore the token. */
2468
- $this->ignored = true;
2469
-
2470
- /* Anything else */
2471
- } else {
2472
- /* Process the token as if the insertion mode was "in table". */
2473
- $this->processWithRulesFor($token, self::IN_TABLE);
2474
- }
2475
- break;
2476
-
2477
- case self::IN_ROW:
2478
- $clear = ['tr', 'html'];
2479
-
2480
- /* A start tag whose tag name is one of: "th", "td" */
2481
- if ($token['type'] === HTML5_Tokenizer::STARTTAG &&
2482
- ($token['name'] === 'th' || $token['name'] === 'td')) {
2483
- /* Clear the stack back to a table row context. */
2484
- $this->clearStackToTableContext($clear);
2485
-
2486
- /* Insert an HTML element for the token, then switch the insertion
2487
- mode to "in cell". */
2488
- $this->insertElement($token);
2489
- $this->mode = self::IN_CELL;
2490
-
2491
- /* Insert a marker at the end of the list of active formatting
2492
- elements. */
2493
- $this->a_formatting[] = self::MARKER;
2494
-
2495
- /* An end tag whose tag name is "tr" */
2496
- } elseif ($token['type'] === HTML5_Tokenizer::ENDTAG && $token['name'] === 'tr') {
2497
- /* If the stack of open elements does not have an element in table
2498
- scope with the same tag name as the token, this is a parse error.
2499
- Ignore the token. (fragment case) */
2500
- if (!$this->elementInScope($token['name'], self::SCOPE_TABLE)) {
2501
- // Ignore.
2502
- $this->ignored = true;
2503
- } else {
2504
- /* Clear the stack back to a table row context. */
2505
- $this->clearStackToTableContext($clear);
2506
-
2507
- /* Pop the current node (which will be a tr element) from the
2508
- stack of open elements. Switch the insertion mode to "in table
2509
- body". */
2510
- array_pop($this->stack);
2511
- $this->mode = self::IN_TABLE_BODY;
2512
- }
2513
-
2514
- /* A start tag whose tag name is one of: "caption", "col", "colgroup",
2515
- "tbody", "tfoot", "thead", "tr" or an end tag whose tag name is "table" */
2516
- } elseif (($token['type'] === HTML5_Tokenizer::STARTTAG && in_array($token['name'],
2517
- ['caption', 'col', 'colgroup', 'tbody', 'tfoot', 'thead', 'tr'])) ||
2518
- ($token['type'] === HTML5_Tokenizer::ENDTAG && $token['name'] === 'table')) {
2519
- /* Act as if an end tag with the tag name "tr" had been seen, then,
2520
- if that token wasn't ignored, reprocess the current token. */
2521
- $this->emitToken([
2522
- 'name' => 'tr',
2523
- 'type' => HTML5_Tokenizer::ENDTAG
2524
- ]);
2525
- if (!$this->ignored) {
2526
- $this->emitToken($token);
2527
- }
2528
-
2529
- /* An end tag whose tag name is one of: "tbody", "tfoot", "thead" */
2530
- } elseif ($token['type'] === HTML5_Tokenizer::ENDTAG &&
2531
- in_array($token['name'], ['tbody', 'tfoot', 'thead'])) {
2532
- /* If the stack of open elements does not have an element in table
2533
- scope with the same tag name as the token, this is a parse error.
2534
- Ignore the token. */
2535
- if (!$this->elementInScope($token['name'], self::SCOPE_TABLE)) {
2536
- $this->ignored = true;
2537
-
2538
- /* Otherwise: */
2539
- } else {
2540
- /* Otherwise, act as if an end tag with the tag name "tr" had
2541
- been seen, then reprocess the current token. */
2542
- $this->emitToken([
2543
- 'name' => 'tr',
2544
- 'type' => HTML5_Tokenizer::ENDTAG
2545
- ]);
2546
-
2547
- $this->emitToken($token);
2548
- }
2549
-
2550
- /* An end tag whose tag name is one of: "body", "caption", "col",
2551
- "colgroup", "html", "td", "th" */
2552
- } elseif ($token['type'] === HTML5_Tokenizer::ENDTAG && in_array($token['name'],
2553
- ['body', 'caption', 'col', 'colgroup', 'html', 'td', 'th'])) {
2554
- /* Parse error. Ignore the token. */
2555
- $this->ignored = true;
2556
-
2557
- /* Anything else */
2558
- } else {
2559
- /* Process the token as if the insertion mode was "in table". */
2560
- $this->processWithRulesFor($token, self::IN_TABLE);
2561
- }
2562
- break;
2563
-
2564
- case self::IN_CELL:
2565
- /* An end tag whose tag name is one of: "td", "th" */
2566
- if ($token['type'] === HTML5_Tokenizer::ENDTAG &&
2567
- ($token['name'] === 'td' || $token['name'] === 'th')) {
2568
- /* If the stack of open elements does not have an element in table
2569
- scope with the same tag name as that of the token, then this is a
2570
- parse error and the token must be ignored. */
2571
- if (!$this->elementInScope($token['name'], self::SCOPE_TABLE)) {
2572
- $this->ignored = true;
2573
-
2574
- /* Otherwise: */
2575
- } else {
2576
- /* Generate implied end tags, except for elements with the same
2577
- tag name as the token. */
2578
- $this->generateImpliedEndTags([$token['name']]);
2579
-
2580
- /* Now, if the current node is not an element with the same tag
2581
- name as the token, then this is a parse error. */
2582
- // XERROR: Implement parse error code
2583
-
2584
- /* Pop elements from this stack until an element with the same
2585
- tag name as the token has been popped from the stack. */
2586
- do {
2587
- $node = array_pop($this->stack);
2588
- } while ($node->tagName !== $token['name']);
2589
-
2590
- /* Clear the list of active formatting elements up to the last
2591
- marker. */
2592
- $this->clearTheActiveFormattingElementsUpToTheLastMarker();
2593
-
2594
- /* Switch the insertion mode to "in row". (The current node
2595
- will be a tr element at this point.) */
2596
- $this->mode = self::IN_ROW;
2597
- }
2598
-
2599
- /* A start tag whose tag name is one of: "caption", "col", "colgroup",
2600
- "tbody", "td", "tfoot", "th", "thead", "tr" */
2601
- } elseif ($token['type'] === HTML5_Tokenizer::STARTTAG && in_array($token['name'],
2602
- ['caption', 'col', 'colgroup', 'tbody', 'td', 'tfoot', 'th',
2603
- 'thead', 'tr'])) {
2604
- /* If the stack of open elements does not have a td or th element
2605
- in table scope, then this is a parse error; ignore the token.
2606
- (fragment case) */
2607
- if (!$this->elementInScope(['td', 'th'], self::SCOPE_TABLE)) {
2608
- // parse error
2609
- $this->ignored = true;
2610
-
2611
- /* Otherwise, close the cell (see below) and reprocess the current
2612
- token. */
2613
- } else {
2614
- $this->closeCell();
2615
- $this->emitToken($token);
2616
- }
2617
-
2618
- /* An end tag whose tag name is one of: "body", "caption", "col",
2619
- "colgroup", "html" */
2620
- } elseif ($token['type'] === HTML5_Tokenizer::ENDTAG && in_array($token['name'],
2621
- ['body', 'caption', 'col', 'colgroup', 'html'])) {
2622
- /* Parse error. Ignore the token. */
2623
- $this->ignored = true;
2624
-
2625
- /* An end tag whose tag name is one of: "table", "tbody", "tfoot",
2626
- "thead", "tr" */
2627
- } elseif ($token['type'] === HTML5_Tokenizer::ENDTAG && in_array($token['name'],
2628
- ['table', 'tbody', 'tfoot', 'thead', 'tr'])) {
2629
- /* If the stack of open elements does not have a td or th element
2630
- in table scope, then this is a parse error; ignore the token.
2631
- (innerHTML case) */
2632
- if (!$this->elementInScope(['td', 'th'], self::SCOPE_TABLE)) {
2633
- // Parse error
2634
- $this->ignored = true;
2635
-
2636
- /* Otherwise, close the cell (see below) and reprocess the current
2637
- token. */
2638
- } else {
2639
- $this->closeCell();
2640
- $this->emitToken($token);
2641
- }
2642
-
2643
- /* Anything else */
2644
- } else {
2645
- /* Process the token as if the insertion mode was "in body". */
2646
- $this->processWithRulesFor($token, self::IN_BODY);
2647
- }
2648
- break;
2649
-
2650
- case self::IN_SELECT:
2651
- /* Handle the token as follows: */
2652
-
2653
- /* A character token */
2654
- if (
2655
- $token['type'] === HTML5_Tokenizer::CHARACTER ||
2656
- $token['type'] === HTML5_Tokenizer::SPACECHARACTER
2657
- ) {
2658
- /* Append the token's character to the current node. */
2659
- $this->insertText($token['data']);
2660
-
2661
- /* A comment token */
2662
- } elseif ($token['type'] === HTML5_Tokenizer::COMMENT) {
2663
- /* Append a Comment node to the current node with the data
2664
- attribute set to the data given in the comment token. */
2665
- $this->insertComment($token['data']);
2666
-
2667
- } elseif ($token['type'] === HTML5_Tokenizer::DOCTYPE) {
2668
- // parse error
2669
-
2670
- } elseif ($token['type'] === HTML5_Tokenizer::STARTTAG && $token['name'] === 'html') {
2671
- $this->processWithRulesFor($token, self::IN_BODY);
2672
-
2673
- /* A start tag token whose tag name is "option" */
2674
- } elseif ($token['type'] === HTML5_Tokenizer::STARTTAG &&
2675
- $token['name'] === 'option') {
2676
- /* If the current node is an option element, act as if an end tag
2677
- with the tag name "option" had been seen. */
2678
- if (end($this->stack)->tagName === 'option') {
2679
- $this->emitToken([
2680
- 'name' => 'option',
2681
- 'type' => HTML5_Tokenizer::ENDTAG
2682
- ]);
2683
- }
2684
-
2685
- /* Insert an HTML element for the token. */
2686
- $this->insertElement($token);
2687
-
2688
- /* A start tag token whose tag name is "optgroup" */
2689
- } elseif ($token['type'] === HTML5_Tokenizer::STARTTAG &&
2690
- $token['name'] === 'optgroup') {
2691
- /* If the current node is an option element, act as if an end tag
2692
- with the tag name "option" had been seen. */
2693
- if (end($this->stack)->tagName === 'option') {
2694
- $this->emitToken([
2695
- 'name' => 'option',
2696
- 'type' => HTML5_Tokenizer::ENDTAG
2697
- ]);
2698
- }
2699
-
2700
- /* If the current node is an optgroup element, act as if an end tag
2701
- with the tag name "optgroup" had been seen. */
2702
- if (end($this->stack)->tagName === 'optgroup') {
2703
- $this->emitToken([
2704
- 'name' => 'optgroup',
2705
- 'type' => HTML5_Tokenizer::ENDTAG
2706
- ]);
2707
- }
2708
-
2709
- /* Insert an HTML element for the token. */
2710
- $this->insertElement($token);
2711
-
2712
- /* An end tag token whose tag name is "optgroup" */
2713
- } elseif ($token['type'] === HTML5_Tokenizer::ENDTAG &&
2714
- $token['name'] === 'optgroup') {
2715
- /* First, if the current node is an option element, and the node
2716
- immediately before it in the stack of open elements is an optgroup
2717
- element, then act as if an end tag with the tag name "option" had
2718
- been seen. */
2719
- $elements_in_stack = count($this->stack);
2720
-
2721
- if ($this->stack[$elements_in_stack - 1]->tagName === 'option' &&
2722
- $this->stack[$elements_in_stack - 2]->tagName === 'optgroup') {
2723
- $this->emitToken([
2724
- 'name' => 'option',
2725
- 'type' => HTML5_Tokenizer::ENDTAG
2726
- ]);
2727
- }
2728
-
2729
- /* If the current node is an optgroup element, then pop that node
2730
- from the stack of open elements. Otherwise, this is a parse error,
2731
- ignore the token. */
2732
- if (end($this->stack)->tagName === 'optgroup') {
2733
- array_pop($this->stack);
2734
- } else {
2735
- // parse error
2736
- $this->ignored = true;
2737
- }
2738
-
2739
- /* An end tag token whose tag name is "option" */
2740
- } elseif ($token['type'] === HTML5_Tokenizer::ENDTAG &&
2741
- $token['name'] === 'option') {
2742
- /* If the current node is an option element, then pop that node
2743
- from the stack of open elements. Otherwise, this is a parse error,
2744
- ignore the token. */
2745
- if (end($this->stack)->tagName === 'option') {
2746
- array_pop($this->stack);
2747
- } else {
2748
- // parse error
2749
- $this->ignored = true;
2750
- }
2751
-
2752
- /* An end tag whose tag name is "select" */
2753
- } elseif ($token['type'] === HTML5_Tokenizer::ENDTAG &&
2754
- $token['name'] === 'select') {
2755
- /* If the stack of open elements does not have an element in table
2756
- scope with the same tag name as the token, this is a parse error.
2757
- Ignore the token. (fragment case) */
2758
- if (!$this->elementInScope($token['name'], self::SCOPE_TABLE)) {
2759
- $this->ignored = true;
2760
- // parse error
2761
-
2762
- /* Otherwise: */
2763
- } else {
2764
- /* Pop elements from the stack of open elements until a select
2765
- element has been popped from the stack. */
2766
- do {
2767
- $node = array_pop($this->stack);
2768
- } while ($node->tagName !== 'select');
2769
-
2770
- /* Reset the insertion mode appropriately. */
2771
- $this->resetInsertionMode();
2772
- }
2773
-
2774
- /* A start tag whose tag name is "select" */
2775
- } elseif ($token['type'] === HTML5_Tokenizer::STARTTAG && $token['name'] === 'select') {
2776
- /* Parse error. Act as if the token had been an end tag with the
2777
- tag name "select" instead. */
2778
- $this->emitToken([
2779
- 'name' => 'select',
2780
- 'type' => HTML5_Tokenizer::ENDTAG
2781
- ]);
2782
-
2783
- } elseif ($token['type'] === HTML5_Tokenizer::STARTTAG &&
2784
- ($token['name'] === 'input' || $token['name'] === 'keygen' || $token['name'] === 'textarea')) {
2785
- // parse error
2786
- $this->emitToken([
2787
- 'name' => 'select',
2788
- 'type' => HTML5_Tokenizer::ENDTAG
2789
- ]);
2790
- $this->emitToken($token);
2791
-
2792
- } elseif ($token['type'] === HTML5_Tokenizer::STARTTAG && $token['name'] === 'script') {
2793
- $this->processWithRulesFor($token, self::IN_HEAD);
2794
-
2795
- } elseif ($token['type'] === HTML5_Tokenizer::EOF) {
2796
- // XERROR: If the current node is not the root html element, then this is a parse error.
2797
- /* Stop parsing */
2798
-
2799
- /* Anything else */
2800
- } else {
2801
- /* Parse error. Ignore the token. */
2802
- $this->ignored = true;
2803
- }
2804
- break;
2805
-
2806
- case self::IN_SELECT_IN_TABLE:
2807
-
2808
- if ($token['type'] === HTML5_Tokenizer::STARTTAG &&
2809
- in_array($token['name'], ['caption', 'table', 'tbody',
2810
- 'tfoot', 'thead', 'tr', 'td', 'th'])) {
2811
- // parse error
2812
- $this->emitToken([
2813
- 'name' => 'select',
2814
- 'type' => HTML5_Tokenizer::ENDTAG,
2815
- ]);
2816
- $this->emitToken($token);
2817
-
2818
- /* An end tag whose tag name is one of: "caption", "table", "tbody",
2819
- "tfoot", "thead", "tr", "td", "th" */
2820
- } elseif ($token['type'] === HTML5_Tokenizer::ENDTAG &&
2821
- in_array($token['name'], ['caption', 'table', 'tbody', 'tfoot', 'thead', 'tr', 'td', 'th'])) {
2822
- /* Parse error. */
2823
- // parse error
2824
-
2825
- /* If the stack of open elements has an element in table scope with
2826
- the same tag name as that of the token, then act as if an end tag
2827
- with the tag name "select" had been seen, and reprocess the token.
2828
- Otherwise, ignore the token. */
2829
- if ($this->elementInScope($token['name'], self::SCOPE_TABLE)) {
2830
- $this->emitToken([
2831
- 'name' => 'select',
2832
- 'type' => HTML5_Tokenizer::ENDTAG
2833
- ]);
2834
-
2835
- $this->emitToken($token);
2836
- } else {
2837
- $this->ignored = true;
2838
- }
2839
- } else {
2840
- $this->processWithRulesFor($token, self::IN_SELECT);
2841
- }
2842
- break;
2843
-
2844
- case self::IN_FOREIGN_CONTENT:
2845
- if ($token['type'] === HTML5_Tokenizer::CHARACTER ||
2846
- $token['type'] === HTML5_Tokenizer::SPACECHARACTER) {
2847
- $this->insertText($token['data']);
2848
- } elseif ($token['type'] === HTML5_Tokenizer::COMMENT) {
2849
- $this->insertComment($token['data']);
2850
- } elseif ($token['type'] === HTML5_Tokenizer::DOCTYPE) {
2851
- // XERROR: parse error
2852
- } elseif ($token['type'] === HTML5_Tokenizer::ENDTAG &&
2853
- $token['name'] === 'script' && end($this->stack)->tagName === 'script' &&
2854
- // XDOM
2855
- end($this->stack)->namespaceURI === self::NS_SVG) {
2856
- array_pop($this->stack);
2857
- // a bunch of script running mumbo jumbo
2858
- } elseif (
2859
- ($token['type'] === HTML5_Tokenizer::STARTTAG &&
2860
- ((
2861
- $token['name'] !== 'mglyph' &&
2862
- $token['name'] !== 'malignmark' &&
2863
- // XDOM
2864
- end($this->stack)->namespaceURI === self::NS_MATHML &&
2865
- in_array(end($this->stack)->tagName, ['mi', 'mo', 'mn', 'ms', 'mtext'])
2866
- ) ||
2867
- (
2868
- $token['name'] === 'svg' &&
2869
- // XDOM
2870
- end($this->stack)->namespaceURI === self::NS_MATHML &&
2871
- end($this->stack)->tagName === 'annotation-xml'
2872
- ) ||
2873
- (
2874
- // XDOM
2875
- end($this->stack)->namespaceURI === self::NS_SVG &&
2876
- in_array(end($this->stack)->tagName, ['foreignObject', 'desc', 'title'])
2877
- ) ||
2878
- (
2879
- // XSKETCHY && XDOM
2880
- end($this->stack)->namespaceURI === self::NS_HTML
2881
- ))
2882
- ) || $token['type'] === HTML5_Tokenizer::ENDTAG
2883
- ) {
2884
- $this->processWithRulesFor($token, $this->secondary_mode);
2885
- /* If, after doing so, the insertion mode is still "in foreign
2886
- * content", but there is no element in scope that has a namespace
2887
- * other than the HTML namespace, switch the insertion mode to the
2888
- * secondary insertion mode. */
2889
- if ($this->mode === self::IN_FOREIGN_CONTENT) {
2890
- $found = false;
2891
- // this basically duplicates elementInScope()
2892
- for ($i = count($this->stack) - 1; $i >= 0; $i--) {
2893
- // XDOM
2894
- $node = $this->stack[$i];
2895
- if ($node->namespaceURI !== self::NS_HTML) {
2896
- $found = true;
2897
- break;
2898
- } elseif (in_array($node->tagName, ['table', 'html',
2899
- 'applet', 'caption', 'td', 'th', 'button', 'marquee',
2900
- 'object']) || ($node->tagName === 'foreignObject' &&
2901
- $node->namespaceURI === self::NS_SVG)) {
2902
- break;
2903
- }
2904
- }
2905
- if (!$found) {
2906
- $this->mode = $this->secondary_mode;
2907
- }
2908
- }
2909
- } elseif ($token['type'] === HTML5_Tokenizer::EOF || (
2910
- $token['type'] === HTML5_Tokenizer::STARTTAG &&
2911
- (in_array($token['name'], ['b', "big", "blockquote", "body", "br",
2912
- "center", "code", "dc", "dd", "div", "dl", "ds", "dt", "em", "embed", "h1", "h2",
2913
- "h3", "h4", "h5", "h6", "head", "hr", "i", "img", "li", "listing",
2914
- "menu", "meta", "nobr", "ol", "p", "pre", "ruby", "s", "small",
2915
- "span", "strong", "strike", "sub", "sup", "table", "tt", "u", "ul",
2916
- "var"]) || ($token['name'] === 'font' && ($this->getAttr($token, 'color') ||
2917
- $this->getAttr($token, 'face') || $this->getAttr($token, 'size')))))) {
2918
- // XERROR: parse error
2919
- do {
2920
- $node = array_pop($this->stack);
2921
- // XDOM
2922
- } while ($node->namespaceURI !== self::NS_HTML);
2923
- $this->stack[] = $node;
2924
- $this->mode = $this->secondary_mode;
2925
- $this->emitToken($token);
2926
- } elseif ($token['type'] === HTML5_Tokenizer::STARTTAG) {
2927
- static $svg_lookup = [
2928
- 'altglyph' => 'altGlyph',
2929
- 'altglyphdef' => 'altGlyphDef',
2930
- 'altglyphitem' => 'altGlyphItem',
2931
- 'animatecolor' => 'animateColor',
2932
- 'animatemotion' => 'animateMotion',
2933
- 'animatetransform' => 'animateTransform',
2934
- 'clippath' => 'clipPath',
2935
- 'feblend' => 'feBlend',
2936
- 'fecolormatrix' => 'feColorMatrix',
2937
- 'fecomponenttransfer' => 'feComponentTransfer',
2938
- 'fecomposite' => 'feComposite',
2939
- 'feconvolvematrix' => 'feConvolveMatrix',
2940
- 'fediffuselighting' => 'feDiffuseLighting',
2941
- 'fedisplacementmap' => 'feDisplacementMap',
2942
- 'fedistantlight' => 'feDistantLight',
2943
- 'feflood' => 'feFlood',
2944
- 'fefunca' => 'feFuncA',
2945
- 'fefuncb' => 'feFuncB',
2946
- 'fefuncg' => 'feFuncG',
2947
- 'fefuncr' => 'feFuncR',
2948
- 'fegaussianblur' => 'feGaussianBlur',
2949
- 'feimage' => 'feImage',
2950
- 'femerge' => 'feMerge',
2951
- 'femergenode' => 'feMergeNode',
2952
- 'femorphology' => 'feMorphology',
2953
- 'feoffset' => 'feOffset',
2954
- 'fepointlight' => 'fePointLight',
2955
- 'fespecularlighting' => 'feSpecularLighting',
2956
- 'fespotlight' => 'feSpotLight',
2957
- 'fetile' => 'feTile',
2958
- 'feturbulence' => 'feTurbulence',
2959
- 'foreignobject' => 'foreignObject',
2960
- 'glyphref' => 'glyphRef',
2961
- 'lineargradient' => 'linearGradient',
2962
- 'radialgradient' => 'radialGradient',
2963
- 'textpath' => 'textPath',
2964
- ];
2965
- // XDOM
2966
- $current = end($this->stack);
2967
- if ($current->namespaceURI === self::NS_MATHML) {
2968
- $token = $this->adjustMathMLAttributes($token);
2969
- }
2970
- if ($current->namespaceURI === self::NS_SVG &&
2971
- isset($svg_lookup[$token['name']])) {
2972
- $token['name'] = $svg_lookup[$token['name']];
2973
- }
2974
- if ($current->namespaceURI === self::NS_SVG) {
2975
- $token = $this->adjustSVGAttributes($token);
2976
- }
2977
- $token = $this->adjustForeignAttributes($token);
2978
- $this->insertForeignElement($token, $current->namespaceURI);
2979
- if (isset($token['self-closing'])) {
2980
- array_pop($this->stack);
2981
- // XERROR: acknowledge self-closing flag
2982
- }
2983
- }
2984
- break;
2985
-
2986
- case self::AFTER_BODY:
2987
- /* Handle the token as follows: */
2988
-
2989
- /* A character token that is one of one of U+0009 CHARACTER TABULATION,
2990
- U+000A LINE FEED (LF), U+000B LINE TABULATION, U+000C FORM FEED (FF),
2991
- or U+0020 SPACE */
2992
- if ($token['type'] === HTML5_Tokenizer::SPACECHARACTER) {
2993
- /* Process the token as it would be processed if the insertion mode
2994
- was "in body". */
2995
- $this->processWithRulesFor($token, self::IN_BODY);
2996
-
2997
- /* A comment token */
2998
- } elseif ($token['type'] === HTML5_Tokenizer::COMMENT) {
2999
- /* Append a Comment node to the first element in the stack of open
3000
- elements (the html element), with the data attribute set to the
3001
- data given in the comment token. */
3002
- // XDOM
3003
- $comment = $this->dom->createComment($token['data']);
3004
- $this->stack[0]->appendChild($comment);
3005
-
3006
- } elseif ($token['type'] === HTML5_Tokenizer::DOCTYPE) {
3007
- // parse error
3008
-
3009
- } elseif ($token['type'] === HTML5_Tokenizer::STARTTAG && $token['name'] === 'html') {
3010
- $this->processWithRulesFor($token, self::IN_BODY);
3011
-
3012
- /* An end tag with the tag name "html" */
3013
- } elseif ($token['type'] === HTML5_Tokenizer::ENDTAG && $token['name'] === 'html') {
3014
- /* If the parser was originally created as part of the HTML
3015
- * fragment parsing algorithm, this is a parse error; ignore
3016
- * the token. (fragment case) */
3017
- $this->ignored = true;
3018
- // XERROR: implement this
3019
-
3020
- $this->mode = self::AFTER_AFTER_BODY;
3021
-
3022
- } elseif ($token['type'] === HTML5_Tokenizer::EOF) {
3023
- /* Stop parsing */
3024
-
3025
- /* Anything else */
3026
- } else {
3027
- /* Parse error. Set the insertion mode to "in body" and reprocess
3028
- the token. */
3029
- $this->mode = self::IN_BODY;
3030
- $this->emitToken($token);
3031
- }
3032
- break;
3033
-
3034
- case self::IN_FRAMESET:
3035
- /* Handle the token as follows: */
3036
-
3037
- /* A character token that is one of one of U+0009 CHARACTER TABULATION,
3038
- U+000A LINE FEED (LF), U+000B LINE TABULATION, U+000C FORM FEED (FF),
3039
- U+000D CARRIAGE RETURN (CR), or U+0020 SPACE */
3040
- if ($token['type'] === HTML5_Tokenizer::SPACECHARACTER) {
3041
- /* Append the character to the current node. */
3042
- $this->insertText($token['data']);
3043
-
3044
- /* A comment token */
3045
- } elseif ($token['type'] === HTML5_Tokenizer::COMMENT) {
3046
- /* Append a Comment node to the current node with the data
3047
- attribute set to the data given in the comment token. */
3048
- $this->insertComment($token['data']);
3049
-
3050
- } elseif ($token['type'] === HTML5_Tokenizer::DOCTYPE) {
3051
- // parse error
3052
-
3053
- /* A start tag with the tag name "frameset" */
3054
- } elseif ($token['type'] === HTML5_Tokenizer::STARTTAG &&
3055
- $token['name'] === 'frameset') {
3056
- $this->insertElement($token);
3057
-
3058
- /* An end tag with the tag name "frameset" */
3059
- } elseif ($token['type'] === HTML5_Tokenizer::ENDTAG &&
3060
- $token['name'] === 'frameset') {
3061
- /* If the current node is the root html element, then this is a
3062
- parse error; ignore the token. (fragment case) */
3063
- if (end($this->stack)->tagName === 'html') {
3064
- $this->ignored = true;
3065
- // Parse error
3066
-
3067
- } else {
3068
- /* Otherwise, pop the current node from the stack of open
3069
- elements. */
3070
- array_pop($this->stack);
3071
-
3072
- /* If the parser was not originally created as part of the HTML
3073
- * fragment parsing algorithm (fragment case), and the current
3074
- * node is no longer a frameset element, then switch the
3075
- * insertion mode to "after frameset". */
3076
- $this->mode = self::AFTER_FRAMESET;
3077
- }
3078
-
3079
- /* A start tag with the tag name "frame" */
3080
- } elseif ($token['type'] === HTML5_Tokenizer::STARTTAG &&
3081
- $token['name'] === 'frame') {
3082
- /* Insert an HTML element for the token. */
3083
- $this->insertElement($token);
3084
-
3085
- /* Immediately pop the current node off the stack of open elements. */
3086
- array_pop($this->stack);
3087
-
3088
- // XERROR: Acknowledge the token's self-closing flag, if it is set.
3089
-
3090
- /* A start tag with the tag name "noframes" */
3091
- } elseif ($token['type'] === HTML5_Tokenizer::STARTTAG &&
3092
- $token['name'] === 'noframes') {
3093
- /* Process the token using the rules for the "in head" insertion mode. */
3094
- $this->processwithRulesFor($token, self::IN_HEAD);
3095
-
3096
- } elseif ($token['type'] === HTML5_Tokenizer::EOF) {
3097
- // XERROR: If the current node is not the root html element, then this is a parse error.
3098
- /* Stop parsing */
3099
- /* Anything else */
3100
- } else {
3101
- /* Parse error. Ignore the token. */
3102
- $this->ignored = true;
3103
- }
3104
- break;
3105
-
3106
- case self::AFTER_FRAMESET:
3107
- /* Handle the token as follows: */
3108
-
3109
- /* A character token that is one of one of U+0009 CHARACTER TABULATION,
3110
- U+000A LINE FEED (LF), U+000B LINE TABULATION, U+000C FORM FEED (FF),
3111
- U+000D CARRIAGE RETURN (CR), or U+0020 SPACE */
3112
- if ($token['type'] === HTML5_Tokenizer::SPACECHARACTER) {
3113
- /* Append the character to the current node. */
3114
- $this->insertText($token['data']);
3115
-
3116
- /* A comment token */
3117
- } elseif ($token['type'] === HTML5_Tokenizer::COMMENT) {
3118
- /* Append a Comment node to the current node with the data
3119
- attribute set to the data given in the comment token. */
3120
- $this->insertComment($token['data']);
3121
-
3122
- } elseif ($token['type'] === HTML5_Tokenizer::DOCTYPE) {
3123
- // parse error
3124
-
3125
- } elseif ($token['type'] === HTML5_Tokenizer::STARTTAG && $token['name'] === 'html') {
3126
- $this->processWithRulesFor($token, self::IN_BODY);
3127
-
3128
- /* An end tag with the tag name "html" */
3129
- } elseif ($token['type'] === HTML5_Tokenizer::ENDTAG &&
3130
- $token['name'] === 'html') {
3131
- $this->mode = self::AFTER_AFTER_FRAMESET;
3132
-
3133
- /* A start tag with the tag name "noframes" */
3134
- } elseif ($token['type'] === HTML5_Tokenizer::STARTTAG &&
3135
- $token['name'] === 'noframes') {
3136
- $this->processWithRulesFor($token, self::IN_HEAD);
3137
-
3138
- } elseif ($token['type'] === HTML5_Tokenizer::EOF) {
3139
- /* Stop parsing */
3140
-
3141
- /* Anything else */
3142
- } else {
3143
- /* Parse error. Ignore the token. */
3144
- $this->ignored = true;
3145
- }
3146
- break;
3147
-
3148
- case self::AFTER_AFTER_BODY:
3149
- /* A comment token */
3150
- if ($token['type'] === HTML5_Tokenizer::COMMENT) {
3151
- /* Append a Comment node to the Document object with the data
3152
- attribute set to the data given in the comment token. */
3153
- // XDOM
3154
- $comment = $this->dom->createComment($token['data']);
3155
- $this->dom->appendChild($comment);
3156
-
3157
- } elseif ($token['type'] === HTML5_Tokenizer::DOCTYPE ||
3158
- $token['type'] === HTML5_Tokenizer::SPACECHARACTER ||
3159
- ($token['type'] === HTML5_Tokenizer::STARTTAG && $token['name'] === 'html')) {
3160
- $this->processWithRulesFor($token, self::IN_BODY);
3161
-
3162
- /* An end-of-file token */
3163
- } elseif ($token['type'] === HTML5_Tokenizer::EOF) {
3164
- /* OMG DONE!! */
3165
- } else {
3166
- // parse error
3167
- $this->mode = self::IN_BODY;
3168
- $this->emitToken($token);
3169
- }
3170
- break;
3171
-
3172
- case self::AFTER_AFTER_FRAMESET:
3173
- /* A comment token */
3174
- if ($token['type'] === HTML5_Tokenizer::COMMENT) {
3175
- /* Append a Comment node to the Document object with the data
3176
- attribute set to the data given in the comment token. */
3177
- // XDOM
3178
- $comment = $this->dom->createComment($token['data']);
3179
- $this->dom->appendChild($comment);
3180
- } elseif ($token['type'] === HTML5_Tokenizer::DOCTYPE ||
3181
- $token['type'] === HTML5_Tokenizer::SPACECHARACTER ||
3182
- ($token['type'] === HTML5_Tokenizer::STARTTAG && $token['name'] === 'html')) {
3183
- $this->processWithRulesFor($token, self::IN_BODY);
3184
-
3185
- /* An end-of-file token */
3186
- } elseif ($token['type'] === HTML5_Tokenizer::EOF) {
3187
- /* OMG DONE!! */
3188
- } elseif ($token['type'] === HTML5_Tokenizer::STARTTAG && $token['name'] === 'nofrmaes') {
3189
- $this->processWithRulesFor($token, self::IN_HEAD);
3190
- } else {
3191
- // parse error
3192
- }
3193
- break;
3194
- }
3195
- }
3196
-
3197
- private function insertElement($token, $append = true) {
3198
- $el = $this->dom->createElementNS(self::NS_HTML, $token['name']);
3199
-
3200
- if (!empty($token['attr'])) {
3201
- foreach ($token['attr'] as $attr) {
3202
- if (!$el->hasAttribute($attr['name']) && preg_match("/^[a-zA-Z_:]/", $attr['name'])) {
3203
- $el->setAttribute($attr['name'], $attr['value']);
3204
- }
3205
- }
3206
- }
3207
- if ($append) {
3208
- $this->appendToRealParent($el);
3209
- $this->stack[] = $el;
3210
- }
3211
-
3212
- return $el;
3213
- }
3214
-
3215
- /**
3216
- * @param $data
3217
- */
3218
- private function insertText($data) {
3219
- if ($data === '') {
3220
- return;
3221
- }
3222
- if ($this->ignore_lf_token) {
3223
- if ($data[0] === "\n") {
3224
- $data = substr($data, 1);
3225
- if ($data === false) {
3226
- return;
3227
- }
3228
- }
3229
- }
3230
- $text = $this->dom->createTextNode($data);
3231
- $this->appendToRealParent($text);
3232
- }
3233
-
3234
- /**
3235
- * @param $data
3236
- */
3237
- private function insertComment($data) {
3238
- $comment = $this->dom->createComment($data);
3239
- $this->appendToRealParent($comment);
3240
- }
3241
-
3242
- /**
3243
- * @param $node
3244
- */
3245
- private function appendToRealParent($node) {
3246
- // this is only for the foster_parent case
3247
- /* If the current node is a table, tbody, tfoot, thead, or tr
3248
- element, then, whenever a node would be inserted into the current
3249
- node, it must instead be inserted into the foster parent element. */
3250
- if (
3251
- !$this->foster_parent ||
3252
- !in_array(
3253
- end($this->stack)->tagName,
3254
- ['table', 'tbody', 'tfoot', 'thead', 'tr']
3255
- )
3256
- ) {
3257
- end($this->stack)->appendChild($node);
3258
- } else {
3259
- $this->fosterParent($node);
3260
- }
3261
- }
3262
-
3263
- /**
3264
- * @param $el
3265
- * @param int $scope
3266
- * @return bool|null
3267
- */
3268
- private function elementInScope($el, $scope = self::SCOPE) {
3269
- if (is_array($el)) {
3270
- foreach($el as $element) {
3271
- if ($this->elementInScope($element, $scope)) {
3272
- return true;
3273
- }
3274
- }
3275
-
3276
- return false;
3277
- }
3278
-
3279
- $leng = count($this->stack);
3280
-
3281
- for ($n = 0; $n < $leng; $n++) {
3282
- /* 1. Initialise node to be the current node (the bottommost node of
3283
- the stack). */
3284
- $node = $this->stack[$leng - 1 - $n];
3285
-
3286
- if ($node->tagName === $el) {
3287
- /* 2. If node is the target node, terminate in a match state. */
3288
- return true;
3289
-
3290
- // We've expanded the logic for these states a little differently;
3291
- // Hixie's refactoring into "specific scope" is more general, but
3292
- // this "gets the job done"
3293
-
3294
- // these are the common states for all scopes
3295
- } elseif ($node->tagName === 'table' || $node->tagName === 'html') {
3296
- return false;
3297
-
3298
- // these are valid for "in scope" and "in list item scope"
3299
- } elseif ($scope !== self::SCOPE_TABLE &&
3300
- (in_array($node->tagName, ['applet', 'caption', 'td',
3301
- 'th', 'button', 'marquee', 'object']) ||
3302
- $node->tagName === 'foreignObject' && $node->namespaceURI === self::NS_SVG)) {
3303
- return false;
3304
-
3305
-
3306
- // these are valid for "in list item scope"
3307
- } elseif ($scope === self::SCOPE_LISTITEM && in_array($node->tagName, ['ol', 'ul'])) {
3308
- return false;
3309
- }
3310
-
3311
- /* Otherwise, set node to the previous entry in the stack of open
3312
- elements and return to step 2. (This will never fail, since the loop
3313
- will always terminate in the previous step if the top of the stack
3314
- is reached.) */
3315
- }
3316
-
3317
- // To fix warning. This never happens or should return true/false
3318
- return null;
3319
- }
3320
-
3321
- /**
3322
- * @return bool
3323
- */
3324
- private function reconstructActiveFormattingElements() {
3325
- /* 1. If there are no entries in the list of active formatting elements,
3326
- then there is nothing to reconstruct; stop this algorithm. */
3327
- $formatting_elements = count($this->a_formatting);
3328
-
3329
- if ($formatting_elements === 0) {
3330
- return false;
3331
- }
3332
-
3333
- /* 3. Let entry be the last (most recently added) element in the list
3334
- of active formatting elements. */
3335
- $entry = end($this->a_formatting);
3336
-
3337
- /* 2. If the last (most recently added) entry in the list of active
3338
- formatting elements is a marker, or if it is an element that is in the
3339
- stack of open elements, then there is nothing to reconstruct; stop this
3340
- algorithm. */
3341
- if ($entry === self::MARKER || in_array($entry, $this->stack, true)) {
3342
- return false;
3343
- }
3344
-
3345
- for ($a = $formatting_elements - 1; $a >= 0; true) {
3346
- /* 4. If there are no entries before entry in the list of active
3347
- formatting elements, then jump to step 8. */
3348
- if ($a === 0) {
3349
- $step_seven = false;
3350
- break;
3351
- }
3352
-
3353
- /* 5. Let entry be the entry one earlier than entry in the list of
3354
- active formatting elements. */
3355
- $a--;
3356
- $entry = $this->a_formatting[$a];
3357
-
3358
- /* 6. If entry is neither a marker nor an element that is also in
3359
- thetack of open elements, go to step 4. */
3360
- if ($entry === self::MARKER || in_array($entry, $this->stack, true)) {
3361
- break;
3362
- }
3363
- }
3364
-
3365
- while (true) {
3366
- /* 7. Let entry be the element one later than entry in the list of
3367
- active formatting elements. */
3368
- if (isset($step_seven) && $step_seven === true) {
3369
- $a++;
3370
- $entry = $this->a_formatting[$a];
3371
- }
3372
-
3373
- /* 8. Perform a shallow clone of the element entry to obtain clone. */
3374
- $clone = $entry->cloneNode();
3375
-
3376
- /* 9. Append clone to the current node and push it onto the stack
3377
- of open elements so that it is the new current node. */
3378
- $this->appendToRealParent($clone);
3379
- $this->stack[] = $clone;
3380
-
3381
- /* 10. Replace the entry for entry in the list with an entry for
3382
- clone. */
3383
- $this->a_formatting[$a] = $clone;
3384
-
3385
- /* 11. If the entry for clone in the list of active formatting
3386
- elements is not the last entry in the list, return to step 7. */
3387
- if (end($this->a_formatting) !== $clone) {
3388
- $step_seven = true;
3389
- } else {
3390
- break;
3391
- }
3392
- }
3393
-
3394
- // Return value not in use ATM. Would just make sense to also return true here.
3395
- return true;
3396
- }
3397
-
3398
- /**
3399
- *
3400
- */
3401
- private function clearTheActiveFormattingElementsUpToTheLastMarker() {
3402
- /* When the steps below require the UA to clear the list of active
3403
- formatting elements up to the last marker, the UA must perform the
3404
- following steps: */
3405
-
3406
- while (true) {
3407
- /* 1. Let entry be the last (most recently added) entry in the list
3408
- of active formatting elements. */
3409
- $entry = end($this->a_formatting);
3410
-
3411
- /* 2. Remove entry from the list of active formatting elements. */
3412
- array_pop($this->a_formatting);
3413
-
3414
- /* 3. If entry was a marker, then stop the algorithm at this point.
3415
- The list has been cleared up to the last marker. */
3416
- if ($entry === self::MARKER) {
3417
- break;
3418
- }
3419
- }
3420
- }
3421
-
3422
- /**
3423
- * @param array $exclude
3424
- */
3425
- private function generateImpliedEndTags($exclude = []) {
3426
- /* When the steps below require the UA to generate implied end tags,
3427
- * then, while the current node is a dc element, a dd element, a ds
3428
- * element, a dt element, an li element, an option element, an optgroup
3429
- * element, a p element, an rp element, or an rt element, the UA must
3430
- * pop the current node off the stack of open elements. */
3431
- $node = end($this->stack);
3432
- $elements = array_diff(['dc', 'dd', 'ds', 'dt', 'li', 'p', 'td', 'th', 'tr'], $exclude);
3433
-
3434
- while (in_array(end($this->stack)->tagName, $elements)) {
3435
- array_pop($this->stack);
3436
- }
3437
- }
3438
-
3439
- /**
3440
- * @param $node
3441
- * @return int
3442
- */
3443
- private function getElementCategory($node) {
3444
- if (!is_object($node)) {
3445
- debug_print_backtrace();
3446
- }
3447
- $name = $node->tagName;
3448
- if (in_array($name, $this->special)) {
3449
- return self::SPECIAL;
3450
- } elseif (in_array($name, $this->scoping)) {
3451
- return self::SCOPING;
3452
- } elseif (in_array($name, $this->formatting)) {
3453
- return self::FORMATTING;
3454
- } else {
3455
- return self::PHRASING;
3456
- }
3457
- }
3458
-
3459
- /**
3460
- * @param $elements
3461
- */
3462
- private function clearStackToTableContext($elements) {
3463
- /* When the steps above require the UA to clear the stack back to a
3464
- table context, it means that the UA must, while the current node is not
3465
- a table element or an html element, pop elements from the stack of open
3466
- elements. */
3467
- while (true) {
3468
- $name = end($this->stack)->tagName;
3469
-
3470
- if (in_array($name, $elements)) {
3471
- break;
3472
- } else {
3473
- array_pop($this->stack);
3474
- }
3475
- }
3476
- }
3477
-
3478
- /**
3479
- * @param null $context
3480
- */
3481
- private function resetInsertionMode($context = null) {
3482
- /* 1. Let last be false. */
3483
- $last = false;
3484
- $leng = count($this->stack);
3485
-
3486
- for ($n = $leng - 1; $n >= 0; $n--) {
3487
- /* 2. Let node be the last node in the stack of open elements. */
3488
- $node = $this->stack[$n];
3489
-
3490
- /* 3. If node is the first node in the stack of open elements, then
3491
- * set last to true and set node to the context element. (fragment
3492
- * case) */
3493
- if ($this->stack[0]->isSameNode($node)) {
3494
- $last = true;
3495
- $node = $context;
3496
- }
3497
-
3498
- /* 4. If node is a select element, then switch the insertion mode to
3499
- "in select" and abort these steps. (fragment case) */
3500
- if ($node->tagName === 'select') {
3501
- $this->mode = self::IN_SELECT;
3502
- break;
3503
-
3504
- /* 5. If node is a td or th element, then switch the insertion mode
3505
- to "in cell" and abort these steps. */
3506
- } elseif ($node->tagName === 'td' || $node->nodeName === 'th') {
3507
- $this->mode = self::IN_CELL;
3508
- break;
3509
-
3510
- /* 6. If node is a tr element, then switch the insertion mode to
3511
- "in row" and abort these steps. */
3512
- } elseif ($node->tagName === 'tr') {
3513
- $this->mode = self::IN_ROW;
3514
- break;
3515
-
3516
- /* 7. If node is a tbody, thead, or tfoot element, then switch the
3517
- insertion mode to "in table body" and abort these steps. */
3518
- } elseif (in_array($node->tagName, ['tbody', 'thead', 'tfoot'])) {
3519
- $this->mode = self::IN_TABLE_BODY;
3520
- break;
3521
-
3522
- /* 8. If node is a caption element, then switch the insertion mode
3523
- to "in caption" and abort these steps. */
3524
- } elseif ($node->tagName === 'caption') {
3525
- $this->mode = self::IN_CAPTION;
3526
- break;
3527
-
3528
- /* 9. If node is a colgroup element, then switch the insertion mode
3529
- to "in column group" and abort these steps. (innerHTML case) */
3530
- } elseif ($node->tagName === 'colgroup') {
3531
- $this->mode = self::IN_COLUMN_GROUP;
3532
- break;
3533
-
3534
- /* 10. If node is a table element, then switch the insertion mode
3535
- to "in table" and abort these steps. */
3536
- } elseif ($node->tagName === 'table') {
3537
- $this->mode = self::IN_TABLE;
3538
- break;
3539
-
3540
- /* 11. If node is an element from the MathML namespace or the SVG
3541
- * namespace, then switch the insertion mode to "in foreign
3542
- * content", let the secondary insertion mode be "in body", and
3543
- * abort these steps. */
3544
- } elseif ($node->namespaceURI === self::NS_SVG ||
3545
- $node->namespaceURI === self::NS_MATHML) {
3546
- $this->mode = self::IN_FOREIGN_CONTENT;
3547
- $this->secondary_mode = self::IN_BODY;
3548
- break;
3549
-
3550
- /* 12. If node is a head element, then switch the insertion mode
3551
- to "in body" ("in body"! not "in head"!) and abort these steps.
3552
- (fragment case) */
3553
- } elseif ($node->tagName === 'head') {
3554
- $this->mode = self::IN_BODY;
3555
- break;
3556
-
3557
- /* 13. If node is a body element, then switch the insertion mode to
3558
- "in body" and abort these steps. */
3559
- } elseif ($node->tagName === 'body') {
3560
- $this->mode = self::IN_BODY;
3561
- break;
3562
-
3563
- /* 14. If node is a frameset element, then switch the insertion
3564
- mode to "in frameset" and abort these steps. (fragment case) */
3565
- } elseif ($node->tagName === 'frameset') {
3566
- $this->mode = self::IN_FRAMESET;
3567
- break;
3568
-
3569
- /* 15. If node is an html element, then: if the head element
3570
- pointer is null, switch the insertion mode to "before head",
3571
- otherwise, switch the insertion mode to "after head". In either
3572
- case, abort these steps. (fragment case) */
3573
- } elseif ($node->tagName === 'html') {
3574
- $this->mode = ($this->head_pointer === null)
3575
- ? self::BEFORE_HEAD
3576
- : self::AFTER_HEAD;
3577
-
3578
- break;
3579
-
3580
- /* 16. If last is true, then set the insertion mode to "in body"
3581
- and abort these steps. (fragment case) */
3582
- } elseif ($last) {
3583
- $this->mode = self::IN_BODY;
3584
- break;
3585
- }
3586
- }
3587
- }
3588
-
3589
- /**
3590
- *
3591
- */
3592
- private function closeCell() {
3593
- /* If the stack of open elements has a td or th element in table scope,
3594
- then act as if an end tag token with that tag name had been seen. */
3595
- foreach (['td', 'th'] as $cell) {
3596
- if ($this->elementInScope($cell, self::SCOPE_TABLE)) {
3597
- $this->emitToken([
3598
- 'name' => $cell,
3599
- 'type' => HTML5_Tokenizer::ENDTAG
3600
- ]);
3601
-
3602
- break;
3603
- }
3604
- }
3605
- }
3606
-
3607
- /**
3608
- * @param $token
3609
- * @param $mode
3610
- */
3611
- private function processWithRulesFor($token, $mode) {
3612
- /* "using the rules for the m insertion mode", where m is one of these
3613
- * modes, the user agent must use the rules described under the m
3614
- * insertion mode's section, but must leave the insertion mode
3615
- * unchanged unless the rules in m themselves switch the insertion mode
3616
- * to a new value. */
3617
- $this->emitToken($token, $mode);
3618
- }
3619
-
3620
- /**
3621
- * @param $token
3622
- */
3623
- private function insertCDATAElement($token) {
3624
- $this->insertElement($token);
3625
- $this->original_mode = $this->mode;
3626
- $this->mode = self::IN_CDATA_RCDATA;
3627
- $this->content_model = HTML5_Tokenizer::CDATA;
3628
- }
3629
-
3630
- /**
3631
- * @param $token
3632
- */
3633
- private function insertRCDATAElement($token) {
3634
- $this->insertElement($token);
3635
- $this->original_mode = $this->mode;
3636
- $this->mode = self::IN_CDATA_RCDATA;
3637
- $this->content_model = HTML5_Tokenizer::RCDATA;
3638
- }
3639
-
3640
- /**
3641
- * @param $token
3642
- * @param $key
3643
- * @return bool
3644
- */
3645
- private function getAttr($token, $key) {
3646
- if (!isset($token['attr'])) {
3647
- return false;
3648
- }
3649
- $ret = false;
3650
- foreach ($token['attr'] as $keypair) {
3651
- if ($keypair['name'] === $key) {
3652
- $ret = $keypair['value'];
3653
- }
3654
- }
3655
- return $ret;
3656
- }
3657
-
3658
- /**
3659
- * @return mixed
3660
- */
3661
- private function getCurrentTable() {
3662
- /* The current table is the last table element in the stack of open
3663
- * elements, if there is one. If there is no table element in the stack
3664
- * of open elements (fragment case), then the current table is the
3665
- * first element in the stack of open elements (the html element). */
3666
- for ($i = count($this->stack) - 1; $i >= 0; $i--) {
3667
- if ($this->stack[$i]->tagName === 'table') {
3668
- return $this->stack[$i];
3669
- }
3670
- }
3671
- return $this->stack[0];
3672
- }
3673
-
3674
- /**
3675
- * @return mixed
3676
- */
3677
- private function getFosterParent() {
3678
- /* The foster parent element is the parent element of the last
3679
- table element in the stack of open elements, if there is a
3680
- table element and it has such a parent element. If there is no
3681
- table element in the stack of open elements (innerHTML case),
3682
- then the foster parent element is the first element in the
3683
- stack of open elements (the html element). Otherwise, if there
3684
- is a table element in the stack of open elements, but the last
3685
- table element in the stack of open elements has no parent, or
3686
- its parent node is not an element, then the foster parent
3687
- element is the element before the last table element in the
3688
- stack of open elements. */
3689
- for ($n = count($this->stack) - 1; $n >= 0; $n--) {
3690
- if ($this->stack[$n]->tagName === 'table') {
3691
- $table = $this->stack[$n];
3692
- break;
3693
- }
3694
- }
3695
-
3696
- if (isset($table) && $table->parentNode !== null) {
3697
- return $table->parentNode;
3698
-
3699
- } elseif (!isset($table)) {
3700
- return $this->stack[0];
3701
-
3702
- } elseif (isset($table) && ($table->parentNode === null ||
3703
- $table->parentNode->nodeType !== XML_ELEMENT_NODE)) {
3704
- return $this->stack[$n - 1];
3705
- }
3706
-
3707
- return null;
3708
- }
3709
-
3710
- /**
3711
- * @param $node
3712
- */
3713
- public function fosterParent($node) {
3714
- $foster_parent = $this->getFosterParent();
3715
- $table = $this->getCurrentTable(); // almost equivalent to last table element, except it can be html
3716
- /* When a node node is to be foster parented, the node node must be
3717
- * be inserted into the foster parent element. */
3718
- /* If the foster parent element is the parent element of the last table
3719
- * element in the stack of open elements, then node must be inserted
3720
- * immediately before the last table element in the stack of open
3721
- * elements in the foster parent element; otherwise, node must be
3722
- * appended to the foster parent element. */
3723
- if ($table->tagName === 'table' && $table->parentNode->isSameNode($foster_parent)) {
3724
- $foster_parent->insertBefore($node, $table);
3725
- } else {
3726
- $foster_parent->appendChild($node);
3727
- }
3728
- }
3729
-
3730
- /**
3731
- * For debugging, prints the stack
3732
- */
3733
- private function printStack() {
3734
- $names = [];
3735
- foreach ($this->stack as $i => $element) {
3736
- $names[] = $element->tagName;
3737
- }
3738
- echo " -> stack [" . implode(', ', $names) . "]\n";
3739
- }
3740
-
3741
- /**
3742
- * For debugging, prints active formatting elements
3743
- */
3744
- private function printActiveFormattingElements() {
3745
- if (!$this->a_formatting) {
3746
- return;
3747
- }
3748
- $names = [];
3749
- foreach ($this->a_formatting as $node) {
3750
- if ($node === self::MARKER) {
3751
- $names[] = 'MARKER';
3752
- } else {
3753
- $names[] = $node->tagName;
3754
- }
3755
- }
3756
- echo " -> active formatting [" . implode(', ', $names) . "]\n";
3757
- }
3758
-
3759
- /**
3760
- * @return bool
3761
- */
3762
- public function currentTableIsTainted() {
3763
- return !empty($this->getCurrentTable()->tainted);
3764
- }
3765
-
3766
- /**
3767
- * Sets up the tree constructor for building a fragment.
3768
- *
3769
- * @param null $context
3770
- */
3771
- public function setupContext($context = null) {
3772
- $this->fragment = true;
3773
- if ($context) {
3774
- $context = $this->dom->createElementNS(self::NS_HTML, $context);
3775
- /* 4.1. Set the HTML parser's tokenization stage's content model
3776
- * flag according to the context element, as follows: */
3777
- switch ($context->tagName) {
3778
- case 'title': case 'textarea':
3779
- $this->content_model = HTML5_Tokenizer::RCDATA;
3780
- break;
3781
- case 'style': case 'script': case 'xmp': case 'iframe':
3782
- case 'noembed': case 'noframes':
3783
- $this->content_model = HTML5_Tokenizer::CDATA;
3784
- break;
3785
- case 'noscript':
3786
- // XSCRIPT: assuming scripting is enabled
3787
- $this->content_model = HTML5_Tokenizer::CDATA;
3788
- break;
3789
- case 'plaintext':
3790
- $this->content_model = HTML5_Tokenizer::PLAINTEXT;
3791
- break;
3792
- }
3793
- /* 4.2. Let root be a new html element with no attributes. */
3794
- $root = $this->dom->createElementNS(self::NS_HTML, 'html');
3795
- $this->root = $root;
3796
- /* 4.3 Append the element root to the Document node created above. */
3797
- $this->dom->appendChild($root);
3798
- /* 4.4 Set up the parser's stack of open elements so that it
3799
- * contains just the single element root. */
3800
- $this->stack = [$root];
3801
- /* 4.5 Reset the parser's insertion mode appropriately. */
3802
- $this->resetInsertionMode($context);
3803
- /* 4.6 Set the parser's form element pointer to the nearest node
3804
- * to the context element that is a form element (going straight up
3805
- * the ancestor chain, and including the element itself, if it is a
3806
- * form element), or, if there is no such form element, to null. */
3807
- $node = $context;
3808
- do {
3809
- if ($node->tagName === 'form') {
3810
- $this->form_pointer = $node;
3811
- break;
3812
- }
3813
- } while ($node = $node->parentNode);
3814
- }
3815
- }
3816
-
3817
- /**
3818
- * @param $token
3819
- * @return mixed
3820
- */
3821
- public function adjustMathMLAttributes($token) {
3822
- foreach ($token['attr'] as &$kp) {
3823
- if ($kp['name'] === 'definitionurl') {
3824
- $kp['name'] = 'definitionURL';
3825
- }
3826
- }
3827
- return $token;
3828
- }
3829
-
3830
- /**
3831
- * @param $token
3832
- * @return mixed
3833
- */
3834
- public function adjustSVGAttributes($token) {
3835
- static $lookup = [
3836
- 'attributename' => 'attributeName',
3837
- 'attributetype' => 'attributeType',
3838
- 'basefrequency' => 'baseFrequency',
3839
- 'baseprofile' => 'baseProfile',
3840
- 'calcmode' => 'calcMode',
3841
- 'clippathunits' => 'clipPathUnits',
3842
- 'contentscripttype' => 'contentScriptType',
3843
- 'contentstyletype' => 'contentStyleType',
3844
- 'diffuseconstant' => 'diffuseConstant',
3845
- 'edgemode' => 'edgeMode',
3846
- 'externalresourcesrequired' => 'externalResourcesRequired',
3847
- 'filterres' => 'filterRes',
3848
- 'filterunits' => 'filterUnits',
3849
- 'glyphref' => 'glyphRef',
3850
- 'gradienttransform' => 'gradientTransform',
3851
- 'gradientunits' => 'gradientUnits',
3852
- 'kernelmatrix' => 'kernelMatrix',
3853
- 'kernelunitlength' => 'kernelUnitLength',
3854
- 'keypoints' => 'keyPoints',
3855
- 'keysplines' => 'keySplines',
3856
- 'keytimes' => 'keyTimes',
3857
- 'lengthadjust' => 'lengthAdjust',
3858
- 'limitingconeangle' => 'limitingConeAngle',
3859
- 'markerheight' => 'markerHeight',
3860
- 'markerunits' => 'markerUnits',
3861
- 'markerwidth' => 'markerWidth',
3862
- 'maskcontentunits' => 'maskContentUnits',
3863
- 'maskunits' => 'maskUnits',
3864
- 'numoctaves' => 'numOctaves',
3865
- 'pathlength' => 'pathLength',
3866
- 'patterncontentunits' => 'patternContentUnits',
3867
- 'patterntransform' => 'patternTransform',
3868
- 'patternunits' => 'patternUnits',
3869
- 'pointsatx' => 'pointsAtX',
3870
- 'pointsaty' => 'pointsAtY',
3871
- 'pointsatz' => 'pointsAtZ',
3872
- 'preservealpha' => 'preserveAlpha',
3873
- 'preserveaspectratio' => 'preserveAspectRatio',
3874
- 'primitiveunits' => 'primitiveUnits',
3875
- 'refx' => 'refX',
3876
- 'refy' => 'refY',
3877
- 'repeatcount' => 'repeatCount',
3878
- 'repeatdur' => 'repeatDur',
3879
- 'requiredextensions' => 'requiredExtensions',
3880
- 'requiredfeatures' => 'requiredFeatures',
3881
- 'specularconstant' => 'specularConstant',
3882
- 'specularexponent' => 'specularExponent',
3883
- 'spreadmethod' => 'spreadMethod',
3884
- 'startoffset' => 'startOffset',
3885
- 'stddeviation' => 'stdDeviation',
3886
- 'stitchtiles' => 'stitchTiles',
3887
- 'surfacescale' => 'surfaceScale',
3888
- 'systemlanguage' => 'systemLanguage',
3889
- 'tablevalues' => 'tableValues',
3890
- 'targetx' => 'targetX',
3891
- 'targety' => 'targetY',
3892
- 'textlength' => 'textLength',
3893
- 'viewbox' => 'viewBox',
3894
- 'viewtarget' => 'viewTarget',
3895
- 'xchannelselector' => 'xChannelSelector',
3896
- 'ychannelselector' => 'yChannelSelector',
3897
- 'zoomandpan' => 'zoomAndPan',
3898
- ];
3899
- foreach ($token['attr'] as &$kp) {
3900
- if (isset($lookup[$kp['name']])) {
3901
- $kp['name'] = $lookup[$kp['name']];
3902
- }
3903
- }
3904
- return $token;
3905
- }
3906
-
3907
- /**
3908
- * @param $token
3909
- * @return mixed
3910
- */
3911
- public function adjustForeignAttributes($token) {
3912
- static $lookup = [
3913
- 'xlink:actuate' => ['xlink', 'actuate', self::NS_XLINK],
3914
- 'xlink:arcrole' => ['xlink', 'arcrole', self::NS_XLINK],
3915
- 'xlink:href' => ['xlink', 'href', self::NS_XLINK],
3916
- 'xlink:role' => ['xlink', 'role', self::NS_XLINK],
3917
- 'xlink:show' => ['xlink', 'show', self::NS_XLINK],
3918
- 'xlink:title' => ['xlink', 'title', self::NS_XLINK],
3919
- 'xlink:type' => ['xlink', 'type', self::NS_XLINK],
3920
- 'xml:base' => ['xml', 'base', self::NS_XML],
3921
- 'xml:lang' => ['xml', 'lang', self::NS_XML],
3922
- 'xml:space' => ['xml', 'space', self::NS_XML],
3923
- 'xmlns' => [null, 'xmlns', self::NS_XMLNS],
3924
- 'xmlns:xlink' => ['xmlns', 'xlink', self::NS_XMLNS],
3925
- ];
3926
- foreach ($token['attr'] as &$kp) {
3927
- if (isset($lookup[$kp['name']])) {
3928
- $kp['name'] = $lookup[$kp['name']];
3929
- }
3930
- }
3931
- return $token;
3932
- }
3933
-
3934
- /**
3935
- * @param $token
3936
- * @param $namespaceURI
3937
- */
3938
- public function insertForeignElement($token, $namespaceURI) {
3939
- $el = $this->dom->createElementNS($namespaceURI, $token['name']);
3940
-
3941
- if (!empty($token['attr'])) {
3942
- foreach ($token['attr'] as $kp) {
3943
- $attr = $kp['name'];
3944
- if (is_array($attr)) {
3945
- $ns = $attr[2];
3946
- $attr = $attr[1];
3947
- } else {
3948
- $ns = self::NS_HTML;
3949
- }
3950
- if (!$el->hasAttributeNS($ns, $attr)) {
3951
- // XSKETCHY: work around godawful libxml bug
3952
- if ($ns === self::NS_XLINK) {
3953
- $el->setAttribute('xlink:'.$attr, $kp['value']);
3954
- } elseif ($ns === self::NS_HTML) {
3955
- // Another godawful libxml bug
3956
- $el->setAttribute($attr, $kp['value']);
3957
- } else {
3958
- $el->setAttributeNS($ns, $attr, $kp['value']);
3959
- }
3960
- }
3961
- }
3962
- }
3963
- $this->appendToRealParent($el);
3964
- $this->stack[] = $el;
3965
- // XERROR: see below
3966
- /* If the newly created element has an xmlns attribute in the XMLNS
3967
- * namespace whose value is not exactly the same as the element's
3968
- * namespace, that is a parse error. Similarly, if the newly created
3969
- * element has an xmlns:xlink attribute in the XMLNS namespace whose
3970
- * value is not the XLink Namespace, that is a parse error. */
3971
- }
3972
-
3973
- /**
3974
- * @return DOMDocument|DOMNodeList
3975
- */
3976
- public function save() {
3977
- $this->dom->normalize();
3978
- if (!$this->fragment) {
3979
- return $this->dom;
3980
- } else {
3981
- if ($this->root) {
3982
- return $this->root->childNodes;
3983
- } else {
3984
- return $this->dom->childNodes;
3985
- }
3986
- }
3987
- }
3988
- }
3989
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
vendor/dompdf/dompdf/lib/html5lib/named-character-references.ser DELETED
@@ -1 +0,0 @@
1
- a:52:{s:1:"A";a:16:{s:1:"E";a:1:{s:1:"l";a:1:{s:1:"i";a:1:{s:1:"g";a:2:{s:1:";";a:1:{s:9:"codepoint";i:198;}s:9:"codepoint";i:198;}}}}s:1:"M";a:1:{s:1:"P";a:2:{s:1:";";a:1:{s:9:"codepoint";i:38;}s:9:"codepoint";i:38;}}s:1:"a";a:1:{s:1:"c";a:1:{s:1:"u";a:1:{s:1:"t";a:1:{s:1:"e";a:2:{s:1:";";a:1:{s:9:"codepoint";i:193;}s:9:"codepoint";i:193;}}}}}s:1:"b";a:1:{s:1:"r";a:1:{s:1:"e";a:1:{s:1:"v";a:1:{s:1:"e";a:1:{s:1:";";a:1:{s:9:"codepoint";i:258;}}}}}}s:1:"c";a:2:{s:1:"i";a:1:{s:1:"r";a:1:{s:1:"c";a:2:{s:1:";";a:1:{s:9:"codepoint";i:194;}s:9:"codepoint";i:194;}}}s:1:"y";a:1:{s:1:";";a:1:{s:9:"codepoint";i:1040;}}}s:1:"f";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:120068;}}}s:1:"g";a:1:{s:1:"r";a:1:{s:1:"a";a:1:{s:1:"v";a:1:{s:1:"e";a:2:{s:1:";";a:1:{s:9:"codepoint";i:192;}s:9:"codepoint";i:192;}}}}}s:1:"l";a:1:{s:1:"p";a:1:{s:1:"h";a:1:{s:1:"a";a:1:{s:1:";";a:1:{s:9:"codepoint";i:913;}}}}}s:1:"m";a:1:{s:1:"a";a:1:{s:1:"c";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:256;}}}}}s:1:"n";a:1:{s:1:"d";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10835;}}}s:1:"o";a:2:{s:1:"g";a:1:{s:1:"o";a:1:{s:1:"n";a:1:{s:1:";";a:1:{s:9:"codepoint";i:260;}}}}s:1:"p";a:1:{s:1:"f";a:1:{s:1:";";a:1:{s:9:"codepoint";i:120120;}}}}s:1:"p";a:1:{s:1:"p";a:1:{s:1:"l";a:1:{s:1:"y";a:1:{s:1:"F";a:1:{s:1:"u";a:1:{s:1:"n";a:1:{s:1:"c";a:1:{s:1:"t";a:1:{s:1:"i";a:1:{s:1:"o";a:1:{s:1:"n";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8289;}}}}}}}}}}}}}s:1:"r";a:1:{s:1:"i";a:1:{s:1:"n";a:1:{s:1:"g";a:2:{s:1:";";a:1:{s:9:"codepoint";i:197;}s:9:"codepoint";i:197;}}}}s:1:"s";a:2:{s:1:"c";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:119964;}}}s:1:"s";a:1:{s:1:"i";a:1:{s:1:"g";a:1:{s:1:"n";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8788;}}}}}}s:1:"t";a:1:{s:1:"i";a:1:{s:1:"l";a:1:{s:1:"d";a:1:{s:1:"e";a:2:{s:1:";";a:1:{s:9:"codepoint";i:195;}s:9:"codepoint";i:195;}}}}}s:1:"u";a:1:{s:1:"m";a:1:{s:1:"l";a:2:{s:1:";";a:1:{s:9:"codepoint";i:196;}s:9:"codepoint";i:196;}}}}s:1:"B";a:8:{s:1:"a";a:2:{s:1:"c";a:1:{s:1:"k";a:1:{s:1:"s";a:1:{s:1:"l";a:1:{s:1:"a";a:1:{s:1:"s";a:1:{s:1:"h";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8726;}}}}}}}}s:1:"r";a:2:{s:1:"v";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10983;}}s:1:"w";a:1:{s:1:"e";a:1:{s:1:"d";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8966;}}}}}}s:1:"c";a:1:{s:1:"y";a:1:{s:1:";";a:1:{s:9:"codepoint";i:1041;}}}s:1:"e";a:3:{s:1:"c";a:1:{s:1:"a";a:1:{s:1:"u";a:1:{s:1:"s";a:1:{s:1:"e";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8757;}}}}}}s:1:"r";a:1:{s:1:"n";a:1:{s:1:"o";a:1:{s:1:"u";a:1:{s:1:"l";a:1:{s:1:"l";a:1:{s:1:"i";a:1:{s:1:"s";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8492;}}}}}}}}}s:1:"t";a:1:{s:1:"a";a:1:{s:1:";";a:1:{s:9:"codepoint";i:914;}}}}s:1:"f";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:120069;}}}s:1:"o";a:1:{s:1:"p";a:1:{s:1:"f";a:1:{s:1:";";a:1:{s:9:"codepoint";i:120121;}}}}s:1:"r";a:1:{s:1:"e";a:1:{s:1:"v";a:1:{s:1:"e";a:1:{s:1:";";a:1:{s:9:"codepoint";i:728;}}}}}s:1:"s";a:1:{s:1:"c";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8492;}}}}s:1:"u";a:1:{s:1:"m";a:1:{s:1:"p";a:1:{s:1:"e";a:1:{s:1:"q";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8782;}}}}}}}s:1:"C";a:14:{s:1:"H";a:1:{s:1:"c";a:1:{s:1:"y";a:1:{s:1:";";a:1:{s:9:"codepoint";i:1063;}}}}s:1:"O";a:1:{s:1:"P";a:1:{s:1:"Y";a:2:{s:1:";";a:1:{s:9:"codepoint";i:169;}s:9:"codepoint";i:169;}}}s:1:"a";a:3:{s:1:"c";a:1:{s:1:"u";a:1:{s:1:"t";a:1:{s:1:"e";a:1:{s:1:";";a:1:{s:9:"codepoint";i:262;}}}}}s:1:"p";a:2:{s:1:";";a:1:{s:9:"codepoint";i:8914;}s:1:"i";a:1:{s:1:"t";a:1:{s:1:"a";a:1:{s:1:"l";a:1:{s:1:"D";a:1:{s:1:"i";a:1:{s:1:"f";a:1:{s:1:"f";a:1:{s:1:"e";a:1:{s:1:"r";a:1:{s:1:"e";a:1:{s:1:"n";a:1:{s:1:"t";a:1:{s:1:"i";a:1:{s:1:"a";a:1:{s:1:"l";a:1:{s:1:"D";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8517;}}}}}}}}}}}}}}}}}}}s:1:"y";a:1:{s:1:"l";a:1:{s:1:"e";a:1:{s:1:"y";a:1:{s:1:"s";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8493;}}}}}}}s:1:"c";a:4:{s:1:"a";a:1:{s:1:"r";a:1:{s:1:"o";a:1:{s:1:"n";a:1:{s:1:";";a:1:{s:9:"codepoint";i:268;}}}}}s:1:"e";a:1:{s:1:"d";a:1:{s:1:"i";a:1:{s:1:"l";a:2:{s:1:";";a:1:{s:9:"codepoint";i:199;}s:9:"codepoint";i:199;}}}}s:1:"i";a:1:{s:1:"r";a:1:{s:1:"c";a:1:{s:1:";";a:1:{s:9:"codepoint";i:264;}}}}s:1:"o";a:1:{s:1:"n";a:1:{s:1:"i";a:1:{s:1:"n";a:1:{s:1:"t";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8752;}}}}}}}s:1:"d";a:1:{s:1:"o";a:1:{s:1:"t";a:1:{s:1:";";a:1:{s:9:"codepoint";i:266;}}}}s:1:"e";a:2:{s:1:"d";a:1:{s:1:"i";a:1:{s:1:"l";a:1:{s:1:"l";a:1:{s:1:"a";a:1:{s:1:";";a:1:{s:9:"codepoint";i:184;}}}}}}s:1:"n";a:1:{s:1:"t";a:1:{s:1:"e";a:1:{s:1:"r";a:1:{s:1:"D";a:1:{s:1:"o";a:1:{s:1:"t";a:1:{s:1:";";a:1:{s:9:"codepoint";i:183;}}}}}}}}}s:1:"f";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8493;}}}s:1:"h";a:1:{s:1:"i";a:1:{s:1:";";a:1:{s:9:"codepoint";i:935;}}}s:1:"i";a:1:{s:1:"r";a:1:{s:1:"c";a:1:{s:1:"l";a:1:{s:1:"e";a:4:{s:1:"D";a:1:{s:1:"o";a:1:{s:1:"t";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8857;}}}}s:1:"M";a:1:{s:1:"i";a:1:{s:1:"n";a:1:{s:1:"u";a:1:{s:1:"s";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8854;}}}}}}s:1:"P";a:1:{s:1:"l";a:1:{s:1:"u";a:1:{s:1:"s";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8853;}}}}}s:1:"T";a:1:{s:1:"i";a:1:{s:1:"m";a:1:{s:1:"e";a:1:{s:1:"s";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8855;}}}}}}}}}}}s:1:"l";a:1:{s:1:"o";a:2:{s:1:"c";a:1:{s:1:"k";a:1:{s:1:"w";a:1:{s:1:"i";a:1:{s:1:"s";a:1:{s:1:"e";a:1:{s:1:"C";a:1:{s:1:"o";a:1:{s:1:"n";a:1:{s:1:"t";a:1:{s:1:"o";a:1:{s:1:"u";a:1:{s:1:"r";a:1:{s:1:"I";a:1:{s:1:"n";a:1:{s:1:"t";a:1:{s:1:"e";a:1:{s:1:"g";a:1:{s:1:"r";a:1:{s:1:"a";a:1:{s:1:"l";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8754;}}}}}}}}}}}}}}}}}}}}}}s:1:"s";a:1:{s:1:"e";a:1:{s:1:"C";a:1:{s:1:"u";a:1:{s:1:"r";a:1:{s:1:"l";a:1:{s:1:"y";a:2:{s:1:"D";a:1:{s:1:"o";a:1:{s:1:"u";a:1:{s:1:"b";a:1:{s:1:"l";a:1:{s:1:"e";a:1:{s:1:"Q";a:1:{s:1:"u";a:1:{s:1:"o";a:1:{s:1:"t";a:1:{s:1:"e";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8221;}}}}}}}}}}}}s:1:"Q";a:1:{s:1:"u";a:1:{s:1:"o";a:1:{s:1:"t";a:1:{s:1:"e";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8217;}}}}}}}}}}}}}}}s:1:"o";a:4:{s:1:"l";a:1:{s:1:"o";a:1:{s:1:"n";a:2:{s:1:";";a:1:{s:9:"codepoint";i:8759;}s:1:"e";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10868;}}}}}s:1:"n";a:3:{s:1:"g";a:1:{s:1:"r";a:1:{s:1:"u";a:1:{s:1:"e";a:1:{s:1:"n";a:1:{s:1:"t";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8801;}}}}}}}s:1:"i";a:1:{s:1:"n";a:1:{s:1:"t";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8751;}}}}s:1:"t";a:1:{s:1:"o";a:1:{s:1:"u";a:1:{s:1:"r";a:1:{s:1:"I";a:1:{s:1:"n";a:1:{s:1:"t";a:1:{s:1:"e";a:1:{s:1:"g";a:1:{s:1:"r";a:1:{s:1:"a";a:1:{s:1:"l";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8750;}}}}}}}}}}}}}}s:1:"p";a:2:{s:1:"f";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8450;}}s:1:"r";a:1:{s:1:"o";a:1:{s:1:"d";a:1:{s:1:"u";a:1:{s:1:"c";a:1:{s:1:"t";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8720;}}}}}}}}s:1:"u";a:1:{s:1:"n";a:1:{s:1:"t";a:1:{s:1:"e";a:1:{s:1:"r";a:1:{s:1:"C";a:1:{s:1:"l";a:1:{s:1:"o";a:1:{s:1:"c";a:1:{s:1:"k";a:1:{s:1:"w";a:1:{s:1:"i";a:1:{s:1:"s";a:1:{s:1:"e";a:1:{s:1:"C";a:1:{s:1:"o";a:1:{s:1:"n";a:1:{s:1:"t";a:1:{s:1:"o";a:1:{s:1:"u";a:1:{s:1:"r";a:1:{s:1:"I";a:1:{s:1:"n";a:1:{s:1:"t";a:1:{s:1:"e";a:1:{s:1:"g";a:1:{s:1:"r";a:1:{s:1:"a";a:1:{s:1:"l";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8755;}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}s:1:"r";a:1:{s:1:"o";a:1:{s:1:"s";a:1:{s:1:"s";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10799;}}}}}s:1:"s";a:1:{s:1:"c";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:119966;}}}}s:1:"u";a:1:{s:1:"p";a:2:{s:1:";";a:1:{s:9:"codepoint";i:8915;}s:1:"C";a:1:{s:1:"a";a:1:{s:1:"p";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8781;}}}}}}}s:1:"D";a:11:{s:1:"D";a:2:{s:1:";";a:1:{s:9:"codepoint";i:8517;}s:1:"o";a:1:{s:1:"t";a:1:{s:1:"r";a:1:{s:1:"a";a:1:{s:1:"h";a:1:{s:1:"d";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10513;}}}}}}}}s:1:"J";a:1:{s:1:"c";a:1:{s:1:"y";a:1:{s:1:";";a:1:{s:9:"codepoint";i:1026;}}}}s:1:"S";a:1:{s:1:"c";a:1:{s:1:"y";a:1:{s:1:";";a:1:{s:9:"codepoint";i:1029;}}}}s:1:"Z";a:1:{s:1:"c";a:1:{s:1:"y";a:1:{s:1:";";a:1:{s:9:"codepoint";i:1039;}}}}s:1:"a";a:3:{s:1:"g";a:1:{s:1:"g";a:1:{s:1:"e";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8225;}}}}}s:1:"r";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8609;}}}s:1:"s";a:1:{s:1:"h";a:1:{s:1:"v";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10980;}}}}}s:1:"c";a:2:{s:1:"a";a:1:{s:1:"r";a:1:{s:1:"o";a:1:{s:1:"n";a:1:{s:1:";";a:1:{s:9:"codepoint";i:270;}}}}}s:1:"y";a:1:{s:1:";";a:1:{s:9:"codepoint";i:1044;}}}s:1:"e";a:1:{s:1:"l";a:2:{s:1:";";a:1:{s:9:"codepoint";i:8711;}s:1:"t";a:1:{s:1:"a";a:1:{s:1:";";a:1:{s:9:"codepoint";i:916;}}}}}s:1:"f";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:120071;}}}s:1:"i";a:2:{s:1:"a";a:2:{s:1:"c";a:1:{s:1:"r";a:1:{s:1:"i";a:1:{s:1:"t";a:1:{s:1:"i";a:1:{s:1:"c";a:1:{s:1:"a";a:1:{s:1:"l";a:4:{s:1:"A";a:1:{s:1:"c";a:1:{s:1:"u";a:1:{s:1:"t";a:1:{s:1:"e";a:1:{s:1:";";a:1:{s:9:"codepoint";i:180;}}}}}}s:1:"D";a:1:{s:1:"o";a:2:{s:1:"t";a:1:{s:1:";";a:1:{s:9:"codepoint";i:729;}}s:1:"u";a:1:{s:1:"b";a:1:{s:1:"l";a:1:{s:1:"e";a:1:{s:1:"A";a:1:{s:1:"c";a:1:{s:1:"u";a:1:{s:1:"t";a:1:{s:1:"e";a:1:{s:1:";";a:1:{s:9:"codepoint";i:733;}}}}}}}}}}}}s:1:"G";a:1:{s:1:"r";a:1:{s:1:"a";a:1:{s:1:"v";a:1:{s:1:"e";a:1:{s:1:";";a:1:{s:9:"codepoint";i:96;}}}}}}s:1:"T";a:1:{s:1:"i";a:1:{s:1:"l";a:1:{s:1:"d";a:1:{s:1:"e";a:1:{s:1:";";a:1:{s:9:"codepoint";i:732;}}}}}}}}}}}}}}s:1:"m";a:1:{s:1:"o";a:1:{s:1:"n";a:1:{s:1:"d";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8900;}}}}}}s:1:"f";a:1:{s:1:"f";a:1:{s:1:"e";a:1:{s:1:"r";a:1:{s:1:"e";a:1:{s:1:"n";a:1:{s:1:"t";a:1:{s:1:"i";a:1:{s:1:"a";a:1:{s:1:"l";a:1:{s:1:"D";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8518;}}}}}}}}}}}}}s:1:"o";a:4:{s:1:"p";a:1:{s:1:"f";a:1:{s:1:";";a:1:{s:9:"codepoint";i:120123;}}}s:1:"t";a:3:{s:1:";";a:1:{s:9:"codepoint";i:168;}s:1:"D";a:1:{s:1:"o";a:1:{s:1:"t";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8412;}}}}s:1:"E";a:1:{s:1:"q";a:1:{s:1:"u";a:1:{s:1:"a";a:1:{s:1:"l";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8784;}}}}}}}s:1:"u";a:1:{s:1:"b";a:1:{s:1:"l";a:1:{s:1:"e";a:6:{s:1:"C";a:1:{s:1:"o";a:1:{s:1:"n";a:1:{s:1:"t";a:1:{s:1:"o";a:1:{s:1:"u";a:1:{s:1:"r";a:1:{s:1:"I";a:1:{s:1:"n";a:1:{s:1:"t";a:1:{s:1:"e";a:1:{s:1:"g";a:1:{s:1:"r";a:1:{s:1:"a";a:1:{s:1:"l";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8751;}}}}}}}}}}}}}}}}s:1:"D";a:1:{s:1:"o";a:2:{s:1:"t";a:1:{s:1:";";a:1:{s:9:"codepoint";i:168;}}s:1:"w";a:1:{s:1:"n";a:1:{s:1:"A";a:1:{s:1:"r";a:1:{s:1:"r";a:1:{s:1:"o";a:1:{s:1:"w";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8659;}}}}}}}}}}s:1:"L";a:2:{s:1:"e";a:1:{s:1:"f";a:1:{s:1:"t";a:3:{s:1:"A";a:1:{s:1:"r";a:1:{s:1:"r";a:1:{s:1:"o";a:1:{s:1:"w";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8656;}}}}}}s:1:"R";a:1:{s:1:"i";a:1:{s:1:"g";a:1:{s:1:"h";a:1:{s:1:"t";a:1:{s:1:"A";a:1:{s:1:"r";a:1:{s:1:"r";a:1:{s:1:"o";a:1:{s:1:"w";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8660;}}}}}}}}}}}s:1:"T";a:1:{s:1:"e";a:1:{s:1:"e";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10980;}}}}}}}s:1:"o";a:1:{s:1:"n";a:1:{s:1:"g";a:2:{s:1:"L";a:1:{s:1:"e";a:1:{s:1:"f";a:1:{s:1:"t";a:2:{s:1:"A";a:1:{s:1:"r";a:1:{s:1:"r";a:1:{s:1:"o";a:1:{s:1:"w";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10232;}}}}}}s:1:"R";a:1:{s:1:"i";a:1:{s:1:"g";a:1:{s:1:"h";a:1:{s:1:"t";a:1:{s:1:"A";a:1:{s:1:"r";a:1:{s:1:"r";a:1:{s:1:"o";a:1:{s:1:"w";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10234;}}}}}}}}}}}}}}}s:1:"R";a:1:{s:1:"i";a:1:{s:1:"g";a:1:{s:1:"h";a:1:{s:1:"t";a:1:{s:1:"A";a:1:{s:1:"r";a:1:{s:1:"r";a:1:{s:1:"o";a:1:{s:1:"w";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10233;}}}}}}}}}}}}}}}s:1:"R";a:1:{s:1:"i";a:1:{s:1:"g";a:1:{s:1:"h";a:1:{s:1:"t";a:2:{s:1:"A";a:1:{s:1:"r";a:1:{s:1:"r";a:1:{s:1:"o";a:1:{s:1:"w";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8658;}}}}}}s:1:"T";a:1:{s:1:"e";a:1:{s:1:"e";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8872;}}}}}}}}}s:1:"U";a:1:{s:1:"p";a:2:{s:1:"A";a:1:{s:1:"r";a:1:{s:1:"r";a:1:{s:1:"o";a:1:{s:1:"w";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8657;}}}}}}s:1:"D";a:1:{s:1:"o";a:1:{s:1:"w";a:1:{s:1:"n";a:1:{s:1:"A";a:1:{s:1:"r";a:1:{s:1:"r";a:1:{s:1:"o";a:1:{s:1:"w";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8661;}}}}}}}}}}}}s:1:"V";a:1:{s:1:"e";a:1:{s:1:"r";a:1:{s:1:"t";a:1:{s:1:"i";a:1:{s:1:"c";a:1:{s:1:"a";a:1:{s:1:"l";a:1:{s:1:"B";a:1:{s:1:"a";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8741;}}}}}}}}}}}}}}}}s:1:"w";a:1:{s:1:"n";a:6:{s:1:"A";a:1:{s:1:"r";a:1:{s:1:"r";a:1:{s:1:"o";a:1:{s:1:"w";a:3:{s:1:";";a:1:{s:9:"codepoint";i:8595;}s:1:"B";a:1:{s:1:"a";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10515;}}}}s:1:"U";a:1:{s:1:"p";a:1:{s:1:"A";a:1:{s:1:"r";a:1:{s:1:"r";a:1:{s:1:"o";a:1:{s:1:"w";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8693;}}}}}}}}}}}}}s:1:"B";a:1:{s:1:"r";a:1:{s:1:"e";a:1:{s:1:"v";a:1:{s:1:"e";a:1:{s:1:";";a:1:{s:9:"codepoint";i:785;}}}}}}s:1:"L";a:1:{s:1:"e";a:1:{s:1:"f";a:1:{s:1:"t";a:3:{s:1:"R";a:1:{s:1:"i";a:1:{s:1:"g";a:1:{s:1:"h";a:1:{s:1:"t";a:1:{s:1:"V";a:1:{s:1:"e";a:1:{s:1:"c";a:1:{s:1:"t";a:1:{s:1:"o";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10576;}}}}}}}}}}}}s:1:"T";a:1:{s:1:"e";a:1:{s:1:"e";a:1:{s:1:"V";a:1:{s:1:"e";a:1:{s:1:"c";a:1:{s:1:"t";a:1:{s:1:"o";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10590;}}}}}}}}}}s:1:"V";a:1:{s:1:"e";a:1:{s:1:"c";a:1:{s:1:"t";a:1:{s:1:"o";a:1:{s:1:"r";a:2:{s:1:";";a:1:{s:9:"codepoint";i:8637;}s:1:"B";a:1:{s:1:"a";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10582;}}}}}}}}}}}}}}s:1:"R";a:1:{s:1:"i";a:1:{s:1:"g";a:1:{s:1:"h";a:1:{s:1:"t";a:2:{s:1:"T";a:1:{s:1:"e";a:1:{s:1:"e";a:1:{s:1:"V";a:1:{s:1:"e";a:1:{s:1:"c";a:1:{s:1:"t";a:1:{s:1:"o";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10591;}}}}}}}}}}s:1:"V";a:1:{s:1:"e";a:1:{s:1:"c";a:1:{s:1:"t";a:1:{s:1:"o";a:1:{s:1:"r";a:2:{s:1:";";a:1:{s:9:"codepoint";i:8641;}s:1:"B";a:1:{s:1:"a";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10583;}}}}}}}}}}}}}}}s:1:"T";a:1:{s:1:"e";a:1:{s:1:"e";a:2:{s:1:";";a:1:{s:9:"codepoint";i:8868;}s:1:"A";a:1:{s:1:"r";a:1:{s:1:"r";a:1:{s:1:"o";a:1:{s:1:"w";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8615;}}}}}}}}}s:1:"a";a:1:{s:1:"r";a:1:{s:1:"r";a:1:{s:1:"o";a:1:{s:1:"w";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8659;}}}}}}}}}s:1:"s";a:2:{s:1:"c";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:119967;}}}s:1:"t";a:1:{s:1:"r";a:1:{s:1:"o";a:1:{s:1:"k";a:1:{s:1:";";a:1:{s:9:"codepoint";i:272;}}}}}}}s:1:"E";a:16:{s:1:"N";a:1:{s:1:"G";a:1:{s:1:";";a:1:{s:9:"codepoint";i:330;}}}s:1:"T";a:1:{s:1:"H";a:2:{s:1:";";a:1:{s:9:"codepoint";i:208;}s:9:"codepoint";i:208;}}s:1:"a";a:1:{s:1:"c";a:1:{s:1:"u";a:1:{s:1:"t";a:1:{s:1:"e";a:2:{s:1:";";a:1:{s:9:"codepoint";i:201;}s:9:"codepoint";i:201;}}}}}s:1:"c";a:3:{s:1:"a";a:1:{s:1:"r";a:1:{s:1:"o";a:1:{s:1:"n";a:1:{s:1:";";a:1:{s:9:"codepoint";i:282;}}}}}s:1:"i";a:1:{s:1:"r";a:1:{s:1:"c";a:2:{s:1:";";a:1:{s:9:"codepoint";i:202;}s:9:"codepoint";i:202;}}}s:1:"y";a:1:{s:1:";";a:1:{s:9:"codepoint";i:1069;}}}s:1:"d";a:1:{s:1:"o";a:1:{s:1:"t";a:1:{s:1:";";a:1:{s:9:"codepoint";i:278;}}}}s:1:"f";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:120072;}}}s:1:"g";a:1:{s:1:"r";a:1:{s:1:"a";a:1:{s:1:"v";a:1:{s:1:"e";a:2:{s:1:";";a:1:{s:9:"codepoint";i:200;}s:9:"codepoint";i:200;}}}}}s:1:"l";a:1:{s:1:"e";a:1:{s:1:"m";a:1:{s:1:"e";a:1:{s:1:"n";a:1:{s:1:"t";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8712;}}}}}}}s:1:"m";a:2:{s:1:"a";a:1:{s:1:"c";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:274;}}}}s:1:"p";a:1:{s:1:"t";a:1:{s:1:"y";a:2:{s:1:"S";a:1:{s:1:"m";a:1:{s:1:"a";a:1:{s:1:"l";a:1:{s:1:"l";a:1:{s:1:"S";a:1:{s:1:"q";a:1:{s:1:"u";a:1:{s:1:"a";a:1:{s:1:"r";a:1:{s:1:"e";a:1:{s:1:";";a:1:{s:9:"codepoint";i:9723;}}}}}}}}}}}}s:1:"V";a:1:{s:1:"e";a:1:{s:1:"r";a:1:{s:1:"y";a:1:{s:1:"S";a:1:{s:1:"m";a:1:{s:1:"a";a:1:{s:1:"l";a:1:{s:1:"l";a:1:{s:1:"S";a:1:{s:1:"q";a:1:{s:1:"u";a:1:{s:1:"a";a:1:{s:1:"r";a:1:{s:1:"e";a:1:{s:1:";";a:1:{s:9:"codepoint";i:9643;}}}}}}}}}}}}}}}}}}}}s:1:"o";a:2:{s:1:"g";a:1:{s:1:"o";a:1:{s:1:"n";a:1:{s:1:";";a:1:{s:9:"codepoint";i:280;}}}}s:1:"p";a:1:{s:1:"f";a:1:{s:1:";";a:1:{s:9:"codepoint";i:120124;}}}}s:1:"p";a:1:{s:1:"s";a:1:{s:1:"i";a:1:{s:1:"l";a:1:{s:1:"o";a:1:{s:1:"n";a:1:{s:1:";";a:1:{s:9:"codepoint";i:917;}}}}}}}s:1:"q";a:1:{s:1:"u";a:2:{s:1:"a";a:1:{s:1:"l";a:2:{s:1:";";a:1:{s:9:"codepoint";i:10869;}s:1:"T";a:1:{s:1:"i";a:1:{s:1:"l";a:1:{s:1:"d";a:1:{s:1:"e";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8770;}}}}}}}}s:1:"i";a:1:{s:1:"l";a:1:{s:1:"i";a:1:{s:1:"b";a:1:{s:1:"r";a:1:{s:1:"i";a:1:{s:1:"u";a:1:{s:1:"m";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8652;}}}}}}}}}}}s:1:"s";a:2:{s:1:"c";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8496;}}}s:1:"i";a:1:{s:1:"m";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10867;}}}}s:1:"t";a:1:{s:1:"a";a:1:{s:1:";";a:1:{s:9:"codepoint";i:919;}}}s:1:"u";a:1:{s:1:"m";a:1:{s:1:"l";a:2:{s:1:";";a:1:{s:9:"codepoint";i:203;}s:9:"codepoint";i:203;}}}s:1:"x";a:2:{s:1:"i";a:1:{s:1:"s";a:1:{s:1:"t";a:1:{s:1:"s";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8707;}}}}}s:1:"p";a:1:{s:1:"o";a:1:{s:1:"n";a:1:{s:1:"e";a:1:{s:1:"n";a:1:{s:1:"t";a:1:{s:1:"i";a:1:{s:1:"a";a:1:{s:1:"l";a:1:{s:1:"E";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8519;}}}}}}}}}}}}}s:1:"F";a:5:{s:1:"c";a:1:{s:1:"y";a:1:{s:1:";";a:1:{s:9:"codepoint";i:1060;}}}s:1:"f";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:120073;}}}s:1:"i";a:1:{s:1:"l";a:1:{s:1:"l";a:1:{s:1:"e";a:1:{s:1:"d";a:2:{s:1:"S";a:1:{s:1:"m";a:1:{s:1:"a";a:1:{s:1:"l";a:1:{s:1:"l";a:1:{s:1:"S";a:1:{s:1:"q";a:1:{s:1:"u";a:1:{s:1:"a";a:1:{s:1:"r";a:1:{s:1:"e";a:1:{s:1:";";a:1:{s:9:"codepoint";i:9724;}}}}}}}}}}}}s:1:"V";a:1:{s:1:"e";a:1:{s:1:"r";a:1:{s:1:"y";a:1:{s:1:"S";a:1:{s:1:"m";a:1:{s:1:"a";a:1:{s:1:"l";a:1:{s:1:"l";a:1:{s:1:"S";a:1:{s:1:"q";a:1:{s:1:"u";a:1:{s:1:"a";a:1:{s:1:"r";a:1:{s:1:"e";a:1:{s:1:";";a:1:{s:9:"codepoint";i:9642;}}}}}}}}}}}}}}}}}}}}}s:1:"o";a:3:{s:1:"p";a:1:{s:1:"f";a:1:{s:1:";";a:1:{s:9:"codepoint";i:120125;}}}s:1:"r";a:1:{s:1:"A";a:1:{s:1:"l";a:1:{s:1:"l";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8704;}}}}}s:1:"u";a:1:{s:1:"r";a:1:{s:1:"i";a:1:{s:1:"e";a:1:{s:1:"r";a:1:{s:1:"t";a:1:{s:1:"r";a:1:{s:1:"f";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8497;}}}}}}}}}}s:1:"s";a:1:{s:1:"c";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8497;}}}}}s:1:"G";a:12:{s:1:"J";a:1:{s:1:"c";a:1:{s:1:"y";a:1:{s:1:";";a:1:{s:9:"codepoint";i:1027;}}}}s:1:"T";a:2:{s:1:";";a:1:{s:9:"codepoint";i:62;}s:9:"codepoint";i:62;}s:1:"a";a:1:{s:1:"m";a:1:{s:1:"m";a:1:{s:1:"a";a:2:{s:1:";";a:1:{s:9:"codepoint";i:915;}s:1:"d";a:1:{s:1:";";a:1:{s:9:"codepoint";i:988;}}}}}}s:1:"b";a:1:{s:1:"r";a:1:{s:1:"e";a:1:{s:1:"v";a:1:{s:1:"e";a:1:{s:1:";";a:1:{s:9:"codepoint";i:286;}}}}}}s:1:"c";a:3:{s:1:"e";a:1:{s:1:"d";a:1:{s:1:"i";a:1:{s:1:"l";a:1:{s:1:";";a:1:{s:9:"codepoint";i:290;}}}}}s:1:"i";a:1:{s:1:"r";a:1:{s:1:"c";a:1:{s:1:";";a:1:{s:9:"codepoint";i:284;}}}}s:1:"y";a:1:{s:1:";";a:1:{s:9:"codepoint";i:1043;}}}s:1:"d";a:1:{s:1:"o";a:1:{s:1:"t";a:1:{s:1:";";a:1:{s:9:"codepoint";i:288;}}}}s:1:"f";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:120074;}}}s:1:"g";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8921;}}s:1:"o";a:1:{s:1:"p";a:1:{s:1:"f";a:1:{s:1:";";a:1:{s:9:"codepoint";i:120126;}}}}s:1:"r";a:1:{s:1:"e";a:1:{s:1:"a";a:1:{s:1:"t";a:1:{s:1:"e";a:1:{s:1:"r";a:6:{s:1:"E";a:1:{s:1:"q";a:1:{s:1:"u";a:1:{s:1:"a";a:1:{s:1:"l";a:2:{s:1:";";a:1:{s:9:"codepoint";i:8805;}s:1:"L";a:1:{s:1:"e";a:1:{s:1:"s";a:1:{s:1:"s";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8923;}}}}}}}}}}s:1:"F";a:1:{s:1:"u";a:1:{s:1:"l";a:1:{s:1:"l";a:1:{s:1:"E";a:1:{s:1:"q";a:1:{s:1:"u";a:1:{s:1:"a";a:1:{s:1:"l";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8807;}}}}}}}}}}s:1:"G";a:1:{s:1:"r";a:1:{s:1:"e";a:1:{s:1:"a";a:1:{s:1:"t";a:1:{s:1:"e";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10914;}}}}}}}}s:1:"L";a:1:{s:1:"e";a:1:{s:1:"s";a:1:{s:1:"s";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8823;}}}}}s:1:"S";a:1:{s:1:"l";a:1:{s:1:"a";a:1:{s:1:"n";a:1:{s:1:"t";a:1:{s:1:"E";a:1:{s:1:"q";a:1:{s:1:"u";a:1:{s:1:"a";a:1:{s:1:"l";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10878;}}}}}}}}}}}s:1:"T";a:1:{s:1:"i";a:1:{s:1:"l";a:1:{s:1:"d";a:1:{s:1:"e";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8819;}}}}}}}}}}}}s:1:"s";a:1:{s:1:"c";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:119970;}}}}s:1:"t";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8811;}}}s:1:"H";a:8:{s:1:"A";a:1:{s:1:"R";a:1:{s:1:"D";a:1:{s:1:"c";a:1:{s:1:"y";a:1:{s:1:";";a:1:{s:9:"codepoint";i:1066;}}}}}}s:1:"a";a:2:{s:1:"c";a:1:{s:1:"e";a:1:{s:1:"k";a:1:{s:1:";";a:1:{s:9:"codepoint";i:711;}}}}s:1:"t";a:1:{s:1:";";a:1:{s:9:"codepoint";i:94;}}}s:1:"c";a:1:{s:1:"i";a:1:{s:1:"r";a:1:{s:1:"c";a:1:{s:1:";";a:1:{s:9:"codepoint";i:292;}}}}}s:1:"f";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8460;}}}s:1:"i";a:1:{s:1:"l";a:1:{s:1:"b";a:1:{s:1:"e";a:1:{s:1:"r";a:1:{s:1:"t";a:1:{s:1:"S";a:1:{s:1:"p";a:1:{s:1:"a";a:1:{s:1:"c";a:1:{s:1:"e";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8459;}}}}}}}}}}}}s:1:"o";a:2:{s:1:"p";a:1:{s:1:"f";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8461;}}}s:1:"r";a:1:{s:1:"i";a:1:{s:1:"z";a:1:{s:1:"o";a:1:{s:1:"n";a:1:{s:1:"t";a:1:{s:1:"a";a:1:{s:1:"l";a:1:{s:1:"L";a:1:{s:1:"i";a:1:{s:1:"n";a:1:{s:1:"e";a:1:{s:1:";";a:1:{s:9:"codepoint";i:9472;}}}}}}}}}}}}}}s:1:"s";a:2:{s:1:"c";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8459;}}}s:1:"t";a:1:{s:1:"r";a:1:{s:1:"o";a:1:{s:1:"k";a:1:{s:1:";";a:1:{s:9:"codepoint";i:294;}}}}}}s:1:"u";a:1:{s:1:"m";a:1:{s:1:"p";a:2:{s:1:"D";a:1:{s:1:"o";a:1:{s:1:"w";a:1:{s:1:"n";a:1:{s:1:"H";a:1:{s:1:"u";a:1:{s:1:"m";a:1:{s:1:"p";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8782;}}}}}}}}}s:1:"E";a:1:{s:1:"q";a:1:{s:1:"u";a:1:{s:1:"a";a:1:{s:1:"l";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8783;}}}}}}}}}}s:1:"I";a:14:{s:1:"E";a:1:{s:1:"c";a:1:{s:1:"y";a:1:{s:1:";";a:1:{s:9:"codepoint";i:1045;}}}}s:1:"J";a:1:{s:1:"l";a:1:{s:1:"i";a:1:{s:1:"g";a:1:{s:1:";";a:1:{s:9:"codepoint";i:306;}}}}}s:1:"O";a:1:{s:1:"c";a:1:{s:1:"y";a:1:{s:1:";";a:1:{s:9:"codepoint";i:1025;}}}}s:1:"a";a:1:{s:1:"c";a:1:{s:1:"u";a:1:{s:1:"t";a:1:{s:1:"e";a:2:{s:1:";";a:1:{s:9:"codepoint";i:205;}s:9:"codepoint";i:205;}}}}}s:1:"c";a:2:{s:1:"i";a:1:{s:1:"r";a:1:{s:1:"c";a:2:{s:1:";";a:1:{s:9:"codepoint";i:206;}s:9:"codepoint";i:206;}}}s:1:"y";a:1:{s:1:";";a:1:{s:9:"codepoint";i:1048;}}}s:1:"d";a:1:{s:1:"o";a:1:{s:1:"t";a:1:{s:1:";";a:1:{s:9:"codepoint";i:304;}}}}s:1:"f";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8465;}}}s:1:"g";a:1:{s:1:"r";a:1:{s:1:"a";a:1:{s:1:"v";a:1:{s:1:"e";a:2:{s:1:";";a:1:{s:9:"codepoint";i:204;}s:9:"codepoint";i:204;}}}}}s:1:"m";a:3:{s:1:";";a:1:{s:9:"codepoint";i:8465;}s:1:"a";a:2:{s:1:"c";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:298;}}}s:1:"g";a:1:{s:1:"i";a:1:{s:1:"n";a:1:{s:1:"a";a:1:{s:1:"r";a:1:{s:1:"y";a:1:{s:1:"I";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8520;}}}}}}}}}s:1:"p";a:1:{s:1:"l";a:1:{s:1:"i";a:1:{s:1:"e";a:1:{s:1:"s";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8658;}}}}}}}s:1:"n";a:2:{s:1:"t";a:2:{s:1:";";a:1:{s:9:"codepoint";i:8748;}s:1:"e";a:2:{s:1:"g";a:1:{s:1:"r";a:1:{s:1:"a";a:1:{s:1:"l";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8747;}}}}}s:1:"r";a:1:{s:1:"s";a:1:{s:1:"e";a:1:{s:1:"c";a:1:{s:1:"t";a:1:{s:1:"i";a:1:{s:1:"o";a:1:{s:1:"n";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8898;}}}}}}}}}}}s:1:"v";a:1:{s:1:"i";a:1:{s:1:"s";a:1:{s:1:"i";a:1:{s:1:"b";a:1:{s:1:"l";a:1:{s:1:"e";a:2:{s:1:"C";a:1:{s:1:"o";a:1:{s:1:"m";a:1:{s:1:"m";a:1:{s:1:"a";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8291;}}}}}}s:1:"T";a:1:{s:1:"i";a:1:{s:1:"m";a:1:{s:1:"e";a:1:{s:1:"s";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8290;}}}}}}}}}}}}}}s:1:"o";a:3:{s:1:"g";a:1:{s:1:"o";a:1:{s:1:"n";a:1:{s:1:";";a:1:{s:9:"codepoint";i:302;}}}}s:1:"p";a:1:{s:1:"f";a:1:{s:1:";";a:1:{s:9:"codepoint";i:120128;}}}s:1:"t";a:1:{s:1:"a";a:1:{s:1:";";a:1:{s:9:"codepoint";i:921;}}}}s:1:"s";a:1:{s:1:"c";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8464;}}}}s:1:"t";a:1:{s:1:"i";a:1:{s:1:"l";a:1:{s:1:"d";a:1:{s:1:"e";a:1:{s:1:";";a:1:{s:9:"codepoint";i:296;}}}}}}s:1:"u";a:2:{s:1:"k";a:1:{s:1:"c";a:1:{s:1:"y";a:1:{s:1:";";a:1:{s:9:"codepoint";i:1030;}}}}s:1:"m";a:1:{s:1:"l";a:2:{s:1:";";a:1:{s:9:"codepoint";i:207;}s:9:"codepoint";i:207;}}}}s:1:"J";a:5:{s:1:"c";a:2:{s:1:"i";a:1:{s:1:"r";a:1:{s:1:"c";a:1:{s:1:";";a:1:{s:9:"codepoint";i:308;}}}}s:1:"y";a:1:{s:1:";";a:1:{s:9:"codepoint";i:1049;}}}s:1:"f";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:120077;}}}s:1:"o";a:1:{s:1:"p";a:1:{s:1:"f";a:1:{s:1:";";a:1:{s:9:"codepoint";i:120129;}}}}s:1:"s";a:2:{s:1:"c";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:119973;}}}s:1:"e";a:1:{s:1:"r";a:1:{s:1:"c";a:1:{s:1:"y";a:1:{s:1:";";a:1:{s:9:"codepoint";i:1032;}}}}}}s:1:"u";a:1:{s:1:"k";a:1:{s:1:"c";a:1:{s:1:"y";a:1:{s:1:";";a:1:{s:9:"codepoint";i:1028;}}}}}}s:1:"K";a:7:{s:1:"H";a:1:{s:1:"c";a:1:{s:1:"y";a:1:{s:1:";";a:1:{s:9:"codepoint";i:1061;}}}}s:1:"J";a:1:{s:1:"c";a:1:{s:1:"y";a:1:{s:1:";";a:1:{s:9:"codepoint";i:1036;}}}}s:1:"a";a:1:{s:1:"p";a:1:{s:1:"p";a:1:{s:1:"a";a:1:{s:1:";";a:1:{s:9:"codepoint";i:922;}}}}}s:1:"c";a:2:{s:1:"e";a:1:{s:1:"d";a:1:{s:1:"i";a:1:{s:1:"l";a:1:{s:1:";";a:1:{s:9:"codepoint";i:310;}}}}}s:1:"y";a:1:{s:1:";";a:1:{s:9:"codepoint";i:1050;}}}s:1:"f";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:120078;}}}s:1:"o";a:1:{s:1:"p";a:1:{s:1:"f";a:1:{s:1:";";a:1:{s:9:"codepoint";i:120130;}}}}s:1:"s";a:1:{s:1:"c";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:119974;}}}}}s:1:"L";a:11:{s:1:"J";a:1:{s:1:"c";a:1:{s:1:"y";a:1:{s:1:";";a:1:{s:9:"codepoint";i:1033;}}}}s:1:"T";a:2:{s:1:";";a:1:{s:9:"codepoint";i:60;}s:9:"codepoint";i:60;}s:1:"a";a:5:{s:1:"c";a:1:{s:1:"u";a:1:{s:1:"t";a:1:{s:1:"e";a:1:{s:1:";";a:1:{s:9:"codepoint";i:313;}}}}}s:1:"m";a:1:{s:1:"b";a:1:{s:1:"d";a:1:{s:1:"a";a:1:{s:1:";";a:1:{s:9:"codepoint";i:923;}}}}}s:1:"n";a:1:{s:1:"g";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10218;}}}s:1:"p";a:1:{s:1:"l";a:1:{s:1:"a";a:1:{s:1:"c";a:1:{s:1:"e";a:1:{s:1:"t";a:1:{s:1:"r";a:1:{s:1:"f";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8466;}}}}}}}}}s:1:"r";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8606;}}}}s:1:"c";a:3:{s:1:"a";a:1:{s:1:"r";a:1:{s:1:"o";a:1:{s:1:"n";a:1:{s:1:";";a:1:{s:9:"codepoint";i:317;}}}}}s:1:"e";a:1:{s:1:"d";a:1:{s:1:"i";a:1:{s:1:"l";a:1:{s:1:";";a:1:{s:9:"codepoint";i:315;}}}}}s:1:"y";a:1:{s:1:";";a:1:{s:9:"codepoint";i:1051;}}}s:1:"e";a:2:{s:1:"f";a:1:{s:1:"t";a:10:{s:1:"A";a:2:{s:1:"n";a:1:{s:1:"g";a:1:{s:1:"l";a:1:{s:1:"e";a:1:{s:1:"B";a:1:{s:1:"r";a:1:{s:1:"a";a:1:{s:1:"c";a:1:{s:1:"k";a:1:{s:1:"e";a:1:{s:1:"t";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10216;}}}}}}}}}}}}s:1:"r";a:1:{s:1:"r";a:1:{s:1:"o";a:1:{s:1:"w";a:3:{s:1:";";a:1:{s:9:"codepoint";i:8592;}s:1:"B";a:1:{s:1:"a";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8676;}}}}s:1:"R";a:1:{s:1:"i";a:1:{s:1:"g";a:1:{s:1:"h";a:1:{s:1:"t";a:1:{s:1:"A";a:1:{s:1:"r";a:1:{s:1:"r";a:1:{s:1:"o";a:1:{s:1:"w";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8646;}}}}}}}}}}}}}}}}s:1:"C";a:1:{s:1:"e";a:1:{s:1:"i";a:1:{s:1:"l";a:1:{s:1:"i";a:1:{s:1:"n";a:1:{s:1:"g";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8968;}}}}}}}}s:1:"D";a:1:{s:1:"o";a:2:{s:1:"u";a:1:{s:1:"b";a:1:{s:1:"l";a:1:{s:1:"e";a:1:{s:1:"B";a:1:{s:1:"r";a:1:{s:1:"a";a:1:{s:1:"c";a:1:{s:1:"k";a:1:{s:1:"e";a:1:{s:1:"t";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10214;}}}}}}}}}}}}s:1:"w";a:1:{s:1:"n";a:2:{s:1:"T";a:1:{s:1:"e";a:1:{s:1:"e";a:1:{s:1:"V";a:1:{s:1:"e";a:1:{s:1:"c";a:1:{s:1:"t";a:1:{s:1:"o";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10593;}}}}}}}}}}s:1:"V";a:1:{s:1:"e";a:1:{s:1:"c";a:1:{s:1:"t";a:1:{s:1:"o";a:1:{s:1:"r";a:2:{s:1:";";a:1:{s:9:"codepoint";i:8643;}s:1:"B";a:1:{s:1:"a";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10585;}}}}}}}}}}}}}}s:1:"F";a:1:{s:1:"l";a:1:{s:1:"o";a:1:{s:1:"o";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8970;}}}}}}s:1:"R";a:1:{s:1:"i";a:1:{s:1:"g";a:1:{s:1:"h";a:1:{s:1:"t";a:2:{s:1:"A";a:1:{s:1:"r";a:1:{s:1:"r";a:1:{s:1:"o";a:1:{s:1:"w";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8596;}}}}}}s:1:"V";a:1:{s:1:"e";a:1:{s:1:"c";a:1:{s:1:"t";a:1:{s:1:"o";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10574;}}}}}}}}}}}}s:1:"T";a:2:{s:1:"e";a:1:{s:1:"e";a:3:{s:1:";";a:1:{s:9:"codepoint";i:8867;}s:1:"A";a:1:{s:1:"r";a:1:{s:1:"r";a:1:{s:1:"o";a:1:{s:1:"w";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8612;}}}}}}s:1:"V";a:1:{s:1:"e";a:1:{s:1:"c";a:1:{s:1:"t";a:1:{s:1:"o";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10586;}}}}}}}}}s:1:"r";a:1:{s:1:"i";a:1:{s:1:"a";a:1:{s:1:"n";a:1:{s:1:"g";a:1:{s:1:"l";a:1:{s:1:"e";a:3:{s:1:";";a:1:{s:9:"codepoint";i:8882;}s:1:"B";a:1:{s:1:"a";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10703;}}}}s:1:"E";a:1:{s:1:"q";a:1:{s:1:"u";a:1:{s:1:"a";a:1:{s:1:"l";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8884;}}}}}}}}}}}}}}s:1:"U";a:1:{s:1:"p";a:3:{s:1:"D";a:1:{s:1:"o";a:1:{s:1:"w";a:1:{s:1:"n";a:1:{s:1:"V";a:1:{s:1:"e";a:1:{s:1:"c";a:1:{s:1:"t";a:1:{s:1:"o";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10577;}}}}}}}}}}}s:1:"T";a:1:{s:1:"e";a:1:{s:1:"e";a:1:{s:1:"V";a:1:{s:1:"e";a:1:{s:1:"c";a:1:{s:1:"t";a:1:{s:1:"o";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10592;}}}}}}}}}}s:1:"V";a:1:{s:1:"e";a:1:{s:1:"c";a:1:{s:1:"t";a:1:{s:1:"o";a:1:{s:1:"r";a:2:{s:1:";";a:1:{s:9:"codepoint";i:8639;}s:1:"B";a:1:{s:1:"a";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10584;}}}}}}}}}}}}s:1:"V";a:1:{s:1:"e";a:1:{s:1:"c";a:1:{s:1:"t";a:1:{s:1:"o";a:1:{s:1:"r";a:2:{s:1:";";a:1:{s:9:"codepoint";i:8636;}s:1:"B";a:1:{s:1:"a";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10578;}}}}}}}}}}s:1:"a";a:1:{s:1:"r";a:1:{s:1:"r";a:1:{s:1:"o";a:1:{s:1:"w";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8656;}}}}}}s:1:"r";a:1:{s:1:"i";a:1:{s:1:"g";a:1:{s:1:"h";a:1:{s:1:"t";a:1:{s:1:"a";a:1:{s:1:"r";a:1:{s:1:"r";a:1:{s:1:"o";a:1:{s:1:"w";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8660;}}}}}}}}}}}}}s:1:"s";a:1:{s:1:"s";a:6:{s:1:"E";a:1:{s:1:"q";a:1:{s:1:"u";a:1:{s:1:"a";a:1:{s:1:"l";a:1:{s:1:"G";a:1:{s:1:"r";a:1:{s:1:"e";a:1:{s:1:"a";a:1:{s:1:"t";a:1:{s:1:"e";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8922;}}}}}}}}}}}}}s:1:"F";a:1:{s:1:"u";a:1:{s:1:"l";a:1:{s:1:"l";a:1:{s:1:"E";a:1:{s:1:"q";a:1:{s:1:"u";a:1:{s:1:"a";a:1:{s:1:"l";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8806;}}}}}}}}}}s:1:"G";a:1:{s:1:"r";a:1:{s:1:"e";a:1:{s:1:"a";a:1:{s:1:"t";a:1:{s:1:"e";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8822;}}}}}}}}s:1:"L";a:1:{s:1:"e";a:1:{s:1:"s";a:1:{s:1:"s";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10913;}}}}}s:1:"S";a:1:{s:1:"l";a:1:{s:1:"a";a:1:{s:1:"n";a:1:{s:1:"t";a:1:{s:1:"E";a:1:{s:1:"q";a:1:{s:1:"u";a:1:{s:1:"a";a:1:{s:1:"l";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10877;}}}}}}}}}}}s:1:"T";a:1:{s:1:"i";a:1:{s:1:"l";a:1:{s:1:"d";a:1:{s:1:"e";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8818;}}}}}}}}}s:1:"f";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:120079;}}}s:1:"l";a:2:{s:1:";";a:1:{s:9:"codepoint";i:8920;}s:1:"e";a:1:{s:1:"f";a:1:{s:1:"t";a:1:{s:1:"a";a:1:{s:1:"r";a:1:{s:1:"r";a:1:{s:1:"o";a:1:{s:1:"w";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8666;}}}}}}}}}}s:1:"m";a:1:{s:1:"i";a:1:{s:1:"d";a:1:{s:1:"o";a:1:{s:1:"t";a:1:{s:1:";";a:1:{s:9:"codepoint";i:319;}}}}}}s:1:"o";a:3:{s:1:"n";a:1:{s:1:"g";a:4:{s:1:"L";a:1:{s:1:"e";a:1:{s:1:"f";a:1:{s:1:"t";a:2:{s:1:"A";a:1:{s:1:"r";a:1:{s:1:"r";a:1:{s:1:"o";a:1:{s:1:"w";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10229;}}}}}}s:1:"R";a:1:{s:1:"i";a:1:{s:1:"g";a:1:{s:1:"h";a:1:{s:1:"t";a:1:{s:1:"A";a:1:{s:1:"r";a:1:{s:1:"r";a:1:{s:1:"o";a:1:{s:1:"w";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10231;}}}}}}}}}}}}}}}s:1:"R";a:1:{s:1:"i";a:1:{s:1:"g";a:1:{s:1:"h";a:1:{s:1:"t";a:1:{s:1:"A";a:1:{s:1:"r";a:1:{s:1:"r";a:1:{s:1:"o";a:1:{s:1:"w";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10230;}}}}}}}}}}}s:1:"l";a:1:{s:1:"e";a:1:{s:1:"f";a:1:{s:1:"t";a:2:{s:1:"a";a:1:{s:1:"r";a:1:{s:1:"r";a:1:{s:1:"o";a:1:{s:1:"w";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10232;}}}}}}s:1:"r";a:1:{s:1:"i";a:1:{s:1:"g";a:1:{s:1:"h";a:1:{s:1:"t";a:1:{s:1:"a";a:1:{s:1:"r";a:1:{s:1:"r";a:1:{s:1:"o";a:1:{s:1:"w";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10234;}}}}}}}}}}}}}}}s:1:"r";a:1:{s:1:"i";a:1:{s:1:"g";a:1:{s:1:"h";a:1:{s:1:"t";a:1:{s:1:"a";a:1:{s:1:"r";a:1:{s:1:"r";a:1:{s:1:"o";a:1:{s:1:"w";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10233;}}}}}}}}}}}}}s:1:"p";a:1:{s:1:"f";a:1:{s:1:";";a:1:{s:9:"codepoint";i:120131;}}}s:1:"w";a:1:{s:1:"e";a:1:{s:1:"r";a:2:{s:1:"L";a:1:{s:1:"e";a:1:{s:1:"f";a:1:{s:1:"t";a:1:{s:1:"A";a:1:{s:1:"r";a:1:{s:1:"r";a:1:{s:1:"o";a:1:{s:1:"w";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8601;}}}}}}}}}}s:1:"R";a:1:{s:1:"i";a:1:{s:1:"g";a:1:{s:1:"h";a:1:{s:1:"t";a:1:{s:1:"A";a:1:{s:1:"r";a:1:{s:1:"r";a:1:{s:1:"o";a:1:{s:1:"w";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8600;}}}}}}}}}}}}}}}s:1:"s";a:3:{s:1:"c";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8466;}}}s:1:"h";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8624;}}s:1:"t";a:1:{s:1:"r";a:1:{s:1:"o";a:1:{s:1:"k";a:1:{s:1:";";a:1:{s:9:"codepoint";i:321;}}}}}}s:1:"t";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8810;}}}s:1:"M";a:8:{s:1:"a";a:1:{s:1:"p";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10501;}}}s:1:"c";a:1:{s:1:"y";a:1:{s:1:";";a:1:{s:9:"codepoint";i:1052;}}}s:1:"e";a:2:{s:1:"d";a:1:{s:1:"i";a:1:{s:1:"u";a:1:{s:1:"m";a:1:{s:1:"S";a:1:{s:1:"p";a:1:{s:1:"a";a:1:{s:1:"c";a:1:{s:1:"e";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8287;}}}}}}}}}}s:1:"l";a:1:{s:1:"l";a:1:{s:1:"i";a:1:{s:1:"n";a:1:{s:1:"t";a:1:{s:1:"r";a:1:{s:1:"f";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8499;}}}}}}}}}s:1:"f";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:120080;}}}s:1:"i";a:1:{s:1:"n";a:1:{s:1:"u";a:1:{s:1:"s";a:1:{s:1:"P";a:1:{s:1:"l";a:1:{s:1:"u";a:1:{s:1:"s";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8723;}}}}}}}}}s:1:"o";a:1:{s:1:"p";a:1:{s:1:"f";a:1:{s:1:";";a:1:{s:9:"codepoint";i:120132;}}}}s:1:"s";a:1:{s:1:"c";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8499;}}}}s:1:"u";a:1:{s:1:";";a:1:{s:9:"codepoint";i:924;}}}s:1:"N";a:9:{s:1:"J";a:1:{s:1:"c";a:1:{s:1:"y";a:1:{s:1:";";a:1:{s:9:"codepoint";i:1034;}}}}s:1:"a";a:1:{s:1:"c";a:1:{s:1:"u";a:1:{s:1:"t";a:1:{s:1:"e";a:1:{s:1:";";a:1:{s:9:"codepoint";i:323;}}}}}}s:1:"c";a:3:{s:1:"a";a:1:{s:1:"r";a:1:{s:1:"o";a:1:{s:1:"n";a:1:{s:1:";";a:1:{s:9:"codepoint";i:327;}}}}}s:1:"e";a:1:{s:1:"d";a:1:{s:1:"i";a:1:{s:1:"l";a:1:{s:1:";";a:1:{s:9:"codepoint";i:325;}}}}}s:1:"y";a:1:{s:1:";";a:1:{s:9:"codepoint";i:1053;}}}s:1:"e";a:3:{s:1:"g";a:1:{s:1:"a";a:1:{s:1:"t";a:1:{s:1:"i";a:1:{s:1:"v";a:1:{s:1:"e";a:3:{s:1:"M";a:1:{s:1:"e";a:1:{s:1:"d";a:1:{s:1:"i";a:1:{s:1:"u";a:1:{s:1:"m";a:1:{s:1:"S";a:1:{s:1:"p";a:1:{s:1:"a";a:1:{s:1:"c";a:1:{s:1:"e";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8203;}}}}}}}}}}}}s:1:"T";a:1:{s:1:"h";a:1:{s:1:"i";a:2:{s:1:"c";a:1:{s:1:"k";a:1:{s:1:"S";a:1:{s:1:"p";a:1:{s:1:"a";a:1:{s:1:"c";a:1:{s:1:"e";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8203;}}}}}}}}s:1:"n";a:1:{s:1:"S";a:1:{s:1:"p";a:1:{s:1:"a";a:1:{s:1:"c";a:1:{s:1:"e";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8203;}}}}}}}}}}s:1:"V";a:1:{s:1:"e";a:1:{s:1:"r";a:1:{s:1:"y";a:1:{s:1:"T";a:1:{s:1:"h";a:1:{s:1:"i";a:1:{s:1:"n";a:1:{s:1:"S";a:1:{s:1:"p";a:1:{s:1:"a";a:1:{s:1:"c";a:1:{s:1:"e";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8203;}}}}}}}}}}}}}}}}}}}}s:1:"s";a:1:{s:1:"t";a:1:{s:1:"e";a:1:{s:1:"d";a:2:{s:1:"G";a:1:{s:1:"r";a:1:{s:1:"e";a:1:{s:1:"a";a:1:{s:1:"t";a:1:{s:1:"e";a:1:{s:1:"r";a:1:{s:1:"G";a:1:{s:1:"r";a:1:{s:1:"e";a:1:{s:1:"a";a:1:{s:1:"t";a:1:{s:1:"e";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8811;}}}}}}}}}}}}}}}s:1:"L";a:1:{s:1:"e";a:1:{s:1:"s";a:1:{s:1:"s";a:1:{s:1:"L";a:1:{s:1:"e";a:1:{s:1:"s";a:1:{s:1:"s";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8810;}}}}}}}}}}}}}s:1:"w";a:1:{s:1:"L";a:1:{s:1:"i";a:1:{s:1:"n";a:1:{s:1:"e";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10;}}}}}}}s:1:"f";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:120081;}}}s:1:"o";a:4:{s:1:"B";a:1:{s:1:"r";a:1:{s:1:"e";a:1:{s:1:"a";a:1:{s:1:"k";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8288;}}}}}}s:1:"n";a:1:{s:1:"B";a:1:{s:1:"r";a:1:{s:1:"e";a:1:{s:1:"a";a:1:{s:1:"k";a:1:{s:1:"i";a:1:{s:1:"n";a:1:{s:1:"g";a:1:{s:1:"S";a:1:{s:1:"p";a:1:{s:1:"a";a:1:{s:1:"c";a:1:{s:1:"e";a:1:{s:1:";";a:1:{s:9:"codepoint";i:160;}}}}}}}}}}}}}}}s:1:"p";a:1:{s:1:"f";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8469;}}}s:1:"t";a:11:{s:1:";";a:1:{s:9:"codepoint";i:10988;}s:1:"C";a:2:{s:1:"o";a:1:{s:1:"n";a:1:{s:1:"g";a:1:{s:1:"r";a:1:{s:1:"u";a:1:{s:1:"e";a:1:{s:1:"n";a:1:{s:1:"t";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8802;}}}}}}}}}s:1:"u";a:1:{s:1:"p";a:1:{s:1:"C";a:1:{s:1:"a";a:1:{s:1:"p";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8813;}}}}}}}s:1:"D";a:1:{s:1:"o";a:1:{s:1:"u";a:1:{s:1:"b";a:1:{s:1:"l";a:1:{s:1:"e";a:1:{s:1:"V";a:1:{s:1:"e";a:1:{s:1:"r";a:1:{s:1:"t";a:1:{s:1:"i";a:1:{s:1:"c";a:1:{s:1:"a";a:1:{s:1:"l";a:1:{s:1:"B";a:1:{s:1:"a";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8742;}}}}}}}}}}}}}}}}}}s:1:"E";a:3:{s:1:"l";a:1:{s:1:"e";a:1:{s:1:"m";a:1:{s:1:"e";a:1:{s:1:"n";a:1:{s:1:"t";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8713;}}}}}}}s:1:"q";a:1:{s:1:"u";a:1:{s:1:"a";a:1:{s:1:"l";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8800;}}}}}s:1:"x";a:1:{s:1:"i";a:1:{s:1:"s";a:1:{s:1:"t";a:1:{s:1:"s";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8708;}}}}}}}s:1:"G";a:1:{s:1:"r";a:1:{s:1:"e";a:1:{s:1:"a";a:1:{s:1:"t";a:1:{s:1:"e";a:1:{s:1:"r";a:4:{s:1:";";a:1:{s:9:"codepoint";i:8815;}s:1:"E";a:1:{s:1:"q";a:1:{s:1:"u";a:1:{s:1:"a";a:1:{s:1:"l";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8817;}}}}}}s:1:"L";a:1:{s:1:"e";a:1:{s:1:"s";a:1:{s:1:"s";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8825;}}}}}s:1:"T";a:1:{s:1:"i";a:1:{s:1:"l";a:1:{s:1:"d";a:1:{s:1:"e";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8821;}}}}}}}}}}}}}s:1:"L";a:1:{s:1:"e";a:2:{s:1:"f";a:1:{s:1:"t";a:1:{s:1:"T";a:1:{s:1:"r";a:1:{s:1:"i";a:1:{s:1:"a";a:1:{s:1:"n";a:1:{s:1:"g";a:1:{s:1:"l";a:1:{s:1:"e";a:2:{s:1:";";a:1:{s:9:"codepoint";i:8938;}s:1:"E";a:1:{s:1:"q";a:1:{s:1:"u";a:1:{s:1:"a";a:1:{s:1:"l";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8940;}}}}}}}}}}}}}}}}s:1:"s";a:1:{s:1:"s";a:4:{s:1:";";a:1:{s:9:"codepoint";i:8814;}s:1:"E";a:1:{s:1:"q";a:1:{s:1:"u";a:1:{s:1:"a";a:1:{s:1:"l";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8816;}}}}}}s:1:"G";a:1:{s:1:"r";a:1:{s:1:"e";a:1:{s:1:"a";a:1:{s:1:"t";a:1:{s:1:"e";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8824;}}}}}}}}s:1:"T";a:1:{s:1:"i";a:1:{s:1:"l";a:1:{s:1:"d";a:1:{s:1:"e";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8820;}}}}}}}}}}s:1:"P";a:1:{s:1:"r";a:1:{s:1:"e";a:1:{s:1:"c";a:1:{s:1:"e";a:1:{s:1:"d";a:1:{s:1:"e";a:1:{s:1:"s";a:2:{s:1:";";a:1:{s:9:"codepoint";i:8832;}s:1:"S";a:1:{s:1:"l";a:1:{s:1:"a";a:1:{s:1:"n";a:1:{s:1:"t";a:1:{s:1:"E";a:1:{s:1:"q";a:1:{s:1:"u";a:1:{s:1:"a";a:1:{s:1:"l";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8928;}}}}}}}}}}}}}}}}}}}s:1:"R";a:2:{s:1:"e";a:1:{s:1:"v";a:1:{s:1:"e";a:1:{s:1:"r";a:1:{s:1:"s";a:1:{s:1:"e";a:1:{s:1:"E";a:1:{s:1:"l";a:1:{s:1:"e";a:1:{s:1:"m";a:1:{s:1:"e";a:1:{s:1:"n";a:1:{s:1:"t";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8716;}}}}}}}}}}}}}}s:1:"i";a:1:{s:1:"g";a:1:{s:1:"h";a:1:{s:1:"t";a:1:{s:1:"T";a:1:{s:1:"r";a:1:{s:1:"i";a:1:{s:1:"a";a:1:{s:1:"n";a:1:{s:1:"g";a:1:{s:1:"l";a:1:{s:1:"e";a:2:{s:1:";";a:1:{s:9:"codepoint";i:8939;}s:1:"E";a:1:{s:1:"q";a:1:{s:1:"u";a:1:{s:1:"a";a:1:{s:1:"l";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8941;}}}}}}}}}}}}}}}}}}}s:1:"S";a:2:{s:1:"q";a:1:{s:1:"u";a:1:{s:1:"a";a:1:{s:1:"r";a:1:{s:1:"e";a:1:{s:1:"S";a:1:{s:1:"u";a:2:{s:1:"b";a:1:{s:1:"s";a:1:{s:1:"e";a:1:{s:1:"t";a:1:{s:1:"E";a:1:{s:1:"q";a:1:{s:1:"u";a:1:{s:1:"a";a:1:{s:1:"l";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8930;}}}}}}}}}}s:1:"p";a:1:{s:1:"e";a:1:{s:1:"r";a:1:{s:1:"s";a:1:{s:1:"e";a:1:{s:1:"t";a:1:{s:1:"E";a:1:{s:1:"q";a:1:{s:1:"u";a:1:{s:1:"a";a:1:{s:1:"l";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8931;}}}}}}}}}}}}}}}}}}}s:1:"u";a:3:{s:1:"b";a:1:{s:1:"s";a:1:{s:1:"e";a:1:{s:1:"t";a:1:{s:1:"E";a:1:{s:1:"q";a:1:{s:1:"u";a:1:{s:1:"a";a:1:{s:1:"l";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8840;}}}}}}}}}}s:1:"c";a:1:{s:1:"c";a:1:{s:1:"e";a:1:{s:1:"e";a:1:{s:1:"d";a:1:{s:1:"s";a:2:{s:1:";";a:1:{s:9:"codepoint";i:8833;}s:1:"S";a:1:{s:1:"l";a:1:{s:1:"a";a:1:{s:1:"n";a:1:{s:1:"t";a:1:{s:1:"E";a:1:{s:1:"q";a:1:{s:1:"u";a:1:{s:1:"a";a:1:{s:1:"l";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8929;}}}}}}}}}}}}}}}}}s:1:"p";a:1:{s:1:"e";a:1:{s:1:"r";a:1:{s:1:"s";a:1:{s:1:"e";a:1:{s:1:"t";a:1:{s:1:"E";a:1:{s:1:"q";a:1:{s:1:"u";a:1:{s:1:"a";a:1:{s:1:"l";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8841;}}}}}}}}}}}}}}s:1:"T";a:1:{s:1:"i";a:1:{s:1:"l";a:1:{s:1:"d";a:1:{s:1:"e";a:4:{s:1:";";a:1:{s:9:"codepoint";i:8769;}s:1:"E";a:1:{s:1:"q";a:1:{s:1:"u";a:1:{s:1:"a";a:1:{s:1:"l";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8772;}}}}}}s:1:"F";a:1:{s:1:"u";a:1:{s:1:"l";a:1:{s:1:"l";a:1:{s:1:"E";a:1:{s:1:"q";a:1:{s:1:"u";a:1:{s:1:"a";a:1:{s:1:"l";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8775;}}}}}}}}}}s:1:"T";a:1:{s:1:"i";a:1:{s:1:"l";a:1:{s:1:"d";a:1:{s:1:"e";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8777;}}}}}}}}}}}s:1:"V";a:1:{s:1:"e";a:1:{s:1:"r";a:1:{s:1:"t";a:1:{s:1:"i";a:1:{s:1:"c";a:1:{s:1:"a";a:1:{s:1:"l";a:1:{s:1:"B";a:1:{s:1:"a";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8740;}}}}}}}}}}}}}}s:1:"s";a:1:{s:1:"c";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:119977;}}}}s:1:"t";a:1:{s:1:"i";a:1:{s:1:"l";a:1:{s:1:"d";a:1:{s:1:"e";a:2:{s:1:";";a:1:{s:9:"codepoint";i:209;}s:9:"codepoint";i:209;}}}}}s:1:"u";a:1:{s:1:";";a:1:{s:9:"codepoint";i:925;}}}s:1:"O";a:14:{s:1:"E";a:1:{s:1:"l";a:1:{s:1:"i";a:1:{s:1:"g";a:1:{s:1:";";a:1:{s:9:"codepoint";i:338;}}}}}s:1:"a";a:1:{s:1:"c";a:1:{s:1:"u";a:1:{s:1:"t";a:1:{s:1:"e";a:2:{s:1:";";a:1:{s:9:"codepoint";i:211;}s:9:"codepoint";i:211;}}}}}s:1:"c";a:2:{s:1:"i";a:1:{s:1:"r";a:1:{s:1:"c";a:2:{s:1:";";a:1:{s:9:"codepoint";i:212;}s:9:"codepoint";i:212;}}}s:1:"y";a:1:{s:1:";";a:1:{s:9:"codepoint";i:1054;}}}s:1:"d";a:1:{s:1:"b";a:1:{s:1:"l";a:1:{s:1:"a";a:1:{s:1:"c";a:1:{s:1:";";a:1:{s:9:"codepoint";i:336;}}}}}}s:1:"f";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:120082;}}}s:1:"g";a:1:{s:1:"r";a:1:{s:1:"a";a:1:{s:1:"v";a:1:{s:1:"e";a:2:{s:1:";";a:1:{s:9:"codepoint";i:210;}s:9:"codepoint";i:210;}}}}}s:1:"m";a:3:{s:1:"a";a:1:{s:1:"c";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:332;}}}}s:1:"e";a:1:{s:1:"g";a:1:{s:1:"a";a:1:{s:1:";";a:1:{s:9:"codepoint";i:937;}}}}s:1:"i";a:1:{s:1:"c";a:1:{s:1:"r";a:1:{s:1:"o";a:1:{s:1:"n";a:1:{s:1:";";a:1:{s:9:"codepoint";i:927;}}}}}}}s:1:"o";a:1:{s:1:"p";a:1:{s:1:"f";a:1:{s:1:";";a:1:{s:9:"codepoint";i:120134;}}}}s:1:"p";a:1:{s:1:"e";a:1:{s:1:"n";a:1:{s:1:"C";a:1:{s:1:"u";a:1:{s:1:"r";a:1:{s:1:"l";a:1:{s:1:"y";a:2:{s:1:"D";a:1:{s:1:"o";a:1:{s:1:"u";a:1:{s:1:"b";a:1:{s:1:"l";a:1:{s:1:"e";a:1:{s:1:"Q";a:1:{s:1:"u";a:1:{s:1:"o";a:1:{s:1:"t";a:1:{s:1:"e";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8220;}}}}}}}}}}}}s:1:"Q";a:1:{s:1:"u";a:1:{s:1:"o";a:1:{s:1:"t";a:1:{s:1:"e";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8216;}}}}}}}}}}}}}}s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10836;}}s:1:"s";a:2:{s:1:"c";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:119978;}}}s:1:"l";a:1:{s:1:"a";a:1:{s:1:"s";a:1:{s:1:"h";a:2:{s:1:";";a:1:{s:9:"codepoint";i:216;}s:9:"codepoint";i:216;}}}}}s:1:"t";a:1:{s:1:"i";a:2:{s:1:"l";a:1:{s:1:"d";a:1:{s:1:"e";a:2:{s:1:";";a:1:{s:9:"codepoint";i:213;}s:9:"codepoint";i:213;}}}s:1:"m";a:1:{s:1:"e";a:1:{s:1:"s";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10807;}}}}}}s:1:"u";a:1:{s:1:"m";a:1:{s:1:"l";a:2:{s:1:";";a:1:{s:9:"codepoint";i:214;}s:9:"codepoint";i:214;}}}s:1:"v";a:1:{s:1:"e";a:1:{s:1:"r";a:2:{s:1:"B";a:2:{s:1:"a";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:175;}}}s:1:"r";a:1:{s:1:"a";a:1:{s:1:"c";a:2:{s:1:"e";a:1:{s:1:";";a:1:{s:9:"codepoint";i:9182;}}s:1:"k";a:1:{s:1:"e";a:1:{s:1:"t";a:1:{s:1:";";a:1:{s:9:"codepoint";i:9140;}}}}}}}}s:1:"P";a:1:{s:1:"a";a:1:{s:1:"r";a:1:{s:1:"e";a:1:{s:1:"n";a:1:{s:1:"t";a:1:{s:1:"h";a:1:{s:1:"e";a:1:{s:1:"s";a:1:{s:1:"i";a:1:{s:1:"s";a:1:{s:1:";";a:1:{s:9:"codepoint";i:9180;}}}}}}}}}}}}}}}}s:1:"P";a:9:{s:1:"a";a:1:{s:1:"r";a:1:{s:1:"t";a:1:{s:1:"i";a:1:{s:1:"a";a:1:{s:1:"l";a:1:{s:1:"D";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8706;}}}}}}}}s:1:"c";a:1:{s:1:"y";a:1:{s:1:";";a:1:{s:9:"codepoint";i:1055;}}}s:1:"f";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:120083;}}}s:1:"h";a:1:{s:1:"i";a:1:{s:1:";";a:1:{s:9:"codepoint";i:934;}}}s:1:"i";a:1:{s:1:";";a:1:{s:9:"codepoint";i:928;}}s:1:"l";a:1:{s:1:"u";a:1:{s:1:"s";a:1:{s:1:"M";a:1:{s:1:"i";a:1:{s:1:"n";a:1:{s:1:"u";a:1:{s:1:"s";a:1:{s:1:";";a:1:{s:9:"codepoint";i:177;}}}}}}}}}s:1:"o";a:2:{s:1:"i";a:1:{s:1:"n";a:1:{s:1:"c";a:1:{s:1:"a";a:1:{s:1:"r";a:1:{s:1:"e";a:1:{s:1:"p";a:1:{s:1:"l";a:1:{s:1:"a";a:1:{s:1:"n";a:1:{s:1:"e";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8460;}}}}}}}}}}}}s:1:"p";a:1:{s:1:"f";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8473;}}}}s:1:"r";a:4:{s:1:";";a:1:{s:9:"codepoint";i:10939;}s:1:"e";a:1:{s:1:"c";a:1:{s:1:"e";a:1:{s:1:"d";a:1:{s:1:"e";a:1:{s:1:"s";a:4:{s:1:";";a:1:{s:9:"codepoint";i:8826;}s:1:"E";a:1:{s:1:"q";a:1:{s:1:"u";a:1:{s:1:"a";a:1:{s:1:"l";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10927;}}}}}}s:1:"S";a:1:{s:1:"l";a:1:{s:1:"a";a:1:{s:1:"n";a:1:{s:1:"t";a:1:{s:1:"E";a:1:{s:1:"q";a:1:{s:1:"u";a:1:{s:1:"a";a:1:{s:1:"l";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8828;}}}}}}}}}}}s:1:"T";a:1:{s:1:"i";a:1:{s:1:"l";a:1:{s:1:"d";a:1:{s:1:"e";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8830;}}}}}}}}}}}}s:1:"i";a:1:{s:1:"m";a:1:{s:1:"e";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8243;}}}}s:1:"o";a:2:{s:1:"d";a:1:{s:1:"u";a:1:{s:1:"c";a:1:{s:1:"t";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8719;}}}}}s:1:"p";a:1:{s:1:"o";a:1:{s:1:"r";a:1:{s:1:"t";a:1:{s:1:"i";a:1:{s:1:"o";a:1:{s:1:"n";a:2:{s:1:";";a:1:{s:9:"codepoint";i:8759;}s:1:"a";a:1:{s:1:"l";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8733;}}}}}}}}}}}}s:1:"s";a:2:{s:1:"c";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:119979;}}}s:1:"i";a:1:{s:1:";";a:1:{s:9:"codepoint";i:936;}}}}s:1:"Q";a:4:{s:1:"U";a:1:{s:1:"O";a:1:{s:1:"T";a:2:{s:1:";";a:1:{s:9:"codepoint";i:34;}s:9:"codepoint";i:34;}}}s:1:"f";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:120084;}}}s:1:"o";a:1:{s:1:"p";a:1:{s:1:"f";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8474;}}}}s:1:"s";a:1:{s:1:"c";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:119980;}}}}}s:1:"R";a:12:{s:1:"B";a:1:{s:1:"a";a:1:{s:1:"r";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10512;}}}}}s:1:"E";a:1:{s:1:"G";a:2:{s:1:";";a:1:{s:9:"codepoint";i:174;}s:9:"codepoint";i:174;}}s:1:"a";a:3:{s:1:"c";a:1:{s:1:"u";a:1:{s:1:"t";a:1:{s:1:"e";a:1:{s:1:";";a:1:{s:9:"codepoint";i:340;}}}}}s:1:"n";a:1:{s:1:"g";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10219;}}}s:1:"r";a:1:{s:1:"r";a:2:{s:1:";";a:1:{s:9:"codepoint";i:8608;}s:1:"t";a:1:{s:1:"l";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10518;}}}}}}s:1:"c";a:3:{s:1:"a";a:1:{s:1:"r";a:1:{s:1:"o";a:1:{s:1:"n";a:1:{s:1:";";a:1:{s:9:"codepoint";i:344;}}}}}s:1:"e";a:1:{s:1:"d";a:1:{s:1:"i";a:1:{s:1:"l";a:1:{s:1:";";a:1:{s:9:"codepoint";i:342;}}}}}s:1:"y";a:1:{s:1:";";a:1:{s:9:"codepoint";i:1056;}}}s:1:"e";a:2:{s:1:";";a:1:{s:9:"codepoint";i:8476;}s:1:"v";a:1:{s:1:"e";a:1:{s:1:"r";a:1:{s:1:"s";a:1:{s:1:"e";a:2:{s:1:"E";a:2:{s:1:"l";a:1:{s:1:"e";a:1:{s:1:"m";a:1:{s:1:"e";a:1:{s:1:"n";a:1:{s:1:"t";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8715;}}}}}}}s:1:"q";a:1:{s:1:"u";a:1:{s:1:"i";a:1:{s:1:"l";a:1:{s:1:"i";a:1:{s:1:"b";a:1:{s:1:"r";a:1:{s:1:"i";a:1:{s:1:"u";a:1:{s:1:"m";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8651;}}}}}}}}}}}}s:1:"U";a:1:{s:1:"p";a:1:{s:1:"E";a:1:{s:1:"q";a:1:{s:1:"u";a:1:{s:1:"i";a:1:{s:1:"l";a:1:{s:1:"i";a:1:{s:1:"b";a:1:{s:1:"r";a:1:{s:1:"i";a:1:{s:1:"u";a:1:{s:1:"m";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10607;}}}}}}}}}}}}}}}}}}}}s:1:"f";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8476;}}}s:1:"h";a:1:{s:1:"o";a:1:{s:1:";";a:1:{s:9:"codepoint";i:929;}}}s:1:"i";a:1:{s:1:"g";a:1:{s:1:"h";a:1:{s:1:"t";a:8:{s:1:"A";a:2:{s:1:"n";a:1:{s:1:"g";a:1:{s:1:"l";a:1:{s:1:"e";a:1:{s:1:"B";a:1:{s:1:"r";a:1:{s:1:"a";a:1:{s:1:"c";a:1:{s:1:"k";a:1:{s:1:"e";a:1:{s:1:"t";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10217;}}}}}}}}}}}}s:1:"r";a:1:{s:1:"r";a:1:{s:1:"o";a:1:{s:1:"w";a:3:{s:1:";";a:1:{s:9:"codepoint";i:8594;}s:1:"B";a:1:{s:1:"a";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8677;}}}}s:1:"L";a:1:{s:1:"e";a:1:{s:1:"f";a:1:{s:1:"t";a:1:{s:1:"A";a:1:{s:1:"r";a:1:{s:1:"r";a:1:{s:1:"o";a:1:{s:1:"w";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8644;}}}}}}}}}}}}}}}s:1:"C";a:1:{s:1:"e";a:1:{s:1:"i";a:1:{s:1:"l";a:1:{s:1:"i";a:1:{s:1:"n";a:1:{s:1:"g";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8969;}}}}}}}}s:1:"D";a:1:{s:1:"o";a:2:{s:1:"u";a:1:{s:1:"b";a:1:{s:1:"l";a:1:{s:1:"e";a:1:{s:1:"B";a:1:{s:1:"r";a:1:{s:1:"a";a:1:{s:1:"c";a:1:{s:1:"k";a:1:{s:1:"e";a:1:{s:1:"t";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10215;}}}}}}}}}}}}s:1:"w";a:1:{s:1:"n";a:2:{s:1:"T";a:1:{s:1:"e";a:1:{s:1:"e";a:1:{s:1:"V";a:1:{s:1:"e";a:1:{s:1:"c";a:1:{s:1:"t";a:1:{s:1:"o";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10589;}}}}}}}}}}s:1:"V";a:1:{s:1:"e";a:1:{s:1:"c";a:1:{s:1:"t";a:1:{s:1:"o";a:1:{s:1:"r";a:2:{s:1:";";a:1:{s:9:"codepoint";i:8642;}s:1:"B";a:1:{s:1:"a";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10581;}}}}}}}}}}}}}}s:1:"F";a:1:{s:1:"l";a:1:{s:1:"o";a:1:{s:1:"o";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8971;}}}}}}s:1:"T";a:2:{s:1:"e";a:1:{s:1:"e";a:3:{s:1:";";a:1:{s:9:"codepoint";i:8866;}s:1:"A";a:1:{s:1:"r";a:1:{s:1:"r";a:1:{s:1:"o";a:1:{s:1:"w";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8614;}}}}}}s:1:"V";a:1:{s:1:"e";a:1:{s:1:"c";a:1:{s:1:"t";a:1:{s:1:"o";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10587;}}}}}}}}}s:1:"r";a:1:{s:1:"i";a:1:{s:1:"a";a:1:{s:1:"n";a:1:{s:1:"g";a:1:{s:1:"l";a:1:{s:1:"e";a:3:{s:1:";";a:1:{s:9:"codepoint";i:8883;}s:1:"B";a:1:{s:1:"a";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10704;}}}}s:1:"E";a:1:{s:1:"q";a:1:{s:1:"u";a:1:{s:1:"a";a:1:{s:1:"l";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8885;}}}}}}}}}}}}}}s:1:"U";a:1:{s:1:"p";a:3:{s:1:"D";a:1:{s:1:"o";a:1:{s:1:"w";a:1:{s:1:"n";a:1:{s:1:"V";a:1:{s:1:"e";a:1:{s:1:"c";a:1:{s:1:"t";a:1:{s:1:"o";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10575;}}}}}}}}}}}s:1:"T";a:1:{s:1:"e";a:1:{s:1:"e";a:1:{s:1:"V";a:1:{s:1:"e";a:1:{s:1:"c";a:1:{s:1:"t";a:1:{s:1:"o";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10588;}}}}}}}}}}s:1:"V";a:1:{s:1:"e";a:1:{s:1:"c";a:1:{s:1:"t";a:1:{s:1:"o";a:1:{s:1:"r";a:2:{s:1:";";a:1:{s:9:"codepoint";i:8638;}s:1:"B";a:1:{s:1:"a";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10580;}}}}}}}}}}}}s:1:"V";a:1:{s:1:"e";a:1:{s:1:"c";a:1:{s:1:"t";a:1:{s:1:"o";a:1:{s:1:"r";a:2:{s:1:";";a:1:{s:9:"codepoint";i:8640;}s:1:"B";a:1:{s:1:"a";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10579;}}}}}}}}}}s:1:"a";a:1:{s:1:"r";a:1:{s:1:"r";a:1:{s:1:"o";a:1:{s:1:"w";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8658;}}}}}}}}}}s:1:"o";a:2:{s:1:"p";a:1:{s:1:"f";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8477;}}}s:1:"u";a:1:{s:1:"n";a:1:{s:1:"d";a:1:{s:1:"I";a:1:{s:1:"m";a:1:{s:1:"p";a:1:{s:1:"l";a:1:{s:1:"i";a:1:{s:1:"e";a:1:{s:1:"s";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10608;}}}}}}}}}}}}s:1:"r";a:1:{s:1:"i";a:1:{s:1:"g";a:1:{s:1:"h";a:1:{s:1:"t";a:1:{s:1:"a";a:1:{s:1:"r";a:1:{s:1:"r";a:1:{s:1:"o";a:1:{s:1:"w";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8667;}}}}}}}}}}}s:1:"s";a:2:{s:1:"c";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8475;}}}s:1:"h";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8625;}}}s:1:"u";a:1:{s:1:"l";a:1:{s:1:"e";a:1:{s:1:"D";a:1:{s:1:"e";a:1:{s:1:"l";a:1:{s:1:"a";a:1:{s:1:"y";a:1:{s:1:"e";a:1:{s:1:"d";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10740;}}}}}}}}}}}}s:1:"S";a:13:{s:1:"H";a:2:{s:1:"C";a:1:{s:1:"H";a:1:{s:1:"c";a:1:{s:1:"y";a:1:{s:1:";";a:1:{s:9:"codepoint";i:1065;}}}}}s:1:"c";a:1:{s:1:"y";a:1:{s:1:";";a:1:{s:9:"codepoint";i:1064;}}}}s:1:"O";a:1:{s:1:"F";a:1:{s:1:"T";a:1:{s:1:"c";a:1:{s:1:"y";a:1:{s:1:";";a:1:{s:9:"codepoint";i:1068;}}}}}}s:1:"a";a:1:{s:1:"c";a:1:{s:1:"u";a:1:{s:1:"t";a:1:{s:1:"e";a:1:{s:1:";";a:1:{s:9:"codepoint";i:346;}}}}}}s:1:"c";a:5:{s:1:";";a:1:{s:9:"codepoint";i:10940;}s:1:"a";a:1:{s:1:"r";a:1:{s:1:"o";a:1:{s:1:"n";a:1:{s:1:";";a:1:{s:9:"codepoint";i:352;}}}}}s:1:"e";a:1:{s:1:"d";a:1:{s:1:"i";a:1:{s:1:"l";a:1:{s:1:";";a:1:{s:9:"codepoint";i:350;}}}}}s:1:"i";a:1:{s:1:"r";a:1:{s:1:"c";a:1:{s:1:";";a:1:{s:9:"codepoint";i:348;}}}}s:1:"y";a:1:{s:1:";";a:1:{s:9:"codepoint";i:1057;}}}s:1:"f";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:120086;}}}s:1:"h";a:1:{s:1:"o";a:1:{s:1:"r";a:1:{s:1:"t";a:4:{s:1:"D";a:1:{s:1:"o";a:1:{s:1:"w";a:1:{s:1:"n";a:1:{s:1:"A";a:1:{s:1:"r";a:1:{s:1:"r";a:1:{s:1:"o";a:1:{s:1:"w";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8595;}}}}}}}}}}s:1:"L";a:1:{s:1:"e";a:1:{s:1:"f";a:1:{s:1:"t";a:1:{s:1:"A";a:1:{s:1:"r";a:1:{s:1:"r";a:1:{s:1:"o";a:1:{s:1:"w";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8592;}}}}}}}}}}s:1:"R";a:1:{s:1:"i";a:1:{s:1:"g";a:1:{s:1:"h";a:1:{s:1:"t";a:1:{s:1:"A";a:1:{s:1:"r";a:1:{s:1:"r";a:1:{s:1:"o";a:1:{s:1:"w";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8594;}}}}}}}}}}}s:1:"U";a:1:{s:1:"p";a:1:{s:1:"A";a:1:{s:1:"r";a:1:{s:1:"r";a:1:{s:1:"o";a:1:{s:1:"w";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8593;}}}}}}}}}}}}s:1:"i";a:1:{s:1:"g";a:1:{s:1:"m";a:1:{s:1:"a";a:1:{s:1:";";a:1:{s:9:"codepoint";i:931;}}}}}s:1:"m";a:1:{s:1:"a";a:1:{s:1:"l";a:1:{s:1:"l";a:1:{s:1:"C";a:1:{s:1:"i";a:1:{s:1:"r";a:1:{s:1:"c";a:1:{s:1:"l";a:1:{s:1:"e";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8728;}}}}}}}}}}}s:1:"o";a:1:{s:1:"p";a:1:{s:1:"f";a:1:{s:1:";";a:1:{s:9:"codepoint";i:120138;}}}}s:1:"q";a:2:{s:1:"r";a:1:{s:1:"t";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8730;}}}s:1:"u";a:1:{s:1:"a";a:1:{s:1:"r";a:1:{s:1:"e";a:4:{s:1:";";a:1:{s:9:"codepoint";i:9633;}s:1:"I";a:1:{s:1:"n";a:1:{s:1:"t";a:1:{s:1:"e";a:1:{s:1:"r";a:1:{s:1:"s";a:1:{s:1:"e";a:1:{s:1:"c";a:1:{s:1:"t";a:1:{s:1:"i";a:1:{s:1:"o";a:1:{s:1:"n";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8851;}}}}}}}}}}}}}s:1:"S";a:1:{s:1:"u";a:2:{s:1:"b";a:1:{s:1:"s";a:1:{s:1:"e";a:1:{s:1:"t";a:2:{s:1:";";a:1:{s:9:"codepoint";i:8847;}s:1:"E";a:1:{s:1:"q";a:1:{s:1:"u";a:1:{s:1:"a";a:1:{s:1:"l";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8849;}}}}}}}}}}s:1:"p";a:1:{s:1:"e";a:1:{s:1:"r";a:1:{s:1:"s";a:1:{s:1:"e";a:1:{s:1:"t";a:2:{s:1:";";a:1:{s:9:"codepoint";i:8848;}s:1:"E";a:1:{s:1:"q";a:1:{s:1:"u";a:1:{s:1:"a";a:1:{s:1:"l";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8850;}}}}}}}}}}}}}}s:1:"U";a:1:{s:1:"n";a:1:{s:1:"i";a:1:{s:1:"o";a:1:{s:1:"n";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8852;}}}}}}}}}}}s:1:"s";a:1:{s:1:"c";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:119982;}}}}s:1:"t";a:1:{s:1:"a";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8902;}}}}s:1:"u";a:4:{s:1:"b";a:2:{s:1:";";a:1:{s:9:"codepoint";i:8912;}s:1:"s";a:1:{s:1:"e";a:1:{s:1:"t";a:2:{s:1:";";a:1:{s:9:"codepoint";i:8912;}s:1:"E";a:1:{s:1:"q";a:1:{s:1:"u";a:1:{s:1:"a";a:1:{s:1:"l";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8838;}}}}}}}}}}s:1:"c";a:2:{s:1:"c";a:1:{s:1:"e";a:1:{s:1:"e";a:1:{s:1:"d";a:1:{s:1:"s";a:4:{s:1:";";a:1:{s:9:"codepoint";i:8827;}s:1:"E";a:1:{s:1:"q";a:1:{s:1:"u";a:1:{s:1:"a";a:1:{s:1:"l";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10928;}}}}}}s:1:"S";a:1:{s:1:"l";a:1:{s:1:"a";a:1:{s:1:"n";a:1:{s:1:"t";a:1:{s:1:"E";a:1:{s:1:"q";a:1:{s:1:"u";a:1:{s:1:"a";a:1:{s:1:"l";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8829;}}}}}}}}}}}s:1:"T";a:1:{s:1:"i";a:1:{s:1:"l";a:1:{s:1:"d";a:1:{s:1:"e";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8831;}}}}}}}}}}}s:1:"h";a:1:{s:1:"T";a:1:{s:1:"h";a:1:{s:1:"a";a:1:{s:1:"t";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8715;}}}}}}}s:1:"m";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8721;}}s:1:"p";a:3:{s:1:";";a:1:{s:9:"codepoint";i:8913;}s:1:"e";a:1:{s:1:"r";a:1:{s:1:"s";a:1:{s:1:"e";a:1:{s:1:"t";a:2:{s:1:";";a:1:{s:9:"codepoint";i:8835;}s:1:"E";a:1:{s:1:"q";a:1:{s:1:"u";a:1:{s:1:"a";a:1:{s:1:"l";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8839;}}}}}}}}}}}s:1:"s";a:1:{s:1:"e";a:1:{s:1:"t";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8913;}}}}}}}s:1:"T";a:11:{s:1:"H";a:1:{s:1:"O";a:1:{s:1:"R";a:1:{s:1:"N";a:2:{s:1:";";a:1:{s:9:"codepoint";i:222;}s:9:"codepoint";i:222;}}}}s:1:"R";a:1:{s:1:"A";a:1:{s:1:"D";a:1:{s:1:"E";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8482;}}}}}s:1:"S";a:2:{s:1:"H";a:1:{s:1:"c";a:1:{s:1:"y";a:1:{s:1:";";a:1:{s:9:"codepoint";i:1035;}}}}s:1:"c";a:1:{s:1:"y";a:1:{s:1:";";a:1:{s:9:"codepoint";i:1062;}}}}s:1:"a";a:2:{s:1:"b";a:1:{s:1:";";a:1:{s:9:"codepoint";i:9;}}s:1:"u";a:1:{s:1:";";a:1:{s:9:"codepoint";i:932;}}}s:1:"c";a:3:{s:1:"a";a:1:{s:1:"r";a:1:{s:1:"o";a:1:{s:1:"n";a:1:{s:1:";";a:1:{s:9:"codepoint";i:356;}}}}}s:1:"e";a:1:{s:1:"d";a:1:{s:1:"i";a:1:{s:1:"l";a:1:{s:1:";";a:1:{s:9:"codepoint";i:354;}}}}}s:1:"y";a:1:{s:1:";";a:1:{s:9:"codepoint";i:1058;}}}s:1:"f";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:120087;}}}s:1:"h";a:2:{s:1:"e";a:2:{s:1:"r";a:1:{s:1:"e";a:1:{s:1:"f";a:1:{s:1:"o";a:1:{s:1:"r";a:1:{s:1:"e";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8756;}}}}}}}s:1:"t";a:1:{s:1:"a";a:1:{s:1:";";a:1:{s:9:"codepoint";i:920;}}}}s:1:"i";a:1:{s:1:"n";a:1:{s:1:"S";a:1:{s:1:"p";a:1:{s:1:"a";a:1:{s:1:"c";a:1:{s:1:"e";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8201;}}}}}}}}}s:1:"i";a:1:{s:1:"l";a:1:{s:1:"d";a:1:{s:1:"e";a:4:{s:1:";";a:1:{s:9:"codepoint";i:8764;}s:1:"E";a:1:{s:1:"q";a:1:{s:1:"u";a:1:{s:1:"a";a:1:{s:1:"l";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8771;}}}}}}s:1:"F";a:1:{s:1:"u";a:1:{s:1:"l";a:1:{s:1:"l";a:1:{s:1:"E";a:1:{s:1:"q";a:1:{s:1:"u";a:1:{s:1:"a";a:1:{s:1:"l";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8773;}}}}}}}}}}s:1:"T";a:1:{s:1:"i";a:1:{s:1:"l";a:1:{s:1:"d";a:1:{s:1:"e";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8776;}}}}}}}}}}s:1:"o";a:1:{s:1:"p";a:1:{s:1:"f";a:1:{s:1:";";a:1:{s:9:"codepoint";i:120139;}}}}s:1:"r";a:1:{s:1:"i";a:1:{s:1:"p";a:1:{s:1:"l";a:1:{s:1:"e";a:1:{s:1:"D";a:1:{s:1:"o";a:1:{s:1:"t";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8411;}}}}}}}}}s:1:"s";a:2:{s:1:"c";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:119983;}}}s:1:"t";a:1:{s:1:"r";a:1:{s:1:"o";a:1:{s:1:"k";a:1:{s:1:";";a:1:{s:9:"codepoint";i:358;}}}}}}}s:1:"U";a:14:{s:1:"a";a:2:{s:1:"c";a:1:{s:1:"u";a:1:{s:1:"t";a:1:{s:1:"e";a:2:{s:1:";";a:1:{s:9:"codepoint";i:218;}s:9:"codepoint";i:218;}}}}s:1:"r";a:1:{s:1:"r";a:2:{s:1:";";a:1:{s:9:"codepoint";i:8607;}s:1:"o";a:1:{s:1:"c";a:1:{s:1:"i";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10569;}}}}}}}}s:1:"b";a:1:{s:1:"r";a:2:{s:1:"c";a:1:{s:1:"y";a:1:{s:1:";";a:1:{s:9:"codepoint";i:1038;}}}s:1:"e";a:1:{s:1:"v";a:1:{s:1:"e";a:1:{s:1:";";a:1:{s:9:"codepoint";i:364;}}}}}}s:1:"c";a:2:{s:1:"i";a:1:{s:1:"r";a:1:{s:1:"c";a:2:{s:1:";";a:1:{s:9:"codepoint";i:219;}s:9:"codepoint";i:219;}}}s:1:"y";a:1:{s:1:";";a:1:{s:9:"codepoint";i:1059;}}}s:1:"d";a:1:{s:1:"b";a:1:{s:1:"l";a:1:{s:1:"a";a:1:{s:1:"c";a:1:{s:1:";";a:1:{s:9:"codepoint";i:368;}}}}}}s:1:"f";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:120088;}}}s:1:"g";a:1:{s:1:"r";a:1:{s:1:"a";a:1:{s:1:"v";a:1:{s:1:"e";a:2:{s:1:";";a:1:{s:9:"codepoint";i:217;}s:9:"codepoint";i:217;}}}}}s:1:"m";a:1:{s:1:"a";a:1:{s:1:"c";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:362;}}}}}s:1:"n";a:2:{s:1:"d";a:1:{s:1:"e";a:1:{s:1:"r";a:2:{s:1:"B";a:2:{s:1:"a";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:818;}}}s:1:"r";a:1:{s:1:"a";a:1:{s:1:"c";a:2:{s:1:"e";a:1:{s:1:";";a:1:{s:9:"codepoint";i:9183;}}s:1:"k";a:1:{s:1:"e";a:1:{s:1:"t";a:1:{s:1:";";a:1:{s:9:"codepoint";i:9141;}}}}}}}}s:1:"P";a:1:{s:1:"a";a:1:{s:1:"r";a:1:{s:1:"e";a:1:{s:1:"n";a:1:{s:1:"t";a:1:{s:1:"h";a:1:{s:1:"e";a:1:{s:1:"s";a:1:{s:1:"i";a:1:{s:1:"s";a:1:{s:1:";";a:1:{s:9:"codepoint";i:9181;}}}}}}}}}}}}}}}s:1:"i";a:1:{s:1:"o";a:1:{s:1:"n";a:2:{s:1:";";a:1:{s:9:"codepoint";i:8899;}s:1:"P";a:1:{s:1:"l";a:1:{s:1:"u";a:1:{s:1:"s";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8846;}}}}}}}}}s:1:"o";a:2:{s:1:"g";a:1:{s:1:"o";a:1:{s:1:"n";a:1:{s:1:";";a:1:{s:9:"codepoint";i:370;}}}}s:1:"p";a:1:{s:1:"f";a:1:{s:1:";";a:1:{s:9:"codepoint";i:120140;}}}}s:1:"p";a:8:{s:1:"A";a:1:{s:1:"r";a:1:{s:1:"r";a:1:{s:1:"o";a:1:{s:1:"w";a:3:{s:1:";";a:1:{s:9:"codepoint";i:8593;}s:1:"B";a:1:{s:1:"a";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10514;}}}}s:1:"D";a:1:{s:1:"o";a:1:{s:1:"w";a:1:{s:1:"n";a:1:{s:1:"A";a:1:{s:1:"r";a:1:{s:1:"r";a:1:{s:1:"o";a:1:{s:1:"w";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8645;}}}}}}}}}}}}}}}s:1:"D";a:1:{s:1:"o";a:1:{s:1:"w";a:1:{s:1:"n";a:1:{s:1:"A";a:1:{s:1:"r";a:1:{s:1:"r";a:1:{s:1:"o";a:1:{s:1:"w";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8597;}}}}}}}}}}s:1:"E";a:1:{s:1:"q";a:1:{s:1:"u";a:1:{s:1:"i";a:1:{s:1:"l";a:1:{s:1:"i";a:1:{s:1:"b";a:1:{s:1:"r";a:1:{s:1:"i";a:1:{s:1:"u";a:1:{s:1:"m";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10606;}}}}}}}}}}}}s:1:"T";a:1:{s:1:"e";a:1:{s:1:"e";a:2:{s:1:";";a:1:{s:9:"codepoint";i:8869;}s:1:"A";a:1:{s:1:"r";a:1:{s:1:"r";a:1:{s:1:"o";a:1:{s:1:"w";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8613;}}}}}}}}}s:1:"a";a:1:{s:1:"r";a:1:{s:1:"r";a:1:{s:1:"o";a:1:{s:1:"w";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8657;}}}}}}s:1:"d";a:1:{s:1:"o";a:1:{s:1:"w";a:1:{s:1:"n";a:1:{s:1:"a";a:1:{s:1:"r";a:1:{s:1:"r";a:1:{s:1:"o";a:1:{s:1:"w";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8661;}}}}}}}}}}s:1:"p";a:1:{s:1:"e";a:1:{s:1:"r";a:2:{s:1:"L";a:1:{s:1:"e";a:1:{s:1:"f";a:1:{s:1:"t";a:1:{s:1:"A";a:1:{s:1:"r";a:1:{s:1:"r";a:1:{s:1:"o";a:1:{s:1:"w";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8598;}}}}}}}}}}s:1:"R";a:1:{s:1:"i";a:1:{s:1:"g";a:1:{s:1:"h";a:1:{s:1:"t";a:1:{s:1:"A";a:1:{s:1:"r";a:1:{s:1:"r";a:1:{s:1:"o";a:1:{s:1:"w";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8599;}}}}}}}}}}}}}}s:1:"s";a:1:{s:1:"i";a:2:{s:1:";";a:1:{s:9:"codepoint";i:978;}s:1:"l";a:1:{s:1:"o";a:1:{s:1:"n";a:1:{s:1:";";a:1:{s:9:"codepoint";i:933;}}}}}}}s:1:"r";a:1:{s:1:"i";a:1:{s:1:"n";a:1:{s:1:"g";a:1:{s:1:";";a:1:{s:9:"codepoint";i:366;}}}}}s:1:"s";a:1:{s:1:"c";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:119984;}}}}s:1:"t";a:1:{s:1:"i";a:1:{s:1:"l";a:1:{s:1:"d";a:1:{s:1:"e";a:1:{s:1:";";a:1:{s:9:"codepoint";i:360;}}}}}}s:1:"u";a:1:{s:1:"m";a:1:{s:1:"l";a:2:{s:1:";";a:1:{s:9:"codepoint";i:220;}s:9:"codepoint";i:220;}}}}s:1:"V";a:9:{s:1:"D";a:1:{s:1:"a";a:1:{s:1:"s";a:1:{s:1:"h";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8875;}}}}}s:1:"b";a:1:{s:1:"a";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10987;}}}}s:1:"c";a:1:{s:1:"y";a:1:{s:1:";";a:1:{s:9:"codepoint";i:1042;}}}s:1:"d";a:1:{s:1:"a";a:1:{s:1:"s";a:1:{s:1:"h";a:2:{s:1:";";a:1:{s:9:"codepoint";i:8873;}s:1:"l";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10982;}}}}}}s:1:"e";a:2:{s:1:"e";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8897;}}s:1:"r";a:3:{s:1:"b";a:1:{s:1:"a";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8214;}}}}s:1:"t";a:2:{s:1:";";a:1:{s:9:"codepoint";i:8214;}s:1:"i";a:1:{s:1:"c";a:1:{s:1:"a";a:1:{s:1:"l";a:4:{s:1:"B";a:1:{s:1:"a";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8739;}}}}s:1:"L";a:1:{s:1:"i";a:1:{s:1:"n";a:1:{s:1:"e";a:1:{s:1:";";a:1:{s:9:"codepoint";i:124;}}}}}s:1:"S";a:1:{s:1:"e";a:1:{s:1:"p";a:1:{s:1:"a";a:1:{s:1:"r";a:1:{s:1:"a";a:1:{s:1:"t";a:1:{s:1:"o";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10072;}}}}}}}}}}s:1:"T";a:1:{s:1:"i";a:1:{s:1:"l";a:1:{s:1:"d";a:1:{s:1:"e";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8768;}}}}}}}}}}}s:1:"y";a:1:{s:1:"T";a:1:{s:1:"h";a:1:{s:1:"i";a:1:{s:1:"n";a:1:{s:1:"S";a:1:{s:1:"p";a:1:{s:1:"a";a:1:{s:1:"c";a:1:{s:1:"e";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8202;}}}}}}}}}}}}}s:1:"f";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:120089;}}}s:1:"o";a:1:{s:1:"p";a:1:{s:1:"f";a:1:{s:1:";";a:1:{s:9:"codepoint";i:120141;}}}}s:1:"s";a:1:{s:1:"c";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:119985;}}}}s:1:"v";a:1:{s:1:"d";a:1:{s:1:"a";a:1:{s:1:"s";a:1:{s:1:"h";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8874;}}}}}}}s:1:"W";a:5:{s:1:"c";a:1:{s:1:"i";a:1:{s:1:"r";a:1:{s:1:"c";a:1:{s:1:";";a:1:{s:9:"codepoint";i:372;}}}}}s:1:"e";a:1:{s:1:"d";a:1:{s:1:"g";a:1:{s:1:"e";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8896;}}}}}s:1:"f";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:120090;}}}s:1:"o";a:1:{s:1:"p";a:1:{s:1:"f";a:1:{s:1:";";a:1:{s:9:"codepoint";i:120142;}}}}s:1:"s";a:1:{s:1:"c";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:119986;}}}}}s:1:"X";a:4:{s:1:"f";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:120091;}}}s:1:"i";a:1:{s:1:";";a:1:{s:9:"codepoint";i:926;}}s:1:"o";a:1:{s:1:"p";a:1:{s:1:"f";a:1:{s:1:";";a:1:{s:9:"codepoint";i:120143;}}}}s:1:"s";a:1:{s:1:"c";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:119987;}}}}}s:1:"Y";a:9:{s:1:"A";a:1:{s:1:"c";a:1:{s:1:"y";a:1:{s:1:";";a:1:{s:9:"codepoint";i:1071;}}}}s:1:"I";a:1:{s:1:"c";a:1:{s:1:"y";a:1:{s:1:";";a:1:{s:9:"codepoint";i:1031;}}}}s:1:"U";a:1:{s:1:"c";a:1:{s:1:"y";a:1:{s:1:";";a:1:{s:9:"codepoint";i:1070;}}}}s:1:"a";a:1:{s:1:"c";a:1:{s:1:"u";a:1:{s:1:"t";a:1:{s:1:"e";a:2:{s:1:";";a:1:{s:9:"codepoint";i:221;}s:9:"codepoint";i:221;}}}}}s:1:"c";a:2:{s:1:"i";a:1:{s:1:"r";a:1:{s:1:"c";a:1:{s:1:";";a:1:{s:9:"codepoint";i:374;}}}}s:1:"y";a:1:{s:1:";";a:1:{s:9:"codepoint";i:1067;}}}s:1:"f";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:120092;}}}s:1:"o";a:1:{s:1:"p";a:1:{s:1:"f";a:1:{s:1:";";a:1:{s:9:"codepoint";i:120144;}}}}s:1:"s";a:1:{s:1:"c";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:119988;}}}}s:1:"u";a:1:{s:1:"m";a:1:{s:1:"l";a:1:{s:1:";";a:1:{s:9:"codepoint";i:376;}}}}}s:1:"Z";a:8:{s:1:"H";a:1:{s:1:"c";a:1:{s:1:"y";a:1:{s:1:";";a:1:{s:9:"codepoint";i:1046;}}}}s:1:"a";a:1:{s:1:"c";a:1:{s:1:"u";a:1:{s:1:"t";a:1:{s:1:"e";a:1:{s:1:";";a:1:{s:9:"codepoint";i:377;}}}}}}s:1:"c";a:2:{s:1:"a";a:1:{s:1:"r";a:1:{s:1:"o";a:1:{s:1:"n";a:1:{s:1:";";a:1:{s:9:"codepoint";i:381;}}}}}s:1:"y";a:1:{s:1:";";a:1:{s:9:"codepoint";i:1047;}}}s:1:"d";a:1:{s:1:"o";a:1:{s:1:"t";a:1:{s:1:";";a:1:{s:9:"codepoint";i:379;}}}}s:1:"e";a:2:{s:1:"r";a:1:{s:1:"o";a:1:{s:1:"W";a:1:{s:1:"i";a:1:{s:1:"d";a:1:{s:1:"t";a:1:{s:1:"h";a:1:{s:1:"S";a:1:{s:1:"p";a:1:{s:1:"a";a:1:{s:1:"c";a:1:{s:1:"e";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8203;}}}}}}}}}}}}}s:1:"t";a:1:{s:1:"a";a:1:{s:1:";";a:1:{s:9:"codepoint";i:918;}}}}s:1:"f";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8488;}}}s:1:"o";a:1:{s:1:"p";a:1:{s:1:"f";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8484;}}}}s:1:"s";a:1:{s:1:"c";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:119989;}}}}}s:1:"a";a:16:{s:1:"a";a:1:{s:1:"c";a:1:{s:1:"u";a:1:{s:1:"t";a:1:{s:1:"e";a:2:{s:1:";";a:1:{s:9:"codepoint";i:225;}s:9:"codepoint";i:225;}}}}}s:1:"b";a:1:{s:1:"r";a:1:{s:1:"e";a:1:{s:1:"v";a:1:{s:1:"e";a:1:{s:1:";";a:1:{s:9:"codepoint";i:259;}}}}}}s:1:"c";a:5:{s:1:";";a:1:{s:9:"codepoint";i:8766;}s:1:"d";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8767;}}s:1:"i";a:1:{s:1:"r";a:1:{s:1:"c";a:2:{s:1:";";a:1:{s:9:"codepoint";i:226;}s:9:"codepoint";i:226;}}}s:1:"u";a:1:{s:1:"t";a:1:{s:1:"e";a:2:{s:1:";";a:1:{s:9:"codepoint";i:180;}s:9:"codepoint";i:180;}}}s:1:"y";a:1:{s:1:";";a:1:{s:9:"codepoint";i:1072;}}}s:1:"e";a:1:{s:1:"l";a:1:{s:1:"i";a:1:{s:1:"g";a:2:{s:1:";";a:1:{s:9:"codepoint";i:230;}s:9:"codepoint";i:230;}}}}s:1:"f";a:2:{s:1:";";a:1:{s:9:"codepoint";i:8289;}s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:120094;}}}s:1:"g";a:1:{s:1:"r";a:1:{s:1:"a";a:1:{s:1:"v";a:1:{s:1:"e";a:2:{s:1:";";a:1:{s:9:"codepoint";i:224;}s:9:"codepoint";i:224;}}}}}s:1:"l";a:2:{s:1:"e";a:2:{s:1:"f";a:1:{s:1:"s";a:1:{s:1:"y";a:1:{s:1:"m";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8501;}}}}}s:1:"p";a:1:{s:1:"h";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8501;}}}}s:1:"p";a:1:{s:1:"h";a:1:{s:1:"a";a:1:{s:1:";";a:1:{s:9:"codepoint";i:945;}}}}}s:1:"m";a:2:{s:1:"a";a:2:{s:1:"c";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:257;}}}s:1:"l";a:1:{s:1:"g";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10815;}}}}s:1:"p";a:2:{s:1:";";a:1:{s:9:"codepoint";i:38;}s:9:"codepoint";i:38;}}s:1:"n";a:2:{s:1:"d";a:5:{s:1:";";a:1:{s:9:"codepoint";i:8743;}s:1:"a";a:1:{s:1:"n";a:1:{s:1:"d";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10837;}}}}s:1:"d";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10844;}}s:1:"s";a:1:{s:1:"l";a:1:{s:1:"o";a:1:{s:1:"p";a:1:{s:1:"e";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10840;}}}}}}s:1:"v";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10842;}}}s:1:"g";a:7:{s:1:";";a:1:{s:9:"codepoint";i:8736;}s:1:"e";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10660;}}s:1:"l";a:1:{s:1:"e";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8736;}}}s:1:"m";a:1:{s:1:"s";a:1:{s:1:"d";a:2:{s:1:";";a:1:{s:9:"codepoint";i:8737;}s:1:"a";a:8:{s:1:"a";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10664;}}s:1:"b";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10665;}}s:1:"c";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10666;}}s:1:"d";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10667;}}s:1:"e";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10668;}}s:1:"f";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10669;}}s:1:"g";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10670;}}s:1:"h";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10671;}}}}}}s:1:"r";a:1:{s:1:"t";a:2:{s:1:";";a:1:{s:9:"codepoint";i:8735;}s:1:"v";a:1:{s:1:"b";a:2:{s:1:";";a:1:{s:9:"codepoint";i:8894;}s:1:"d";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10653;}}}}}}s:1:"s";a:2:{s:1:"p";a:1:{s:1:"h";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8738;}}}s:1:"t";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8491;}}}s:1:"z";a:1:{s:1:"a";a:1:{s:1:"r";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:9084;}}}}}}}s:1:"o";a:2:{s:1:"g";a:1:{s:1:"o";a:1:{s:1:"n";a:1:{s:1:";";a:1:{s:9:"codepoint";i:261;}}}}s:1:"p";a:1:{s:1:"f";a:1:{s:1:";";a:1:{s:9:"codepoint";i:120146;}}}}s:1:"p";a:7:{s:1:";";a:1:{s:9:"codepoint";i:8776;}s:1:"E";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10864;}}s:1:"a";a:1:{s:1:"c";a:1:{s:1:"i";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10863;}}}}}s:1:"e";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8778;}}s:1:"i";a:1:{s:1:"d";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8779;}}}s:1:"o";a:1:{s:1:"s";a:1:{s:1:";";a:1:{s:9:"codepoint";i:39;}}}s:1:"p";a:1:{s:1:"r";a:1:{s:1:"o";a:1:{s:1:"x";a:2:{s:1:";";a:1:{s:9:"codepoint";i:8776;}s:1:"e";a:1:{s:1:"q";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8778;}}}}}}}}s:1:"r";a:1:{s:1:"i";a:1:{s:1:"n";a:1:{s:1:"g";a:2:{s:1:";";a:1:{s:9:"codepoint";i:229;}s:9:"codepoint";i:229;}}}}s:1:"s";a:3:{s:1:"c";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:119990;}}}s:1:"t";a:1:{s:1:";";a:1:{s:9:"codepoint";i:42;}}s:1:"y";a:1:{s:1:"m";a:1:{s:1:"p";a:2:{s:1:";";a:1:{s:9:"codepoint";i:8776;}s:1:"e";a:1:{s:1:"q";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8781;}}}}}}}s:1:"t";a:1:{s:1:"i";a:1:{s:1:"l";a:1:{s:1:"d";a:1:{s:1:"e";a:2:{s:1:";";a:1:{s:9:"codepoint";i:227;}s:9:"codepoint";i:227;}}}}}s:1:"u";a:1:{s:1:"m";a:1:{s:1:"l";a:2:{s:1:";";a:1:{s:9:"codepoint";i:228;}s:9:"codepoint";i:228;}}}s:1:"w";a:2:{s:1:"c";a:1:{s:1:"o";a:1:{s:1:"n";a:1:{s:1:"i";a:1:{s:1:"n";a:1:{s:1:"t";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8755;}}}}}}}s:1:"i";a:1:{s:1:"n";a:1:{s:1:"t";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10769;}}}}}}s:1:"b";a:16:{s:1:"N";a:1:{s:1:"o";a:1:{s:1:"t";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10989;}}}}s:1:"a";a:2:{s:1:"c";a:1:{s:1:"k";a:4:{s:1:"c";a:1:{s:1:"o";a:1:{s:1:"n";a:1:{s:1:"g";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8780;}}}}}s:1:"e";a:1:{s:1:"p";a:1:{s:1:"s";a:1:{s:1:"i";a:1:{s:1:"l";a:1:{s:1:"o";a:1:{s:1:"n";a:1:{s:1:";";a:1:{s:9:"codepoint";i:1014;}}}}}}}}s:1:"p";a:1:{s:1:"r";a:1:{s:1:"i";a:1:{s:1:"m";a:1:{s:1:"e";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8245;}}}}}}s:1:"s";a:1:{s:1:"i";a:1:{s:1:"m";a:2:{s:1:";";a:1:{s:9:"codepoint";i:8765;}s:1:"e";a:1:{s:1:"q";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8909;}}}}}}}}s:1:"r";a:2:{s:1:"v";a:1:{s:1:"e";a:1:{s:1:"e";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8893;}}}}s:1:"w";a:1:{s:1:"e";a:1:{s:1:"d";a:2:{s:1:";";a:1:{s:9:"codepoint";i:8965;}s:1:"g";a:1:{s:1:"e";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8965;}}}}}}}}s:1:"b";a:1:{s:1:"r";a:1:{s:1:"k";a:2:{s:1:";";a:1:{s:9:"codepoint";i:9141;}s:1:"t";a:1:{s:1:"b";a:1:{s:1:"r";a:1:{s:1:"k";a:1:{s:1:";";a:1:{s:9:"codepoint";i:9142;}}}}}}}}s:1:"c";a:2:{s:1:"o";a:1:{s:1:"n";a:1:{s:1:"g";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8780;}}}}s:1:"y";a:1:{s:1:";";a:1:{s:9:"codepoint";i:1073;}}}s:1:"d";a:1:{s:1:"q";a:1:{s:1:"u";a:1:{s:1:"o";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8222;}}}}}s:1:"e";a:5:{s:1:"c";a:1:{s:1:"a";a:1:{s:1:"u";a:1:{s:1:"s";a:2:{s:1:";";a:1:{s:9:"codepoint";i:8757;}s:1:"e";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8757;}}}}}}s:1:"m";a:1:{s:1:"p";a:1:{s:1:"t";a:1:{s:1:"y";a:1:{s:1:"v";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10672;}}}}}}s:1:"p";a:1:{s:1:"s";a:1:{s:1:"i";a:1:{s:1:";";a:1:{s:9:"codepoint";i:1014;}}}}s:1:"r";a:1:{s:1:"n";a:1:{s:1:"o";a:1:{s:1:"u";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8492;}}}}}s:1:"t";a:3:{s:1:"a";a:1:{s:1:";";a:1:{s:9:"codepoint";i:946;}}s:1:"h";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8502;}}s:1:"w";a:1:{s:1:"e";a:1:{s:1:"e";a:1:{s:1:"n";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8812;}}}}}}}s:1:"f";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:120095;}}}s:1:"i";a:1:{s:1:"g";a:7:{s:1:"c";a:3:{s:1:"a";a:1:{s:1:"p";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8898;}}}s:1:"i";a:1:{s:1:"r";a:1:{s:1:"c";a:1:{s:1:";";a:1:{s:9:"codepoint";i:9711;}}}}s:1:"u";a:1:{s:1:"p";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8899;}}}}s:1:"o";a:3:{s:1:"d";a:1:{s:1:"o";a:1:{s:1:"t";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10752;}}}}s:1:"p";a:1:{s:1:"l";a:1:{s:1:"u";a:1:{s:1:"s";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10753;}}}}}s:1:"t";a:1:{s:1:"i";a:1:{s:1:"m";a:1:{s:1:"e";a:1:{s:1:"s";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10754;}}}}}}}s:1:"s";a:2:{s:1:"q";a:1:{s:1:"c";a:1:{s:1:"u";a:1:{s:1:"p";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10758;}}}}}s:1:"t";a:1:{s:1:"a";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:9733;}}}}}s:1:"t";a:1:{s:1:"r";a:1:{s:1:"i";a:1:{s:1:"a";a:1:{s:1:"n";a:1:{s:1:"g";a:1:{s:1:"l";a:1:{s:1:"e";a:2:{s:1:"d";a:1:{s:1:"o";a:1:{s:1:"w";a:1:{s:1:"n";a:1:{s:1:";";a:1:{s:9:"codepoint";i:9661;}}}}}s:1:"u";a:1:{s:1:"p";a:1:{s:1:";";a:1:{s:9:"codepoint";i:9651;}}}}}}}}}}}s:1:"u";a:1:{s:1:"p";a:1:{s:1:"l";a:1:{s:1:"u";a:1:{s:1:"s";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10756;}}}}}}s:1:"v";a:1:{s:1:"e";a:1:{s:1:"e";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8897;}}}}s:1:"w";a:1:{s:1:"e";a:1:{s:1:"d";a:1:{s:1:"g";a:1:{s:1:"e";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8896;}}}}}}}}s:1:"k";a:1:{s:1:"a";a:1:{s:1:"r";a:1:{s:1:"o";a:1:{s:1:"w";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10509;}}}}}}s:1:"l";a:3:{s:1:"a";a:2:{s:1:"c";a:1:{s:1:"k";a:3:{s:1:"l";a:1:{s:1:"o";a:1:{s:1:"z";a:1:{s:1:"e";a:1:{s:1:"n";a:1:{s:1:"g";a:1:{s:1:"e";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10731;}}}}}}}}s:1:"s";a:1:{s:1:"q";a:1:{s:1:"u";a:1:{s:1:"a";a:1:{s:1:"r";a:1:{s:1:"e";a:1:{s:1:";";a:1:{s:9:"codepoint";i:9642;}}}}}}}s:1:"t";a:1:{s:1:"r";a:1:{s:1:"i";a:1:{s:1:"a";a:1:{s:1:"n";a:1:{s:1:"g";a:1:{s:1:"l";a:1:{s:1:"e";a:4:{s:1:";";a:1:{s:9:"codepoint";i:9652;}s:1:"d";a:1:{s:1:"o";a:1:{s:1:"w";a:1:{s:1:"n";a:1:{s:1:";";a:1:{s:9:"codepoint";i:9662;}}}}}s:1:"l";a:1:{s:1:"e";a:1:{s:1:"f";a:1:{s:1:"t";a:1:{s:1:";";a:1:{s:9:"codepoint";i:9666;}}}}}s:1:"r";a:1:{s:1:"i";a:1:{s:1:"g";a:1:{s:1:"h";a:1:{s:1:"t";a:1:{s:1:";";a:1:{s:9:"codepoint";i:9656;}}}}}}}}}}}}}}}}s:1:"n";a:1:{s:1:"k";a:1:{s:1:";";a:1:{s:9:"codepoint";i:9251;}}}}s:1:"k";a:2:{i:1;a:2:{i:2;a:1:{s:1:";";a:1:{s:9:"codepoint";i:9618;}}i:4;a:1:{s:1:";";a:1:{s:9:"codepoint";i:9617;}}}i:3;a:1:{i:4;a:1:{s:1:";";a:1:{s:9:"codepoint";i:9619;}}}}s:1:"o";a:1:{s:1:"c";a:1:{s:1:"k";a:1:{s:1:";";a:1:{s:9:"codepoint";i:9608;}}}}}s:1:"n";a:1:{s:1:"o";a:1:{s:1:"t";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8976;}}}}s:1:"o";a:4:{s:1:"p";a:1:{s:1:"f";a:1:{s:1:";";a:1:{s:9:"codepoint";i:120147;}}}s:1:"t";a:2:{s:1:";";a:1:{s:9:"codepoint";i:8869;}s:1:"t";a:1:{s:1:"o";a:1:{s:1:"m";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8869;}}}}}s:1:"w";a:1:{s:1:"t";a:1:{s:1:"i";a:1:{s:1:"e";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8904;}}}}}s:1:"x";a:12:{s:1:"D";a:4:{s:1:"L";a:1:{s:1:";";a:1:{s:9:"codepoint";i:9559;}}s:1:"R";a:1:{s:1:";";a:1:{s:9:"codepoint";i:9556;}}s:1:"l";a:1:{s:1:";";a:1:{s:9:"codepoint";i:9558;}}s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:9555;}}}s:1:"H";a:5:{s:1:";";a:1:{s:9:"codepoint";i:9552;}s:1:"D";a:1:{s:1:";";a:1:{s:9:"codepoint";i:9574;}}s:1:"U";a:1:{s:1:";";a:1:{s:9:"codepoint";i:9577;}}s:1:"d";a:1:{s:1:";";a:1:{s:9:"codepoint";i:9572;}}s:1:"u";a:1:{s:1:";";a:1:{s:9:"codepoint";i:9575;}}}s:1:"U";a:4:{s:1:"L";a:1:{s:1:";";a:1:{s:9:"codepoint";i:9565;}}s:1:"R";a:1:{s:1:";";a:1:{s:9:"codepoint";i:9562;}}s:1:"l";a:1:{s:1:";";a:1:{s:9:"codepoint";i:9564;}}s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:9561;}}}s:1:"V";a:7:{s:1:";";a:1:{s:9:"codepoint";i:9553;}s:1:"H";a:1:{s:1:";";a:1:{s:9:"codepoint";i:9580;}}s:1:"L";a:1:{s:1:";";a:1:{s:9:"codepoint";i:9571;}}s:1:"R";a:1:{s:1:";";a:1:{s:9:"codepoint";i:9568;}}s:1:"h";a:1:{s:1:";";a:1:{s:9:"codepoint";i:9579;}}s:1:"l";a:1:{s:1:";";a:1:{s:9:"codepoint";i:9570;}}s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:9567;}}}s:1:"b";a:1:{s:1:"o";a:1:{s:1:"x";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10697;}}}}s:1:"d";a:4:{s:1:"L";a:1:{s:1:";";a:1:{s:9:"codepoint";i:9557;}}s:1:"R";a:1:{s:1:";";a:1:{s:9:"codepoint";i:9554;}}s:1:"l";a:1:{s:1:";";a:1:{s:9:"codepoint";i:9488;}}s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:9484;}}}s:1:"h";a:5:{s:1:";";a:1:{s:9:"codepoint";i:9472;}s:1:"D";a:1:{s:1:";";a:1:{s:9:"codepoint";i:9573;}}s:1:"U";a:1:{s:1:";";a:1:{s:9:"codepoint";i:9576;}}s:1:"d";a:1:{s:1:";";a:1:{s:9:"codepoint";i:9516;}}s:1:"u";a:1:{s:1:";";a:1:{s:9:"codepoint";i:9524;}}}s:1:"m";a:1:{s:1:"i";a:1:{s:1:"n";a:1:{s:1:"u";a:1:{s:1:"s";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8863;}}}}}}s:1:"p";a:1:{s:1:"l";a:1:{s:1:"u";a:1:{s:1:"s";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8862;}}}}}s:1:"t";a:1:{s:1:"i";a:1:{s:1:"m";a:1:{s:1:"e";a:1:{s:1:"s";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8864;}}}}}}s:1:"u";a:4:{s:1:"L";a:1:{s:1:";";a:1:{s:9:"codepoint";i:9563;}}s:1:"R";a:1:{s:1:";";a:1:{s:9:"codepoint";i:9560;}}s:1:"l";a:1:{s:1:";";a:1:{s:9:"codepoint";i:9496;}}s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:9492;}}}s:1:"v";a:7:{s:1:";";a:1:{s:9:"codepoint";i:9474;}s:1:"H";a:1:{s:1:";";a:1:{s:9:"codepoint";i:9578;}}s:1:"L";a:1:{s:1:";";a:1:{s:9:"codepoint";i:9569;}}s:1:"R";a:1:{s:1:";";a:1:{s:9:"codepoint";i:9566;}}s:1:"h";a:1:{s:1:";";a:1:{s:9:"codepoint";i:9532;}}s:1:"l";a:1:{s:1:";";a:1:{s:9:"codepoint";i:9508;}}s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:9500;}}}}}s:1:"p";a:1:{s:1:"r";a:1:{s:1:"i";a:1:{s:1:"m";a:1:{s:1:"e";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8245;}}}}}}s:1:"r";a:2:{s:1:"e";a:1:{s:1:"v";a:1:{s:1:"e";a:1:{s:1:";";a:1:{s:9:"codepoint";i:728;}}}}s:1:"v";a:1:{s:1:"b";a:1:{s:1:"a";a:1:{s:1:"r";a:2:{s:1:";";a:1:{s:9:"codepoint";i:166;}s:9:"codepoint";i:166;}}}}}s:1:"s";a:4:{s:1:"c";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:119991;}}}s:1:"e";a:1:{s:1:"m";a:1:{s:1:"i";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8271;}}}}s:1:"i";a:1:{s:1:"m";a:2:{s:1:";";a:1:{s:9:"codepoint";i:8765;}s:1:"e";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8909;}}}}s:1:"o";a:1:{s:1:"l";a:2:{s:1:";";a:1:{s:9:"codepoint";i:92;}s:1:"b";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10693;}}}}}s:1:"u";a:2:{s:1:"l";a:1:{s:1:"l";a:2:{s:1:";";a:1:{s:9:"codepoint";i:8226;}s:1:"e";a:1:{s:1:"t";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8226;}}}}}s:1:"m";a:1:{s:1:"p";a:3:{s:1:";";a:1:{s:9:"codepoint";i:8782;}s:1:"E";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10926;}}s:1:"e";a:2:{s:1:";";a:1:{s:9:"codepoint";i:8783;}s:1:"q";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8783;}}}}}}}s:1:"c";a:15:{s:1:"a";a:3:{s:1:"c";a:1:{s:1:"u";a:1:{s:1:"t";a:1:{s:1:"e";a:1:{s:1:";";a:1:{s:9:"codepoint";i:263;}}}}}s:1:"p";a:5:{s:1:";";a:1:{s:9:"codepoint";i:8745;}s:1:"a";a:1:{s:1:"n";a:1:{s:1:"d";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10820;}}}}s:1:"b";a:1:{s:1:"r";a:1:{s:1:"c";a:1:{s:1:"u";a:1:{s:1:"p";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10825;}}}}}}s:1:"c";a:2:{s:1:"a";a:1:{s:1:"p";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10827;}}}s:1:"u";a:1:{s:1:"p";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10823;}}}}s:1:"d";a:1:{s:1:"o";a:1:{s:1:"t";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10816;}}}}}s:1:"r";a:2:{s:1:"e";a:1:{s:1:"t";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8257;}}}s:1:"o";a:1:{s:1:"n";a:1:{s:1:";";a:1:{s:9:"codepoint";i:711;}}}}}s:1:"c";a:4:{s:1:"a";a:2:{s:1:"p";a:1:{s:1:"s";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10829;}}}s:1:"r";a:1:{s:1:"o";a:1:{s:1:"n";a:1:{s:1:";";a:1:{s:9:"codepoint";i:269;}}}}}s:1:"e";a:1:{s:1:"d";a:1:{s:1:"i";a:1:{s:1:"l";a:2:{s:1:";";a:1:{s:9:"codepoint";i:231;}s:9:"codepoint";i:231;}}}}s:1:"i";a:1:{s:1:"r";a:1:{s:1:"c";a:1:{s:1:";";a:1:{s:9:"codepoint";i:265;}}}}s:1:"u";a:1:{s:1:"p";a:1:{s:1:"s";a:2:{s:1:";";a:1:{s:9:"codepoint";i:10828;}s:1:"s";a:1:{s:1:"m";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10832;}}}}}}}s:1:"d";a:1:{s:1:"o";a:1:{s:1:"t";a:1:{s:1:";";a:1:{s:9:"codepoint";i:267;}}}}s:1:"e";a:3:{s:1:"d";a:1:{s:1:"i";a:1:{s:1:"l";a:2:{s:1:";";a:1:{s:9:"codepoint";i:184;}s:9:"codepoint";i:184;}}}s:1:"m";a:1:{s:1:"p";a:1:{s:1:"t";a:1:{s:1:"y";a:1:{s:1:"v";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10674;}}}}}}s:1:"n";a:1:{s:1:"t";a:3:{s:1:";";a:1:{s:9:"codepoint";i:162;}s:9:"codepoint";i:162;s:1:"e";a:1:{s:1:"r";a:1:{s:1:"d";a:1:{s:1:"o";a:1:{s:1:"t";a:1:{s:1:";";a:1:{s:9:"codepoint";i:183;}}}}}}}}}s:1:"f";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:120096;}}}s:1:"h";a:3:{s:1:"c";a:1:{s:1:"y";a:1:{s:1:";";a:1:{s:9:"codepoint";i:1095;}}}s:1:"e";a:1:{s:1:"c";a:1:{s:1:"k";a:2:{s:1:";";a:1:{s:9:"codepoint";i:10003;}s:1:"m";a:1:{s:1:"a";a:1:{s:1:"r";a:1:{s:1:"k";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10003;}}}}}}}}s:1:"i";a:1:{s:1:";";a:1:{s:9:"codepoint";i:967;}}}s:1:"i";a:1:{s:1:"r";a:7:{s:1:";";a:1:{s:9:"codepoint";i:9675;}s:1:"E";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10691;}}s:1:"c";a:3:{s:1:";";a:1:{s:9:"codepoint";i:710;}s:1:"e";a:1:{s:1:"q";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8791;}}}s:1:"l";a:1:{s:1:"e";a:2:{s:1:"a";a:1:{s:1:"r";a:1:{s:1:"r";a:1:{s:1:"o";a:1:{s:1:"w";a:2:{s:1:"l";a:1:{s:1:"e";a:1:{s:1:"f";a:1:{s:1:"t";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8634;}}}}}s:1:"r";a:1:{s:1:"i";a:1:{s:1:"g";a:1:{s:1:"h";a:1:{s:1:"t";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8635;}}}}}}}}}}}s:1:"d";a:5:{s:1:"R";a:1:{s:1:";";a:1:{s:9:"codepoint";i:174;}}s:1:"S";a:1:{s:1:";";a:1:{s:9:"codepoint";i:9416;}}s:1:"a";a:1:{s:1:"s";a:1:{s:1:"t";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8859;}}}}s:1:"c";a:1:{s:1:"i";a:1:{s:1:"r";a:1:{s:1:"c";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8858;}}}}}s:1:"d";a:1:{s:1:"a";a:1:{s:1:"s";a:1:{s:1:"h";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8861;}}}}}}}}}s:1:"e";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8791;}}s:1:"f";a:1:{s:1:"n";a:1:{s:1:"i";a:1:{s:1:"n";a:1:{s:1:"t";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10768;}}}}}}s:1:"m";a:1:{s:1:"i";a:1:{s:1:"d";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10991;}}}}s:1:"s";a:1:{s:1:"c";a:1:{s:1:"i";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10690;}}}}}}}s:1:"l";a:1:{s:1:"u";a:1:{s:1:"b";a:1:{s:1:"s";a:2:{s:1:";";a:1:{s:9:"codepoint";i:9827;}s:1:"u";a:1:{s:1:"i";a:1:{s:1:"t";a:1:{s:1:";";a:1:{s:9:"codepoint";i:9827;}}}}}}}}s:1:"o";a:4:{s:1:"l";a:1:{s:1:"o";a:1:{s:1:"n";a:2:{s:1:";";a:1:{s:9:"codepoint";i:58;}s:1:"e";a:2:{s:1:";";a:1:{s:9:"codepoint";i:8788;}s:1:"q";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8788;}}}}}}s:1:"m";a:2:{s:1:"m";a:1:{s:1:"a";a:2:{s:1:";";a:1:{s:9:"codepoint";i:44;}s:1:"t";a:1:{s:1:";";a:1:{s:9:"codepoint";i:64;}}}}s:1:"p";a:3:{s:1:";";a:1:{s:9:"codepoint";i:8705;}s:1:"f";a:1:{s:1:"n";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8728;}}}s:1:"l";a:1:{s:1:"e";a:2:{s:1:"m";a:1:{s:1:"e";a:1:{s:1:"n";a:1:{s:1:"t";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8705;}}}}}s:1:"x";a:1:{s:1:"e";a:1:{s:1:"s";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8450;}}}}}}}}s:1:"n";a:2:{s:1:"g";a:2:{s:1:";";a:1:{s:9:"codepoint";i:8773;}s:1:"d";a:1:{s:1:"o";a:1:{s:1:"t";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10861;}}}}}s:1:"i";a:1:{s:1:"n";a:1:{s:1:"t";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8750;}}}}}s:1:"p";a:3:{s:1:"f";a:1:{s:1:";";a:1:{s:9:"codepoint";i:120148;}}s:1:"r";a:1:{s:1:"o";a:1:{s:1:"d";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8720;}}}}s:1:"y";a:3:{s:1:";";a:1:{s:9:"codepoint";i:169;}s:9:"codepoint";i:169;s:1:"s";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8471;}}}}}}s:1:"r";a:2:{s:1:"a";a:1:{s:1:"r";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8629;}}}}s:1:"o";a:1:{s:1:"s";a:1:{s:1:"s";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10007;}}}}}s:1:"s";a:2:{s:1:"c";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:119992;}}}s:1:"u";a:2:{s:1:"b";a:2:{s:1:";";a:1:{s:9:"codepoint";i:10959;}s:1:"e";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10961;}}}s:1:"p";a:2:{s:1:";";a:1:{s:9:"codepoint";i:10960;}s:1:"e";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10962;}}}}}s:1:"t";a:1:{s:1:"d";a:1:{s:1:"o";a:1:{s:1:"t";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8943;}}}}}s:1:"u";a:7:{s:1:"d";a:1:{s:1:"a";a:1:{s:1:"r";a:1:{s:1:"r";a:2:{s:1:"l";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10552;}}s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10549;}}}}}}s:1:"e";a:2:{s:1:"p";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8926;}}}s:1:"s";a:1:{s:1:"c";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8927;}}}}s:1:"l";a:1:{s:1:"a";a:1:{s:1:"r";a:1:{s:1:"r";a:2:{s:1:";";a:1:{s:9:"codepoint";i:8630;}s:1:"p";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10557;}}}}}}s:1:"p";a:5:{s:1:";";a:1:{s:9:"codepoint";i:8746;}s:1:"b";a:1:{s:1:"r";a:1:{s:1:"c";a:1:{s:1:"a";a:1:{s:1:"p";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10824;}}}}}}s:1:"c";a:2:{s:1:"a";a:1:{s:1:"p";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10822;}}}s:1:"u";a:1:{s:1:"p";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10826;}}}}s:1:"d";a:1:{s:1:"o";a:1:{s:1:"t";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8845;}}}}s:1:"o";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10821;}}}}s:1:"r";a:4:{s:1:"a";a:1:{s:1:"r";a:1:{s:1:"r";a:2:{s:1:";";a:1:{s:9:"codepoint";i:8631;}s:1:"m";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10556;}}}}}s:1:"l";a:1:{s:1:"y";a:3:{s:1:"e";a:1:{s:1:"q";a:2:{s:1:"p";a:1:{s:1:"r";a:1:{s:1:"e";a:1:{s:1:"c";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8926;}}}}}s:1:"s";a:1:{s:1:"u";a:1:{s:1:"c";a:1:{s:1:"c";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8927;}}}}}}}s:1:"v";a:1:{s:1:"e";a:1:{s:1:"e";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8910;}}}}s:1:"w";a:1:{s:1:"e";a:1:{s:1:"d";a:1:{s:1:"g";a:1:{s:1:"e";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8911;}}}}}}}}s:1:"r";a:1:{s:1:"e";a:1:{s:1:"n";a:2:{s:1:";";a:1:{s:9:"codepoint";i:164;}s:9:"codepoint";i:164;}}}s:1:"v";a:1:{s:1:"e";a:1:{s:1:"a";a:1:{s:1:"r";a:1:{s:1:"r";a:1:{s:1:"o";a:1:{s:1:"w";a:2:{s:1:"l";a:1:{s:1:"e";a:1:{s:1:"f";a:1:{s:1:"t";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8630;}}}}}s:1:"r";a:1:{s:1:"i";a:1:{s:1:"g";a:1:{s:1:"h";a:1:{s:1:"t";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8631;}}}}}}}}}}}}}}s:1:"v";a:1:{s:1:"e";a:1:{s:1:"e";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8910;}}}}s:1:"w";a:1:{s:1:"e";a:1:{s:1:"d";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8911;}}}}}s:1:"w";a:2:{s:1:"c";a:1:{s:1:"o";a:1:{s:1:"n";a:1:{s:1:"i";a:1:{s:1:"n";a:1:{s:1:"t";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8754;}}}}}}}s:1:"i";a:1:{s:1:"n";a:1:{s:1:"t";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8753;}}}}}s:1:"y";a:1:{s:1:"l";a:1:{s:1:"c";a:1:{s:1:"t";a:1:{s:1:"y";a:1:{s:1:";";a:1:{s:9:"codepoint";i:9005;}}}}}}}s:1:"d";a:19:{s:1:"A";a:1:{s:1:"r";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8659;}}}}s:1:"H";a:1:{s:1:"a";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10597;}}}}s:1:"a";a:4:{s:1:"g";a:1:{s:1:"g";a:1:{s:1:"e";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8224;}}}}}s:1:"l";a:1:{s:1:"e";a:1:{s:1:"t";a:1:{s:1:"h";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8504;}}}}}s:1:"r";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8595;}}}s:1:"s";a:1:{s:1:"h";a:2:{s:1:";";a:1:{s:9:"codepoint";i:8208;}s:1:"v";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8867;}}}}}s:1:"b";a:2:{s:1:"k";a:1:{s:1:"a";a:1:{s:1:"r";a:1:{s:1:"o";a:1:{s:1:"w";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10511;}}}}}}s:1:"l";a:1:{s:1:"a";a:1:{s:1:"c";a:1:{s:1:";";a:1:{s:9:"codepoint";i:733;}}}}}s:1:"c";a:2:{s:1:"a";a:1:{s:1:"r";a:1:{s:1:"o";a:1:{s:1:"n";a:1:{s:1:";";a:1:{s:9:"codepoint";i:271;}}}}}s:1:"y";a:1:{s:1:";";a:1:{s:9:"codepoint";i:1076;}}}s:1:"d";a:3:{s:1:";";a:1:{s:9:"codepoint";i:8518;}s:1:"a";a:2:{s:1:"g";a:1:{s:1:"g";a:1:{s:1:"e";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8225;}}}}}s:1:"r";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8650;}}}}s:1:"o";a:1:{s:1:"t";a:1:{s:1:"s";a:1:{s:1:"e";a:1:{s:1:"q";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10871;}}}}}}}s:1:"e";a:3:{s:1:"g";a:2:{s:1:";";a:1:{s:9:"codepoint";i:176;}s:9:"codepoint";i:176;}s:1:"l";a:1:{s:1:"t";a:1:{s:1:"a";a:1:{s:1:";";a:1:{s:9:"codepoint";i:948;}}}}s:1:"m";a:1:{s:1:"p";a:1:{s:1:"t";a:1:{s:1:"y";a:1:{s:1:"v";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10673;}}}}}}}s:1:"f";a:2:{s:1:"i";a:1:{s:1:"s";a:1:{s:1:"h";a:1:{s:1:"t";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10623;}}}}}s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:120097;}}}s:1:"h";a:1:{s:1:"a";a:1:{s:1:"r";a:2:{s:1:"l";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8643;}}s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8642;}}}}}s:1:"i";a:5:{s:1:"a";a:1:{s:1:"m";a:3:{s:1:";";a:1:{s:9:"codepoint";i:8900;}s:1:"o";a:1:{s:1:"n";a:1:{s:1:"d";a:2:{s:1:";";a:1:{s:9:"codepoint";i:8900;}s:1:"s";a:1:{s:1:"u";a:1:{s:1:"i";a:1:{s:1:"t";a:1:{s:1:";";a:1:{s:9:"codepoint";i:9830;}}}}}}}}s:1:"s";a:1:{s:1:";";a:1:{s:9:"codepoint";i:9830;}}}}s:1:"e";a:1:{s:1:";";a:1:{s:9:"codepoint";i:168;}}s:1:"g";a:1:{s:1:"a";a:1:{s:1:"m";a:1:{s:1:"m";a:1:{s:1:"a";a:1:{s:1:";";a:1:{s:9:"codepoint";i:989;}}}}}}s:1:"s";a:1:{s:1:"i";a:1:{s:1:"n";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8946;}}}}s:1:"v";a:3:{s:1:";";a:1:{s:9:"codepoint";i:247;}s:1:"i";a:1:{s:1:"d";a:1:{s:1:"e";a:3:{s:1:";";a:1:{s:9:"codepoint";i:247;}s:9:"codepoint";i:247;s:1:"o";a:1:{s:1:"n";a:1:{s:1:"t";a:1:{s:1:"i";a:1:{s:1:"m";a:1:{s:1:"e";a:1:{s:1:"s";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8903;}}}}}}}}}}}s:1:"o";a:1:{s:1:"n";a:1:{s:1:"x";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8903;}}}}}}s:1:"j";a:1:{s:1:"c";a:1:{s:1:"y";a:1:{s:1:";";a:1:{s:9:"codepoint";i:1106;}}}}s:1:"l";a:1:{s:1:"c";a:2:{s:1:"o";a:1:{s:1:"r";a:1:{s:1:"n";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8990;}}}}s:1:"r";a:1:{s:1:"o";a:1:{s:1:"p";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8973;}}}}}}s:1:"o";a:5:{s:1:"l";a:1:{s:1:"l";a:1:{s:1:"a";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:36;}}}}}s:1:"p";a:1:{s:1:"f";a:1:{s:1:";";a:1:{s:9:"codepoint";i:120149;}}}s:1:"t";a:5:{s:1:";";a:1:{s:9:"codepoint";i:729;}s:1:"e";a:1:{s:1:"q";a:2:{s:1:";";a:1:{s:9:"codepoint";i:8784;}s:1:"d";a:1:{s:1:"o";a:1:{s:1:"t";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8785;}}}}}}s:1:"m";a:1:{s:1:"i";a:1:{s:1:"n";a:1:{s:1:"u";a:1:{s:1:"s";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8760;}}}}}}s:1:"p";a:1:{s:1:"l";a:1:{s:1:"u";a:1:{s:1:"s";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8724;}}}}}s:1:"s";a:1:{s:1:"q";a:1:{s:1:"u";a:1:{s:1:"a";a:1:{s:1:"r";a:1:{s:1:"e";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8865;}}}}}}}}s:1:"u";a:1:{s:1:"b";a:1:{s:1:"l";a:1:{s:1:"e";a:1:{s:1:"b";a:1:{s:1:"a";a:1:{s:1:"r";a:1:{s:1:"w";a:1:{s:1:"e";a:1:{s:1:"d";a:1:{s:1:"g";a:1:{s:1:"e";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8966;}}}}}}}}}}}}}s:1:"w";a:1:{s:1:"n";a:3:{s:1:"a";a:1:{s:1:"r";a:1:{s:1:"r";a:1:{s:1:"o";a:1:{s:1:"w";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8595;}}}}}}s:1:"d";a:1:{s:1:"o";a:1:{s:1:"w";a:1:{s:1:"n";a:1:{s:1:"a";a:1:{s:1:"r";a:1:{s:1:"r";a:1:{s:1:"o";a:1:{s:1:"w";a:1:{s:1:"s";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8650;}}}}}}}}}}}s:1:"h";a:1:{s:1:"a";a:1:{s:1:"r";a:1:{s:1:"p";a:1:{s:1:"o";a:1:{s:1:"o";a:1:{s:1:"n";a:2:{s:1:"l";a:1:{s:1:"e";a:1:{s:1:"f";a:1:{s:1:"t";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8643;}}}}}s:1:"r";a:1:{s:1:"i";a:1:{s:1:"g";a:1:{s:1:"h";a:1:{s:1:"t";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8642;}}}}}}}}}}}}}}}}s:1:"r";a:2:{s:1:"b";a:1:{s:1:"k";a:1:{s:1:"a";a:1:{s:1:"r";a:1:{s:1:"o";a:1:{s:1:"w";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10512;}}}}}}}s:1:"c";a:2:{s:1:"o";a:1:{s:1:"r";a:1:{s:1:"n";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8991;}}}}s:1:"r";a:1:{s:1:"o";a:1:{s:1:"p";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8972;}}}}}}s:1:"s";a:3:{s:1:"c";a:2:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:119993;}}s:1:"y";a:1:{s:1:";";a:1:{s:9:"codepoint";i:1109;}}}s:1:"o";a:1:{s:1:"l";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10742;}}}s:1:"t";a:1:{s:1:"r";a:1:{s:1:"o";a:1:{s:1:"k";a:1:{s:1:";";a:1:{s:9:"codepoint";i:273;}}}}}}s:1:"t";a:2:{s:1:"d";a:1:{s:1:"o";a:1:{s:1:"t";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8945;}}}}s:1:"r";a:1:{s:1:"i";a:2:{s:1:";";a:1:{s:9:"codepoint";i:9663;}s:1:"f";a:1:{s:1:";";a:1:{s:9:"codepoint";i:9662;}}}}}s:1:"u";a:2:{s:1:"a";a:1:{s:1:"r";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8693;}}}}s:1:"h";a:1:{s:1:"a";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10607;}}}}}s:1:"w";a:1:{s:1:"a";a:1:{s:1:"n";a:1:{s:1:"g";a:1:{s:1:"l";a:1:{s:1:"e";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10662;}}}}}}}s:1:"z";a:2:{s:1:"c";a:1:{s:1:"y";a:1:{s:1:";";a:1:{s:9:"codepoint";i:1119;}}}s:1:"i";a:1:{s:1:"g";a:1:{s:1:"r";a:1:{s:1:"a";a:1:{s:1:"r";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10239;}}}}}}}}}s:1:"e";a:18:{s:1:"D";a:2:{s:1:"D";a:1:{s:1:"o";a:1:{s:1:"t";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10871;}}}}s:1:"o";a:1:{s:1:"t";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8785;}}}}s:1:"a";a:2:{s:1:"c";a:1:{s:1:"u";a:1:{s:1:"t";a:1:{s:1:"e";a:2:{s:1:";";a:1:{s:9:"codepoint";i:233;}s:9:"codepoint";i:233;}}}}s:1:"s";a:1:{s:1:"t";a:1:{s:1:"e";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10862;}}}}}}s:1:"c";a:4:{s:1:"a";a:1:{s:1:"r";a:1:{s:1:"o";a:1:{s:1:"n";a:1:{s:1:";";a:1:{s:9:"codepoint";i:283;}}}}}s:1:"i";a:1:{s:1:"r";a:2:{s:1:";";a:1:{s:9:"codepoint";i:8790;}s:1:"c";a:2:{s:1:";";a:1:{s:9:"codepoint";i:234;}s:9:"codepoint";i:234;}}}s:1:"o";a:1:{s:1:"l";a:1:{s:1:"o";a:1:{s:1:"n";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8789;}}}}}s:1:"y";a:1:{s:1:";";a:1:{s:9:"codepoint";i:1101;}}}s:1:"d";a:1:{s:1:"o";a:1:{s:1:"t";a:1:{s:1:";";a:1:{s:9:"codepoint";i:279;}}}}s:1:"e";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8519;}}s:1:"f";a:2:{s:1:"D";a:1:{s:1:"o";a:1:{s:1:"t";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8786;}}}}s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:120098;}}}s:1:"g";a:3:{s:1:";";a:1:{s:9:"codepoint";i:10906;}s:1:"r";a:1:{s:1:"a";a:1:{s:1:"v";a:1:{s:1:"e";a:2:{s:1:";";a:1:{s:9:"codepoint";i:232;}s:9:"codepoint";i:232;}}}}s:1:"s";a:2:{s:1:";";a:1:{s:9:"codepoint";i:10902;}s:1:"d";a:1:{s:1:"o";a:1:{s:1:"t";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10904;}}}}}}s:1:"l";a:4:{s:1:";";a:1:{s:9:"codepoint";i:10905;}s:1:"i";a:1:{s:1:"n";a:1:{s:1:"t";a:1:{s:1:"e";a:1:{s:1:"r";a:1:{s:1:"s";a:1:{s:1:";";a:1:{s:9:"codepoint";i:9191;}}}}}}}s:1:"l";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8467;}}s:1:"s";a:2:{s:1:";";a:1:{s:9:"codepoint";i:10901;}s:1:"d";a:1:{s:1:"o";a:1:{s:1:"t";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10903;}}}}}}s:1:"m";a:3:{s:1:"a";a:1:{s:1:"c";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:275;}}}}s:1:"p";a:1:{s:1:"t";a:1:{s:1:"y";a:3:{s:1:";";a:1:{s:9:"codepoint";i:8709;}s:1:"s";a:1:{s:1:"e";a:1:{s:1:"t";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8709;}}}}s:1:"v";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8709;}}}}}s:1:"s";a:1:{s:1:"p";a:2:{i:1;a:2:{i:3;a:1:{s:1:";";a:1:{s:9:"codepoint";i:8196;}}i:4;a:1:{s:1:";";a:1:{s:9:"codepoint";i:8197;}}}s:1:";";a:1:{s:9:"codepoint";i:8195;}}}}s:1:"n";a:2:{s:1:"g";a:1:{s:1:";";a:1:{s:9:"codepoint";i:331;}}s:1:"s";a:1:{s:1:"p";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8194;}}}}s:1:"o";a:2:{s:1:"g";a:1:{s:1:"o";a:1:{s:1:"n";a:1:{s:1:";";a:1:{s:9:"codepoint";i:281;}}}}s:1:"p";a:1:{s:1:"f";a:1:{s:1:";";a:1:{s:9:"codepoint";i:120150;}}}}s:1:"p";a:3:{s:1:"a";a:1:{s:1:"r";a:2:{s:1:";";a:1:{s:9:"codepoint";i:8917;}s:1:"s";a:1:{s:1:"l";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10723;}}}}}s:1:"l";a:1:{s:1:"u";a:1:{s:1:"s";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10865;}}}}s:1:"s";a:1:{s:1:"i";a:3:{s:1:";";a:1:{s:9:"codepoint";i:1013;}s:1:"l";a:1:{s:1:"o";a:1:{s:1:"n";a:1:{s:1:";";a:1:{s:9:"codepoint";i:949;}}}}s:1:"v";a:1:{s:1:";";a:1:{s:9:"codepoint";i:949;}}}}}s:1:"q";a:4:{s:1:"c";a:2:{s:1:"i";a:1:{s:1:"r";a:1:{s:1:"c";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8790;}}}}s:1:"o";a:1:{s:1:"l";a:1:{s:1:"o";a:1:{s:1:"n";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8789;}}}}}}s:1:"s";a:2:{s:1:"i";a:1:{s:1:"m";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8770;}}}s:1:"l";a:1:{s:1:"a";a:1:{s:1:"n";a:1:{s:1:"t";a:2:{s:1:"g";a:1:{s:1:"t";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10902;}}}}s:1:"l";a:1:{s:1:"e";a:1:{s:1:"s";a:1:{s:1:"s";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10901;}}}}}}}}}}s:1:"u";a:3:{s:1:"a";a:1:{s:1:"l";a:1:{s:1:"s";a:1:{s:1:";";a:1:{s:9:"codepoint";i:61;}}}}s:1:"e";a:1:{s:1:"s";a:1:{s:1:"t";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8799;}}}}s:1:"i";a:1:{s:1:"v";a:2:{s:1:";";a:1:{s:9:"codepoint";i:8801;}s:1:"D";a:1:{s:1:"D";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10872;}}}}}}s:1:"v";a:1:{s:1:"p";a:1:{s:1:"a";a:1:{s:1:"r";a:1:{s:1:"s";a:1:{s:1:"l";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10725;}}}}}}}}s:1:"r";a:2:{s:1:"D";a:1:{s:1:"o";a:1:{s:1:"t";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8787;}}}}s:1:"a";a:1:{s:1:"r";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10609;}}}}}s:1:"s";a:3:{s:1:"c";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8495;}}}s:1:"d";a:1:{s:1:"o";a:1:{s:1:"t";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8784;}}}}s:1:"i";a:1:{s:1:"m";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8770;}}}}s:1:"t";a:2:{s:1:"a";a:1:{s:1:";";a:1:{s:9:"codepoint";i:951;}}s:1:"h";a:2:{s:1:";";a:1:{s:9:"codepoint";i:240;}s:9:"codepoint";i:240;}}s:1:"u";a:2:{s:1:"m";a:1:{s:1:"l";a:2:{s:1:";";a:1:{s:9:"codepoint";i:235;}s:9:"codepoint";i:235;}}s:1:"r";a:1:{s:1:"o";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8364;}}}}s:1:"x";a:3:{s:1:"c";a:1:{s:1:"l";a:1:{s:1:";";a:1:{s:9:"codepoint";i:33;}}}s:1:"i";a:1:{s:1:"s";a:1:{s:1:"t";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8707;}}}}s:1:"p";a:2:{s:1:"e";a:1:{s:1:"c";a:1:{s:1:"t";a:1:{s:1:"a";a:1:{s:1:"t";a:1:{s:1:"i";a:1:{s:1:"o";a:1:{s:1:"n";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8496;}}}}}}}}}s:1:"o";a:1:{s:1:"n";a:1:{s:1:"e";a:1:{s:1:"n";a:1:{s:1:"t";a:1:{s:1:"i";a:1:{s:1:"a";a:1:{s:1:"l";a:1:{s:1:"e";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8519;}}}}}}}}}}}}}s:1:"f";a:11:{s:1:"a";a:1:{s:1:"l";a:1:{s:1:"l";a:1:{s:1:"i";a:1:{s:1:"n";a:1:{s:1:"g";a:1:{s:1:"d";a:1:{s:1:"o";a:1:{s:1:"t";a:1:{s:1:"s";a:1:{s:1:"e";a:1:{s:1:"q";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8786;}}}}}}}}}}}}}s:1:"c";a:1:{s:1:"y";a:1:{s:1:";";a:1:{s:9:"codepoint";i:1092;}}}s:1:"e";a:1:{s:1:"m";a:1:{s:1:"a";a:1:{s:1:"l";a:1:{s:1:"e";a:1:{s:1:";";a:1:{s:9:"codepoint";i:9792;}}}}}}s:1:"f";a:3:{s:1:"i";a:1:{s:1:"l";a:1:{s:1:"i";a:1:{s:1:"g";a:1:{s:1:";";a:1:{s:9:"codepoint";i:64259;}}}}}s:1:"l";a:2:{s:1:"i";a:1:{s:1:"g";a:1:{s:1:";";a:1:{s:9:"codepoint";i:64256;}}}s:1:"l";a:1:{s:1:"i";a:1:{s:1:"g";a:1:{s:1:";";a:1:{s:9:"codepoint";i:64260;}}}}}s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:120099;}}}s:1:"i";a:1:{s:1:"l";a:1:{s:1:"i";a:1:{s:1:"g";a:1:{s:1:";";a:1:{s:9:"codepoint";i:64257;}}}}}s:1:"l";a:3:{s:1:"a";a:1:{s:1:"t";a:1:{s:1:";";a:1:{s:9:"codepoint";i:9837;}}}s:1:"l";a:1:{s:1:"i";a:1:{s:1:"g";a:1:{s:1:";";a:1:{s:9:"codepoint";i:64258;}}}}s:1:"t";a:1:{s:1:"n";a:1:{s:1:"s";a:1:{s:1:";";a:1:{s:9:"codepoint";i:9649;}}}}}s:1:"n";a:1:{s:1:"o";a:1:{s:1:"f";a:1:{s:1:";";a:1:{s:9:"codepoint";i:402;}}}}s:1:"o";a:2:{s:1:"p";a:1:{s:1:"f";a:1:{s:1:";";a:1:{s:9:"codepoint";i:120151;}}}s:1:"r";a:2:{s:1:"a";a:1:{s:1:"l";a:1:{s:1:"l";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8704;}}}}s:1:"k";a:2:{s:1:";";a:1:{s:9:"codepoint";i:8916;}s:1:"v";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10969;}}}}}s:1:"p";a:1:{s:1:"a";a:1:{s:1:"r";a:1:{s:1:"t";a:1:{s:1:"i";a:1:{s:1:"n";a:1:{s:1:"t";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10765;}}}}}}}}s:1:"r";a:2:{s:1:"a";a:2:{s:1:"c";a:6:{i:1;a:6:{i:2;a:2:{s:1:";";a:1:{s:9:"codepoint";i:189;}s:9:"codepoint";i:189;}i:3;a:1:{s:1:";";a:1:{s:9:"codepoint";i:8531;}}i:4;a:2:{s:1:";";a:1:{s:9:"codepoint";i:188;}s:9:"codepoint";i:188;}i:5;a:1:{s:1:";";a:1:{s:9:"codepoint";i:8533;}}i:6;a:1:{s:1:";";a:1:{s:9:"codepoint";i:8537;}}i:8;a:1:{s:1:";";a:1:{s:9:"codepoint";i:8539;}}}i:2;a:2:{i:3;a:1:{s:1:";";a:1:{s:9:"codepoint";i:8532;}}i:5;a:1:{s:1:";";a:1:{s:9:"codepoint";i:8534;}}}i:3;a:3:{i:4;a:2:{s:1:";";a:1:{s:9:"codepoint";i:190;}s:9:"codepoint";i:190;}i:5;a:1:{s:1:";";a:1:{s:9:"codepoint";i:8535;}}i:8;a:1:{s:1:";";a:1:{s:9:"codepoint";i:8540;}}}i:4;a:1:{i:5;a:1:{s:1:";";a:1:{s:9:"codepoint";i:8536;}}}i:5;a:2:{i:6;a:1:{s:1:";";a:1:{s:9:"codepoint";i:8538;}}i:8;a:1:{s:1:";";a:1:{s:9:"codepoint";i:8541;}}}i:7;a:1:{i:8;a:1:{s:1:";";a:1:{s:9:"codepoint";i:8542;}}}}s:1:"s";a:1:{s:1:"l";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8260;}}}}s:1:"o";a:1:{s:1:"w";a:1:{s:1:"n";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8994;}}}}}s:1:"s";a:1:{s:1:"c";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:119995;}}}}}s:1:"g";a:16:{s:1:"E";a:2:{s:1:";";a:1:{s:9:"codepoint";i:8807;}s:1:"l";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10892;}}}s:1:"a";a:3:{s:1:"c";a:1:{s:1:"u";a:1:{s:1:"t";a:1:{s:1:"e";a:1:{s:1:";";a:1:{s:9:"codepoint";i:501;}}}}}s:1:"m";a:1:{s:1:"m";a:1:{s:1:"a";a:2:{s:1:";";a:1:{s:9:"codepoint";i:947;}s:1:"d";a:1:{s:1:";";a:1:{s:9:"codepoint";i:989;}}}}}s:1:"p";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10886;}}}s:1:"b";a:1:{s:1:"r";a:1:{s:1:"e";a:1:{s:1:"v";a:1:{s:1:"e";a:1:{s:1:";";a:1:{s:9:"codepoint";i:287;}}}}}}s:1:"c";a:2:{s:1:"i";a:1:{s:1:"r";a:1:{s:1:"c";a:1:{s:1:";";a:1:{s:9:"codepoint";i:285;}}}}s:1:"y";a:1:{s:1:";";a:1:{s:9:"codepoint";i:1075;}}}s:1:"d";a:1:{s:1:"o";a:1:{s:1:"t";a:1:{s:1:";";a:1:{s:9:"codepoint";i:289;}}}}s:1:"e";a:4:{s:1:";";a:1:{s:9:"codepoint";i:8805;}s:1:"l";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8923;}}s:1:"q";a:3:{s:1:";";a:1:{s:9:"codepoint";i:8805;}s:1:"q";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8807;}}s:1:"s";a:1:{s:1:"l";a:1:{s:1:"a";a:1:{s:1:"n";a:1:{s:1:"t";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10878;}}}}}}}s:1:"s";a:4:{s:1:";";a:1:{s:9:"codepoint";i:10878;}s:1:"c";a:1:{s:1:"c";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10921;}}}s:1:"d";a:1:{s:1:"o";a:1:{s:1:"t";a:2:{s:1:";";a:1:{s:9:"codepoint";i:10880;}s:1:"o";a:2:{s:1:";";a:1:{s:9:"codepoint";i:10882;}s:1:"l";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10884;}}}}}}s:1:"l";a:1:{s:1:"e";a:1:{s:1:"s";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10900;}}}}}}s:1:"f";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:120100;}}}s:1:"g";a:2:{s:1:";";a:1:{s:9:"codepoint";i:8811;}s:1:"g";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8921;}}}s:1:"i";a:1:{s:1:"m";a:1:{s:1:"e";a:1:{s:1:"l";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8503;}}}}}s:1:"j";a:1:{s:1:"c";a:1:{s:1:"y";a:1:{s:1:";";a:1:{s:9:"codepoint";i:1107;}}}}s:1:"l";a:4:{s:1:";";a:1:{s:9:"codepoint";i:8823;}s:1:"E";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10898;}}s:1:"a";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10917;}}s:1:"j";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10916;}}}s:1:"n";a:4:{s:1:"E";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8809;}}s:1:"a";a:1:{s:1:"p";a:2:{s:1:";";a:1:{s:9:"codepoint";i:10890;}s:1:"p";a:1:{s:1:"r";a:1:{s:1:"o";a:1:{s:1:"x";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10890;}}}}}}}s:1:"e";a:2:{s:1:";";a:1:{s:9:"codepoint";i:10888;}s:1:"q";a:2:{s:1:";";a:1:{s:9:"codepoint";i:10888;}s:1:"q";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8809;}}}}s:1:"s";a:1:{s:1:"i";a:1:{s:1:"m";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8935;}}}}}s:1:"o";a:1:{s:1:"p";a:1:{s:1:"f";a:1:{s:1:";";a:1:{s:9:"codepoint";i:120152;}}}}s:1:"r";a:1:{s:1:"a";a:1:{s:1:"v";a:1:{s:1:"e";a:1:{s:1:";";a:1:{s:9:"codepoint";i:96;}}}}}s:1:"s";a:2:{s:1:"c";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8458;}}}s:1:"i";a:1:{s:1:"m";a:3:{s:1:";";a:1:{s:9:"codepoint";i:8819;}s:1:"e";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10894;}}s:1:"l";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10896;}}}}}s:1:"t";a:7:{s:1:";";a:1:{s:9:"codepoint";i:62;}s:9:"codepoint";i:62;s:1:"c";a:2:{s:1:"c";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10919;}}s:1:"i";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10874;}}}}s:1:"d";a:1:{s:1:"o";a:1:{s:1:"t";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8919;}}}}s:1:"l";a:1:{s:1:"P";a:1:{s:1:"a";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10645;}}}}}s:1:"q";a:1:{s:1:"u";a:1:{s:1:"e";a:1:{s:1:"s";a:1:{s:1:"t";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10876;}}}}}}s:1:"r";a:5:{s:1:"a";a:2:{s:1:"p";a:1:{s:1:"p";a:1:{s:1:"r";a:1:{s:1:"o";a:1:{s:1:"x";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10886;}}}}}}s:1:"r";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10616;}}}}s:1:"d";a:1:{s:1:"o";a:1:{s:1:"t";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8919;}}}}s:1:"e";a:1:{s:1:"q";a:2:{s:1:"l";a:1:{s:1:"e";a:1:{s:1:"s";a:1:{s:1:"s";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8923;}}}}}s:1:"q";a:1:{s:1:"l";a:1:{s:1:"e";a:1:{s:1:"s";a:1:{s:1:"s";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10892;}}}}}}}}s:1:"l";a:1:{s:1:"e";a:1:{s:1:"s";a:1:{s:1:"s";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8823;}}}}}s:1:"s";a:1:{s:1:"i";a:1:{s:1:"m";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8819;}}}}}}}s:1:"h";a:10:{s:1:"A";a:1:{s:1:"r";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8660;}}}}s:1:"a";a:4:{s:1:"i";a:1:{s:1:"r";a:1:{s:1:"s";a:1:{s:1:"p";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8202;}}}}}s:1:"l";a:1:{s:1:"f";a:1:{s:1:";";a:1:{s:9:"codepoint";i:189;}}}s:1:"m";a:1:{s:1:"i";a:1:{s:1:"l";a:1:{s:1:"t";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8459;}}}}}s:1:"r";a:2:{s:1:"d";a:1:{s:1:"c";a:1:{s:1:"y";a:1:{s:1:";";a:1:{s:9:"codepoint";i:1098;}}}}s:1:"r";a:3:{s:1:";";a:1:{s:9:"codepoint";i:8596;}s:1:"c";a:1:{s:1:"i";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10568;}}}}s:1:"w";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8621;}}}}}s:1:"b";a:1:{s:1:"a";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8463;}}}}s:1:"c";a:1:{s:1:"i";a:1:{s:1:"r";a:1:{s:1:"c";a:1:{s:1:";";a:1:{s:9:"codepoint";i:293;}}}}}s:1:"e";a:3:{s:1:"a";a:1:{s:1:"r";a:1:{s:1:"t";a:1:{s:1:"s";a:2:{s:1:";";a:1:{s:9:"codepoint";i:9829;}s:1:"u";a:1:{s:1:"i";a:1:{s:1:"t";a:1:{s:1:";";a:1:{s:9:"codepoint";i:9829;}}}}}}}}s:1:"l";a:1:{s:1:"l";a:1:{s:1:"i";a:1:{s:1:"p";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8230;}}}}}s:1:"r";a:1:{s:1:"c";a:1:{s:1:"o";a:1:{s:1:"n";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8889;}}}}}}s:1:"f";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:120101;}}}s:1:"k";a:1:{s:1:"s";a:2:{s:1:"e";a:1:{s:1:"a";a:1:{s:1:"r";a:1:{s:1:"o";a:1:{s:1:"w";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10533;}}}}}}s:1:"w";a:1:{s:1:"a";a:1:{s:1:"r";a:1:{s:1:"o";a:1:{s:1:"w";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10534;}}}}}}}}s:1:"o";a:5:{s:1:"a";a:1:{s:1:"r";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8703;}}}}s:1:"m";a:1:{s:1:"t";a:1:{s:1:"h";a:1:{s:1:"t";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8763;}}}}}s:1:"o";a:1:{s:1:"k";a:2:{s:1:"l";a:1:{s:1:"e";a:1:{s:1:"f";a:1:{s:1:"t";a:1:{s:1:"a";a:1:{s:1:"r";a:1:{s:1:"r";a:1:{s:1:"o";a:1:{s:1:"w";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8617;}}}}}}}}}}s:1:"r";a:1:{s:1:"i";a:1:{s:1:"g";a:1:{s:1:"h";a:1:{s:1:"t";a:1:{s:1:"a";a:1:{s:1:"r";a:1:{s:1:"r";a:1:{s:1:"o";a:1:{s:1:"w";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8618;}}}}}}}}}}}}}s:1:"p";a:1:{s:1:"f";a:1:{s:1:";";a:1:{s:9:"codepoint";i:120153;}}}s:1:"r";a:1:{s:1:"b";a:1:{s:1:"a";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8213;}}}}}}s:1:"s";a:3:{s:1:"c";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:119997;}}}s:1:"l";a:1:{s:1:"a";a:1:{s:1:"s";a:1:{s:1:"h";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8463;}}}}}s:1:"t";a:1:{s:1:"r";a:1:{s:1:"o";a:1:{s:1:"k";a:1:{s:1:";";a:1:{s:9:"codepoint";i:295;}}}}}}s:1:"y";a:2:{s:1:"b";a:1:{s:1:"u";a:1:{s:1:"l";a:1:{s:1:"l";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8259;}}}}}s:1:"p";a:1:{s:1:"h";a:1:{s:1:"e";a:1:{s:1:"n";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8208;}}}}}}}s:1:"i";a:15:{s:1:"a";a:1:{s:1:"c";a:1:{s:1:"u";a:1:{s:1:"t";a:1:{s:1:"e";a:2:{s:1:";";a:1:{s:9:"codepoint";i:237;}s:9:"codepoint";i:237;}}}}}s:1:"c";a:3:{s:1:";";a:1:{s:9:"codepoint";i:8291;}s:1:"i";a:1:{s:1:"r";a:1:{s:1:"c";a:2:{s:1:";";a:1:{s:9:"codepoint";i:238;}s:9:"codepoint";i:238;}}}s:1:"y";a:1:{s:1:";";a:1:{s:9:"codepoint";i:1080;}}}s:1:"e";a:2:{s:1:"c";a:1:{s:1:"y";a:1:{s:1:";";a:1:{s:9:"codepoint";i:1077;}}}s:1:"x";a:1:{s:1:"c";a:1:{s:1:"l";a:2:{s:1:";";a:1:{s:9:"codepoint";i:161;}s:9:"codepoint";i:161;}}}}s:1:"f";a:2:{s:1:"f";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8660;}}s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:120102;}}}s:1:"g";a:1:{s:1:"r";a:1:{s:1:"a";a:1:{s:1:"v";a:1:{s:1:"e";a:2:{s:1:";";a:1:{s:9:"codepoint";i:236;}s:9:"codepoint";i:236;}}}}}s:1:"i";a:4:{s:1:";";a:1:{s:9:"codepoint";i:8520;}s:1:"i";a:2:{s:1:"i";a:1:{s:1:"n";a:1:{s:1:"t";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10764;}}}}s:1:"n";a:1:{s:1:"t";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8749;}}}}s:1:"n";a:1:{s:1:"f";a:1:{s:1:"i";a:1:{s:1:"n";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10716;}}}}}s:1:"o";a:1:{s:1:"t";a:1:{s:1:"a";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8489;}}}}}s:1:"j";a:1:{s:1:"l";a:1:{s:1:"i";a:1:{s:1:"g";a:1:{s:1:";";a:1:{s:9:"codepoint";i:307;}}}}}s:1:"m";a:3:{s:1:"a";a:3:{s:1:"c";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:299;}}}s:1:"g";a:3:{s:1:"e";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8465;}}s:1:"l";a:1:{s:1:"i";a:1:{s:1:"n";a:1:{s:1:"e";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8464;}}}}}s:1:"p";a:1:{s:1:"a";a:1:{s:1:"r";a:1:{s:1:"t";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8465;}}}}}}s:1:"t";a:1:{s:1:"h";a:1:{s:1:";";a:1:{s:9:"codepoint";i:305;}}}}s:1:"o";a:1:{s:1:"f";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8887;}}}s:1:"p";a:1:{s:1:"e";a:1:{s:1:"d";a:1:{s:1:";";a:1:{s:9:"codepoint";i:437;}}}}}s:1:"n";a:5:{s:1:";";a:1:{s:9:"codepoint";i:8712;}s:1:"c";a:1:{s:1:"a";a:1:{s:1:"r";a:1:{s:1:"e";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8453;}}}}}s:1:"f";a:1:{s:1:"i";a:1:{s:1:"n";a:2:{s:1:";";a:1:{s:9:"codepoint";i:8734;}s:1:"t";a:1:{s:1:"i";a:1:{s:1:"e";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10717;}}}}}}}s:1:"o";a:1:{s:1:"d";a:1:{s:1:"o";a:1:{s:1:"t";a:1:{s:1:";";a:1:{s:9:"codepoint";i:305;}}}}}s:1:"t";a:5:{s:1:";";a:1:{s:9:"codepoint";i:8747;}s:1:"c";a:1:{s:1:"a";a:1:{s:1:"l";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8890;}}}}s:1:"e";a:2:{s:1:"g";a:1:{s:1:"e";a:1:{s:1:"r";a:1:{s:1:"s";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8484;}}}}}s:1:"r";a:1:{s:1:"c";a:1:{s:1:"a";a:1:{s:1:"l";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8890;}}}}}}s:1:"l";a:1:{s:1:"a";a:1:{s:1:"r";a:1:{s:1:"h";a:1:{s:1:"k";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10775;}}}}}}s:1:"p";a:1:{s:1:"r";a:1:{s:1:"o";a:1:{s:1:"d";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10812;}}}}}}}s:1:"o";a:4:{s:1:"c";a:1:{s:1:"y";a:1:{s:1:";";a:1:{s:9:"codepoint";i:1105;}}}s:1:"g";a:1:{s:1:"o";a:1:{s:1:"n";a:1:{s:1:";";a:1:{s:9:"codepoint";i:303;}}}}s:1:"p";a:1:{s:1:"f";a:1:{s:1:";";a:1:{s:9:"codepoint";i:120154;}}}s:1:"t";a:1:{s:1:"a";a:1:{s:1:";";a:1:{s:9:"codepoint";i:953;}}}}s:1:"p";a:1:{s:1:"r";a:1:{s:1:"o";a:1:{s:1:"d";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10812;}}}}}s:1:"q";a:1:{s:1:"u";a:1:{s:1:"e";a:1:{s:1:"s";a:1:{s:1:"t";a:2:{s:1:";";a:1:{s:9:"codepoint";i:191;}s:9:"codepoint";i:191;}}}}}s:1:"s";a:2:{s:1:"c";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:119998;}}}s:1:"i";a:1:{s:1:"n";a:5:{s:1:";";a:1:{s:9:"codepoint";i:8712;}s:1:"E";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8953;}}s:1:"d";a:1:{s:1:"o";a:1:{s:1:"t";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8949;}}}}s:1:"s";a:2:{s:1:";";a:1:{s:9:"codepoint";i:8948;}s:1:"v";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8947;}}}s:1:"v";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8712;}}}}}s:1:"t";a:2:{s:1:";";a:1:{s:9:"codepoint";i:8290;}s:1:"i";a:1:{s:1:"l";a:1:{s:1:"d";a:1:{s:1:"e";a:1:{s:1:";";a:1:{s:9:"codepoint";i:297;}}}}}}s:1:"u";a:2:{s:1:"k";a:1:{s:1:"c";a:1:{s:1:"y";a:1:{s:1:";";a:1:{s:9:"codepoint";i:1110;}}}}s:1:"m";a:1:{s:1:"l";a:2:{s:1:";";a:1:{s:9:"codepoint";i:239;}s:9:"codepoint";i:239;}}}}s:1:"j";a:6:{s:1:"c";a:2:{s:1:"i";a:1:{s:1:"r";a:1:{s:1:"c";a:1:{s:1:";";a:1:{s:9:"codepoint";i:309;}}}}s:1:"y";a:1:{s:1:";";a:1:{s:9:"codepoint";i:1081;}}}s:1:"f";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:120103;}}}s:1:"m";a:1:{s:1:"a";a:1:{s:1:"t";a:1:{s:1:"h";a:1:{s:1:";";a:1:{s:9:"codepoint";i:567;}}}}}s:1:"o";a:1:{s:1:"p";a:1:{s:1:"f";a:1:{s:1:";";a:1:{s:9:"codepoint";i:120155;}}}}s:1:"s";a:2:{s:1:"c";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:119999;}}}s:1:"e";a:1:{s:1:"r";a:1:{s:1:"c";a:1:{s:1:"y";a:1:{s:1:";";a:1:{s:9:"codepoint";i:1112;}}}}}}s:1:"u";a:1:{s:1:"k";a:1:{s:1:"c";a:1:{s:1:"y";a:1:{s:1:";";a:1:{s:9:"codepoint";i:1108;}}}}}}s:1:"k";a:8:{s:1:"a";a:1:{s:1:"p";a:1:{s:1:"p";a:1:{s:1:"a";a:2:{s:1:";";a:1:{s:9:"codepoint";i:954;}s:1:"v";a:1:{s:1:";";a:1:{s:9:"codepoint";i:1008;}}}}}}s:1:"c";a:2:{s:1:"e";a:1:{s:1:"d";a:1:{s:1:"i";a:1:{s:1:"l";a:1:{s:1:";";a:1:{s:9:"codepoint";i:311;}}}}}s:1:"y";a:1:{s:1:";";a:1:{s:9:"codepoint";i:1082;}}}s:1:"f";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:120104;}}}s:1:"g";a:1:{s:1:"r";a:1:{s:1:"e";a:1:{s:1:"e";a:1:{s:1:"n";a:1:{s:1:";";a:1:{s:9:"codepoint";i:312;}}}}}}s:1:"h";a:1:{s:1:"c";a:1:{s:1:"y";a:1:{s:1:";";a:1:{s:9:"codepoint";i:1093;}}}}s:1:"j";a:1:{s:1:"c";a:1:{s:1:"y";a:1:{s:1:";";a:1:{s:9:"codepoint";i:1116;}}}}s:1:"o";a:1:{s:1:"p";a:1:{s:1:"f";a:1:{s:1:";";a:1:{s:9:"codepoint";i:120156;}}}}s:1:"s";a:1:{s:1:"c";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:120000;}}}}}s:1:"l";a:22:{s:1:"A";a:3:{s:1:"a";a:1:{s:1:"r";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8666;}}}}s:1:"r";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8656;}}}s:1:"t";a:1:{s:1:"a";a:1:{s:1:"i";a:1:{s:1:"l";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10523;}}}}}}s:1:"B";a:1:{s:1:"a";a:1:{s:1:"r";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10510;}}}}}s:1:"E";a:2:{s:1:";";a:1:{s:9:"codepoint";i:8806;}s:1:"g";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10891;}}}s:1:"H";a:1:{s:1:"a";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10594;}}}}s:1:"a";a:9:{s:1:"c";a:1:{s:1:"u";a:1:{s:1:"t";a:1:{s:1:"e";a:1:{s:1:";";a:1:{s:9:"codepoint";i:314;}}}}}s:1:"e";a:1:{s:1:"m";a:1:{s:1:"p";a:1:{s:1:"t";a:1:{s:1:"y";a:1:{s:1:"v";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10676;}}}}}}}s:1:"g";a:1:{s:1:"r";a:1:{s:1:"a";a:1:{s:1:"n";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8466;}}}}}s:1:"m";a:1:{s:1:"b";a:1:{s:1:"d";a:1:{s:1:"a";a:1:{s:1:";";a:1:{s:9:"codepoint";i:955;}}}}}s:1:"n";a:1:{s:1:"g";a:3:{s:1:";";a:1:{s:9:"codepoint";i:10216;}s:1:"d";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10641;}}s:1:"l";a:1:{s:1:"e";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10216;}}}}}s:1:"p";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10885;}}s:1:"q";a:1:{s:1:"u";a:1:{s:1:"o";a:2:{s:1:";";a:1:{s:9:"codepoint";i:171;}s:9:"codepoint";i:171;}}}s:1:"r";a:1:{s:1:"r";a:8:{s:1:";";a:1:{s:9:"codepoint";i:8592;}s:1:"b";a:2:{s:1:";";a:1:{s:9:"codepoint";i:8676;}s:1:"f";a:1:{s:1:"s";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10527;}}}}s:1:"f";a:1:{s:1:"s";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10525;}}}s:1:"h";a:1:{s:1:"k";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8617;}}}s:1:"l";a:1:{s:1:"p";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8619;}}}s:1:"p";a:1:{s:1:"l";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10553;}}}s:1:"s";a:1:{s:1:"i";a:1:{s:1:"m";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10611;}}}}s:1:"t";a:1:{s:1:"l";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8610;}}}}}s:1:"t";a:3:{s:1:";";a:1:{s:9:"codepoint";i:10923;}s:1:"a";a:1:{s:1:"i";a:1:{s:1:"l";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10521;}}}}s:1:"e";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10925;}}}}s:1:"b";a:3:{s:1:"a";a:1:{s:1:"r";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10508;}}}}s:1:"b";a:1:{s:1:"r";a:1:{s:1:"k";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10098;}}}}s:1:"r";a:2:{s:1:"a";a:1:{s:1:"c";a:2:{s:1:"e";a:1:{s:1:";";a:1:{s:9:"codepoint";i:123;}}s:1:"k";a:1:{s:1:";";a:1:{s:9:"codepoint";i:91;}}}}s:1:"k";a:2:{s:1:"e";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10635;}}s:1:"s";a:1:{s:1:"l";a:2:{s:1:"d";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10639;}}s:1:"u";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10637;}}}}}}}s:1:"c";a:4:{s:1:"a";a:1:{s:1:"r";a:1:{s:1:"o";a:1:{s:1:"n";a:1:{s:1:";";a:1:{s:9:"codepoint";i:318;}}}}}s:1:"e";a:2:{s:1:"d";a:1:{s:1:"i";a:1:{s:1:"l";a:1:{s:1:";";a:1:{s:9:"codepoint";i:316;}}}}s:1:"i";a:1:{s:1:"l";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8968;}}}}s:1:"u";a:1:{s:1:"b";a:1:{s:1:";";a:1:{s:9:"codepoint";i:123;}}}s:1:"y";a:1:{s:1:";";a:1:{s:9:"codepoint";i:1083;}}}s:1:"d";a:4:{s:1:"c";a:1:{s:1:"a";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10550;}}}s:1:"q";a:1:{s:1:"u";a:1:{s:1:"o";a:2:{s:1:";";a:1:{s:9:"codepoint";i:8220;}s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8222;}}}}}s:1:"r";a:2:{s:1:"d";a:1:{s:1:"h";a:1:{s:1:"a";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10599;}}}}}s:1:"u";a:1:{s:1:"s";a:1:{s:1:"h";a:1:{s:1:"a";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10571;}}}}}}}s:1:"s";a:1:{s:1:"h";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8626;}}}}s:1:"e";a:5:{s:1:";";a:1:{s:9:"codepoint";i:8804;}s:1:"f";a:1:{s:1:"t";a:5:{s:1:"a";a:1:{s:1:"r";a:1:{s:1:"r";a:1:{s:1:"o";a:1:{s:1:"w";a:2:{s:1:";";a:1:{s:9:"codepoint";i:8592;}s:1:"t";a:1:{s:1:"a";a:1:{s:1:"i";a:1:{s:1:"l";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8610;}}}}}}}}}}s:1:"h";a:1:{s:1:"a";a:1:{s:1:"r";a:1:{s:1:"p";a:1:{s:1:"o";a:1:{s:1:"o";a:1:{s:1:"n";a:2:{s:1:"d";a:1:{s:1:"o";a:1:{s:1:"w";a:1:{s:1:"n";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8637;}}}}}s:1:"u";a:1:{s:1:"p";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8636;}}}}}}}}}}s:1:"l";a:1:{s:1:"e";a:1:{s:1:"f";a:1:{s:1:"t";a:1:{s:1:"a";a:1:{s:1:"r";a:1:{s:1:"r";a:1:{s:1:"o";a:1:{s:1:"w";a:1:{s:1:"s";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8647;}}}}}}}}}}}s:1:"r";a:1:{s:1:"i";a:1:{s:1:"g";a:1:{s:1:"h";a:1:{s:1:"t";a:3:{s:1:"a";a:1:{s:1:"r";a:1:{s:1:"r";a:1:{s:1:"o";a:1:{s:1:"w";a:2:{s:1:";";a:1:{s:9:"codepoint";i:8596;}s:1:"s";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8646;}}}}}}}s:1:"h";a:1:{s:1:"a";a:1:{s:1:"r";a:1:{s:1:"p";a:1:{s:1:"o";a:1:{s:1:"o";a:1:{s:1:"n";a:1:{s:1:"s";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8651;}}}}}}}}}s:1:"s";a:1:{s:1:"q";a:1:{s:1:"u";a:1:{s:1:"i";a:1:{s:1:"g";a:1:{s:1:"a";a:1:{s:1:"r";a:1:{s:1:"r";a:1:{s:1:"o";a:1:{s:1:"w";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8621;}}}}}}}}}}}}}}}}s:1:"t";a:1:{s:1:"h";a:1:{s:1:"r";a:1:{s:1:"e";a:1:{s:1:"e";a:1:{s:1:"t";a:1:{s:1:"i";a:1:{s:1:"m";a:1:{s:1:"e";a:1:{s:1:"s";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8907;}}}}}}}}}}}}}s:1:"g";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8922;}}s:1:"q";a:3:{s:1:";";a:1:{s:9:"codepoint";i:8804;}s:1:"q";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8806;}}s:1:"s";a:1:{s:1:"l";a:1:{s:1:"a";a:1:{s:1:"n";a:1:{s:1:"t";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10877;}}}}}}}s:1:"s";a:5:{s:1:";";a:1:{s:9:"codepoint";i:10877;}s:1:"c";a:1:{s:1:"c";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10920;}}}s:1:"d";a:1:{s:1:"o";a:1:{s:1:"t";a:2:{s:1:";";a:1:{s:9:"codepoint";i:10879;}s:1:"o";a:2:{s:1:";";a:1:{s:9:"codepoint";i:10881;}s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10883;}}}}}}s:1:"g";a:1:{s:1:"e";a:1:{s:1:"s";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10899;}}}}s:1:"s";a:5:{s:1:"a";a:1:{s:1:"p";a:1:{s:1:"p";a:1:{s:1:"r";a:1:{s:1:"o";a:1:{s:1:"x";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10885;}}}}}}}s:1:"d";a:1:{s:1:"o";a:1:{s:1:"t";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8918;}}}}s:1:"e";a:1:{s:1:"q";a:2:{s:1:"g";a:1:{s:1:"t";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8922;}}}}s:1:"q";a:1:{s:1:"g";a:1:{s:1:"t";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10891;}}}}}}}s:1:"g";a:1:{s:1:"t";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8822;}}}}s:1:"s";a:1:{s:1:"i";a:1:{s:1:"m";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8818;}}}}}}}s:1:"f";a:3:{s:1:"i";a:1:{s:1:"s";a:1:{s:1:"h";a:1:{s:1:"t";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10620;}}}}}s:1:"l";a:1:{s:1:"o";a:1:{s:1:"o";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8970;}}}}}s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:120105;}}}s:1:"g";a:2:{s:1:";";a:1:{s:9:"codepoint";i:8822;}s:1:"E";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10897;}}}s:1:"h";a:2:{s:1:"a";a:1:{s:1:"r";a:2:{s:1:"d";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8637;}}s:1:"u";a:2:{s:1:";";a:1:{s:9:"codepoint";i:8636;}s:1:"l";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10602;}}}}}s:1:"b";a:1:{s:1:"l";a:1:{s:1:"k";a:1:{s:1:";";a:1:{s:9:"codepoint";i:9604;}}}}}s:1:"j";a:1:{s:1:"c";a:1:{s:1:"y";a:1:{s:1:";";a:1:{s:9:"codepoint";i:1113;}}}}s:1:"l";a:5:{s:1:";";a:1:{s:9:"codepoint";i:8810;}s:1:"a";a:1:{s:1:"r";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8647;}}}}s:1:"c";a:1:{s:1:"o";a:1:{s:1:"r";a:1:{s:1:"n";a:1:{s:1:"e";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8990;}}}}}}}s:1:"h";a:1:{s:1:"a";a:1:{s:1:"r";a:1:{s:1:"d";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10603;}}}}}s:1:"t";a:1:{s:1:"r";a:1:{s:1:"i";a:1:{s:1:";";a:1:{s:9:"codepoint";i:9722;}}}}}s:1:"m";a:2:{s:1:"i";a:1:{s:1:"d";a:1:{s:1:"o";a:1:{s:1:"t";a:1:{s:1:";";a:1:{s:9:"codepoint";i:320;}}}}}s:1:"o";a:1:{s:1:"u";a:1:{s:1:"s";a:1:{s:1:"t";a:2:{s:1:";";a:1:{s:9:"codepoint";i:9136;}s:1:"a";a:1:{s:1:"c";a:1:{s:1:"h";a:1:{s:1:"e";a:1:{s:1:";";a:1:{s:9:"codepoint";i:9136;}}}}}}}}}}s:1:"n";a:4:{s:1:"E";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8808;}}s:1:"a";a:1:{s:1:"p";a:2:{s:1:";";a:1:{s:9:"codepoint";i:10889;}s:1:"p";a:1:{s:1:"r";a:1:{s:1:"o";a:1:{s:1:"x";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10889;}}}}}}}s:1:"e";a:2:{s:1:";";a:1:{s:9:"codepoint";i:10887;}s:1:"q";a:2:{s:1:";";a:1:{s:9:"codepoint";i:10887;}s:1:"q";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8808;}}}}s:1:"s";a:1:{s:1:"i";a:1:{s:1:"m";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8934;}}}}}s:1:"o";a:8:{s:1:"a";a:2:{s:1:"n";a:1:{s:1:"g";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10220;}}}s:1:"r";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8701;}}}}s:1:"b";a:1:{s:1:"r";a:1:{s:1:"k";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10214;}}}}s:1:"n";a:1:{s:1:"g";a:3:{s:1:"l";a:1:{s:1:"e";a:1:{s:1:"f";a:1:{s:1:"t";a:2:{s:1:"a";a:1:{s:1:"r";a:1:{s:1:"r";a:1:{s:1:"o";a:1:{s:1:"w";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10229;}}}}}}s:1:"r";a:1:{s:1:"i";a:1:{s:1:"g";a:1:{s:1:"h";a:1:{s:1:"t";a:1:{s:1:"a";a:1:{s:1:"r";a:1:{s:1:"r";a:1:{s:1:"o";a:1:{s:1:"w";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10231;}}}}}}}}}}}}}}}s:1:"m";a:1:{s:1:"a";a:1:{s:1:"p";a:1:{s:1:"s";a:1:{s:1:"t";a:1:{s:1:"o";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10236;}}}}}}}s:1:"r";a:1:{s:1:"i";a:1:{s:1:"g";a:1:{s:1:"h";a:1:{s:1:"t";a:1:{s:1:"a";a:1:{s:1:"r";a:1:{s:1:"r";a:1:{s:1:"o";a:1:{s:1:"w";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10230;}}}}}}}}}}}}}s:1:"o";a:1:{s:1:"p";a:1:{s:1:"a";a:1:{s:1:"r";a:1:{s:1:"r";a:1:{s:1:"o";a:1:{s:1:"w";a:2:{s:1:"l";a:1:{s:1:"e";a:1:{s:1:"f";a:1:{s:1:"t";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8619;}}}}}s:1:"r";a:1:{s:1:"i";a:1:{s:1:"g";a:1:{s:1:"h";a:1:{s:1:"t";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8620;}}}}}}}}}}}}}s:1:"p";a:3:{s:1:"a";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10629;}}}s:1:"f";a:1:{s:1:";";a:1:{s:9:"codepoint";i:120157;}}s:1:"l";a:1:{s:1:"u";a:1:{s:1:"s";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10797;}}}}}s:1:"t";a:1:{s:1:"i";a:1:{s:1:"m";a:1:{s:1:"e";a:1:{s:1:"s";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10804;}}}}}}s:1:"w";a:2:{s:1:"a";a:1:{s:1:"s";a:1:{s:1:"t";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8727;}}}}s:1:"b";a:1:{s:1:"a";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:95;}}}}}s:1:"z";a:3:{s:1:";";a:1:{s:9:"codepoint";i:9674;}s:1:"e";a:1:{s:1:"n";a:1:{s:1:"g";a:1:{s:1:"e";a:1:{s:1:";";a:1:{s:9:"codepoint";i:9674;}}}}}s:1:"f";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10731;}}}}s:1:"p";a:1:{s:1:"a";a:1:{s:1:"r";a:2:{s:1:";";a:1:{s:9:"codepoint";i:40;}s:1:"l";a:1:{s:1:"t";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10643;}}}}}}s:1:"r";a:5:{s:1:"a";a:1:{s:1:"r";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8646;}}}}s:1:"c";a:1:{s:1:"o";a:1:{s:1:"r";a:1:{s:1:"n";a:1:{s:1:"e";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8991;}}}}}}}s:1:"h";a:1:{s:1:"a";a:1:{s:1:"r";a:2:{s:1:";";a:1:{s:9:"codepoint";i:8651;}s:1:"d";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10605;}}}}}s:1:"m";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8206;}}s:1:"t";a:1:{s:1:"r";a:1:{s:1:"i";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8895;}}}}}s:1:"s";a:6:{s:1:"a";a:1:{s:1:"q";a:1:{s:1:"u";a:1:{s:1:"o";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8249;}}}}}s:1:"c";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:120001;}}}s:1:"h";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8624;}}s:1:"i";a:1:{s:1:"m";a:3:{s:1:";";a:1:{s:9:"codepoint";i:8818;}s:1:"e";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10893;}}s:1:"g";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10895;}}}}s:1:"q";a:2:{s:1:"b";a:1:{s:1:";";a:1:{s:9:"codepoint";i:91;}}s:1:"u";a:1:{s:1:"o";a:2:{s:1:";";a:1:{s:9:"codepoint";i:8216;}s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8218;}}}}}s:1:"t";a:1:{s:1:"r";a:1:{s:1:"o";a:1:{s:1:"k";a:1:{s:1:";";a:1:{s:9:"codepoint";i:322;}}}}}}s:1:"t";a:9:{s:1:";";a:1:{s:9:"codepoint";i:60;}s:9:"codepoint";i:60;s:1:"c";a:2:{s:1:"c";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10918;}}s:1:"i";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10873;}}}}s:1:"d";a:1:{s:1:"o";a:1:{s:1:"t";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8918;}}}}s:1:"h";a:1:{s:1:"r";a:1:{s:1:"e";a:1:{s:1:"e";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8907;}}}}}s:1:"i";a:1:{s:1:"m";a:1:{s:1:"e";a:1:{s:1:"s";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8905;}}}}}s:1:"l";a:1:{s:1:"a";a:1:{s:1:"r";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10614;}}}}}s:1:"q";a:1:{s:1:"u";a:1:{s:1:"e";a:1:{s:1:"s";a:1:{s:1:"t";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10875;}}}}}}s:1:"r";a:2:{s:1:"P";a:1:{s:1:"a";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10646;}}}}s:1:"i";a:3:{s:1:";";a:1:{s:9:"codepoint";i:9667;}s:1:"e";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8884;}}s:1:"f";a:1:{s:1:";";a:1:{s:9:"codepoint";i:9666;}}}}}s:1:"u";a:1:{s:1:"r";a:2:{s:1:"d";a:1:{s:1:"s";a:1:{s:1:"h";a:1:{s:1:"a";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10570;}}}}}}s:1:"u";a:1:{s:1:"h";a:1:{s:1:"a";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10598;}}}}}}}}s:1:"m";a:14:{s:1:"D";a:1:{s:1:"D";a:1:{s:1:"o";a:1:{s:1:"t";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8762;}}}}}s:1:"a";a:4:{s:1:"c";a:1:{s:1:"r";a:2:{s:1:";";a:1:{s:9:"codepoint";i:175;}s:9:"codepoint";i:175;}}s:1:"l";a:2:{s:1:"e";a:1:{s:1:";";a:1:{s:9:"codepoint";i:9794;}}s:1:"t";a:2:{s:1:";";a:1:{s:9:"codepoint";i:10016;}s:1:"e";a:1:{s:1:"s";a:1:{s:1:"e";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10016;}}}}}}s:1:"p";a:2:{s:1:";";a:1:{s:9:"codepoint";i:8614;}s:1:"s";a:1:{s:1:"t";a:1:{s:1:"o";a:4:{s:1:";";a:1:{s:9:"codepoint";i:8614;}s:1:"d";a:1:{s:1:"o";a:1:{s:1:"w";a:1:{s:1:"n";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8615;}}}}}s:1:"l";a:1:{s:1:"e";a:1:{s:1:"f";a:1:{s:1:"t";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8612;}}}}}s:1:"u";a:1:{s:1:"p";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8613;}}}}}}}s:1:"r";a:1:{s:1:"k";a:1:{s:1:"e";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:9646;}}}}}}s:1:"c";a:2:{s:1:"o";a:1:{s:1:"m";a:1:{s:1:"m";a:1:{s:1:"a";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10793;}}}}}s:1:"y";a:1:{s:1:";";a:1:{s:9:"codepoint";i:1084;}}}s:1:"d";a:1:{s:1:"a";a:1:{s:1:"s";a:1:{s:1:"h";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8212;}}}}}s:1:"e";a:1:{s:1:"a";a:1:{s:1:"s";a:1:{s:1:"u";a:1:{s:1:"r";a:1:{s:1:"e";a:1:{s:1:"d";a:1:{s:1:"a";a:1:{s:1:"n";a:1:{s:1:"g";a:1:{s:1:"l";a:1:{s:1:"e";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8737;}}}}}}}}}}}}}s:1:"f";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:120106;}}}s:1:"h";a:1:{s:1:"o";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8487;}}}s:1:"i";a:3:{s:1:"c";a:1:{s:1:"r";a:1:{s:1:"o";a:2:{s:1:";";a:1:{s:9:"codepoint";i:181;}s:9:"codepoint";i:181;}}}s:1:"d";a:4:{s:1:";";a:1:{s:9:"codepoint";i:8739;}s:1:"a";a:1:{s:1:"s";a:1:{s:1:"t";a:1:{s:1:";";a:1:{s:9:"codepoint";i:42;}}}}s:1:"c";a:1:{s:1:"i";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10992;}}}}s:1:"d";a:1:{s:1:"o";a:1:{s:1:"t";a:2:{s:1:";";a:1:{s:9:"codepoint";i:183;}s:9:"codepoint";i:183;}}}}s:1:"n";a:1:{s:1:"u";a:1:{s:1:"s";a:3:{s:1:";";a:1:{s:9:"codepoint";i:8722;}s:1:"b";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8863;}}s:1:"d";a:2:{s:1:";";a:1:{s:9:"codepoint";i:8760;}s:1:"u";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10794;}}}}}}}s:1:"l";a:2:{s:1:"c";a:1:{s:1:"p";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10971;}}}s:1:"d";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8230;}}}}s:1:"n";a:1:{s:1:"p";a:1:{s:1:"l";a:1:{s:1:"u";a:1:{s:1:"s";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8723;}}}}}}s:1:"o";a:2:{s:1:"d";a:1:{s:1:"e";a:1:{s:1:"l";a:1:{s:1:"s";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8871;}}}}}s:1:"p";a:1:{s:1:"f";a:1:{s:1:";";a:1:{s:9:"codepoint";i:120158;}}}}s:1:"p";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8723;}}s:1:"s";a:2:{s:1:"c";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:120002;}}}s:1:"t";a:1:{s:1:"p";a:1:{s:1:"o";a:1:{s:1:"s";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8766;}}}}}}s:1:"u";a:3:{s:1:";";a:1:{s:9:"codepoint";i:956;}s:1:"l";a:1:{s:1:"t";a:1:{s:1:"i";a:1:{s:1:"m";a:1:{s:1:"a";a:1:{s:1:"p";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8888;}}}}}}}s:1:"m";a:1:{s:1:"a";a:1:{s:1:"p";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8888;}}}}}}s:1:"n";a:23:{s:1:"L";a:1:{s:1:"e";a:1:{s:1:"f";a:1:{s:1:"t";a:2:{s:1:"a";a:1:{s:1:"r";a:1:{s:1:"r";a:1:{s:1:"o";a:1:{s:1:"w";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8653;}}}}}}s:1:"r";a:1:{s:1:"i";a:1:{s:1:"g";a:1:{s:1:"h";a:1:{s:1:"t";a:1:{s:1:"a";a:1:{s:1:"r";a:1:{s:1:"r";a:1:{s:1:"o";a:1:{s:1:"w";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8654;}}}}}}}}}}}}}}}s:1:"R";a:1:{s:1:"i";a:1:{s:1:"g";a:1:{s:1:"h";a:1:{s:1:"t";a:1:{s:1:"a";a:1:{s:1:"r";a:1:{s:1:"r";a:1:{s:1:"o";a:1:{s:1:"w";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8655;}}}}}}}}}}}s:1:"V";a:2:{s:1:"D";a:1:{s:1:"a";a:1:{s:1:"s";a:1:{s:1:"h";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8879;}}}}}s:1:"d";a:1:{s:1:"a";a:1:{s:1:"s";a:1:{s:1:"h";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8878;}}}}}}s:1:"a";a:4:{s:1:"b";a:1:{s:1:"l";a:1:{s:1:"a";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8711;}}}}s:1:"c";a:1:{s:1:"u";a:1:{s:1:"t";a:1:{s:1:"e";a:1:{s:1:";";a:1:{s:9:"codepoint";i:324;}}}}}s:1:"p";a:3:{s:1:";";a:1:{s:9:"codepoint";i:8777;}s:1:"o";a:1:{s:1:"s";a:1:{s:1:";";a:1:{s:9:"codepoint";i:329;}}}s:1:"p";a:1:{s:1:"r";a:1:{s:1:"o";a:1:{s:1:"x";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8777;}}}}}}s:1:"t";a:1:{s:1:"u";a:1:{s:1:"r";a:2:{s:1:";";a:1:{s:9:"codepoint";i:9838;}s:1:"a";a:1:{s:1:"l";a:2:{s:1:";";a:1:{s:9:"codepoint";i:9838;}s:1:"s";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8469;}}}}}}}}s:1:"b";a:1:{s:1:"s";a:1:{s:1:"p";a:2:{s:1:";";a:1:{s:9:"codepoint";i:160;}s:9:"codepoint";i:160;}}}s:1:"c";a:5:{s:1:"a";a:2:{s:1:"p";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10819;}}s:1:"r";a:1:{s:1:"o";a:1:{s:1:"n";a:1:{s:1:";";a:1:{s:9:"codepoint";i:328;}}}}}s:1:"e";a:1:{s:1:"d";a:1:{s:1:"i";a:1:{s:1:"l";a:1:{s:1:";";a:1:{s:9:"codepoint";i:326;}}}}}s:1:"o";a:1:{s:1:"n";a:1:{s:1:"g";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8775;}}}}s:1:"u";a:1:{s:1:"p";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10818;}}}s:1:"y";a:1:{s:1:";";a:1:{s:9:"codepoint";i:1085;}}}s:1:"d";a:1:{s:1:"a";a:1:{s:1:"s";a:1:{s:1:"h";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8211;}}}}}s:1:"e";a:6:{s:1:";";a:1:{s:9:"codepoint";i:8800;}s:1:"A";a:1:{s:1:"r";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8663;}}}}s:1:"a";a:1:{s:1:"r";a:2:{s:1:"h";a:1:{s:1:"k";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10532;}}}s:1:"r";a:2:{s:1:";";a:1:{s:9:"codepoint";i:8599;}s:1:"o";a:1:{s:1:"w";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8599;}}}}}}s:1:"q";a:1:{s:1:"u";a:1:{s:1:"i";a:1:{s:1:"v";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8802;}}}}}s:1:"s";a:1:{s:1:"e";a:1:{s:1:"a";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10536;}}}}}s:1:"x";a:1:{s:1:"i";a:1:{s:1:"s";a:1:{s:1:"t";a:2:{s:1:";";a:1:{s:9:"codepoint";i:8708;}s:1:"s";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8708;}}}}}}}s:1:"f";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:120107;}}}s:1:"g";a:3:{s:1:"e";a:2:{s:1:";";a:1:{s:9:"codepoint";i:8817;}s:1:"q";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8817;}}}s:1:"s";a:1:{s:1:"i";a:1:{s:1:"m";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8821;}}}}s:1:"t";a:2:{s:1:";";a:1:{s:9:"codepoint";i:8815;}s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8815;}}}}s:1:"h";a:3:{s:1:"A";a:1:{s:1:"r";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8654;}}}}s:1:"a";a:1:{s:1:"r";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8622;}}}}s:1:"p";a:1:{s:1:"a";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10994;}}}}}s:1:"i";a:3:{s:1:";";a:1:{s:9:"codepoint";i:8715;}s:1:"s";a:2:{s:1:";";a:1:{s:9:"codepoint";i:8956;}s:1:"d";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8954;}}}s:1:"v";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8715;}}}s:1:"j";a:1:{s:1:"c";a:1:{s:1:"y";a:1:{s:1:";";a:1:{s:9:"codepoint";i:1114;}}}}s:1:"l";a:6:{s:1:"A";a:1:{s:1:"r";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8653;}}}}s:1:"a";a:1:{s:1:"r";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8602;}}}}s:1:"d";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8229;}}}s:1:"e";a:4:{s:1:";";a:1:{s:9:"codepoint";i:8816;}s:1:"f";a:1:{s:1:"t";a:2:{s:1:"a";a:1:{s:1:"r";a:1:{s:1:"r";a:1:{s:1:"o";a:1:{s:1:"w";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8602;}}}}}}s:1:"r";a:1:{s:1:"i";a:1:{s:1:"g";a:1:{s:1:"h";a:1:{s:1:"t";a:1:{s:1:"a";a:1:{s:1:"r";a:1:{s:1:"r";a:1:{s:1:"o";a:1:{s:1:"w";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8622;}}}}}}}}}}}}}s:1:"q";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8816;}}s:1:"s";a:1:{s:1:"s";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8814;}}}}s:1:"s";a:1:{s:1:"i";a:1:{s:1:"m";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8820;}}}}s:1:"t";a:2:{s:1:";";a:1:{s:9:"codepoint";i:8814;}s:1:"r";a:1:{s:1:"i";a:2:{s:1:";";a:1:{s:9:"codepoint";i:8938;}s:1:"e";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8940;}}}}}}s:1:"m";a:1:{s:1:"i";a:1:{s:1:"d";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8740;}}}}s:1:"o";a:2:{s:1:"p";a:1:{s:1:"f";a:1:{s:1:";";a:1:{s:9:"codepoint";i:120159;}}}s:1:"t";a:4:{s:1:";";a:1:{s:9:"codepoint";i:172;}s:9:"codepoint";i:172;s:1:"i";a:1:{s:1:"n";a:2:{s:1:";";a:1:{s:9:"codepoint";i:8713;}s:1:"v";a:3:{s:1:"a";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8713;}}s:1:"b";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8951;}}s:1:"c";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8950;}}}}}s:1:"n";a:1:{s:1:"i";a:2:{s:1:";";a:1:{s:9:"codepoint";i:8716;}s:1:"v";a:3:{s:1:"a";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8716;}}s:1:"b";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8958;}}s:1:"c";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8957;}}}}}}}s:1:"p";a:3:{s:1:"a";a:1:{s:1:"r";a:2:{s:1:";";a:1:{s:9:"codepoint";i:8742;}s:1:"a";a:1:{s:1:"l";a:1:{s:1:"l";a:1:{s:1:"e";a:1:{s:1:"l";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8742;}}}}}}}}s:1:"o";a:1:{s:1:"l";a:1:{s:1:"i";a:1:{s:1:"n";a:1:{s:1:"t";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10772;}}}}}}s:1:"r";a:3:{s:1:";";a:1:{s:9:"codepoint";i:8832;}s:1:"c";a:1:{s:1:"u";a:1:{s:1:"e";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8928;}}}}s:1:"e";a:1:{s:1:"c";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8832;}}}}}s:1:"r";a:4:{s:1:"A";a:1:{s:1:"r";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8655;}}}}s:1:"a";a:1:{s:1:"r";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8603;}}}}s:1:"i";a:1:{s:1:"g";a:1:{s:1:"h";a:1:{s:1:"t";a:1:{s:1:"a";a:1:{s:1:"r";a:1:{s:1:"r";a:1:{s:1:"o";a:1:{s:1:"w";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8603;}}}}}}}}}}s:1:"t";a:1:{s:1:"r";a:1:{s:1:"i";a:2:{s:1:";";a:1:{s:9:"codepoint";i:8939;}s:1:"e";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8941;}}}}}}s:1:"s";a:7:{s:1:"c";a:3:{s:1:";";a:1:{s:9:"codepoint";i:8833;}s:1:"c";a:1:{s:1:"u";a:1:{s:1:"e";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8929;}}}}s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:120003;}}}s:1:"h";a:1:{s:1:"o";a:1:{s:1:"r";a:1:{s:1:"t";a:2:{s:1:"m";a:1:{s:1:"i";a:1:{s:1:"d";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8740;}}}}s:1:"p";a:1:{s:1:"a";a:1:{s:1:"r";a:1:{s:1:"a";a:1:{s:1:"l";a:1:{s:1:"l";a:1:{s:1:"e";a:1:{s:1:"l";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8742;}}}}}}}}}}}}}s:1:"i";a:1:{s:1:"m";a:2:{s:1:";";a:1:{s:9:"codepoint";i:8769;}s:1:"e";a:2:{s:1:";";a:1:{s:9:"codepoint";i:8772;}s:1:"q";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8772;}}}}}s:1:"m";a:1:{s:1:"i";a:1:{s:1:"d";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8740;}}}}s:1:"p";a:1:{s:1:"a";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8742;}}}}s:1:"q";a:1:{s:1:"s";a:1:{s:1:"u";a:2:{s:1:"b";a:1:{s:1:"e";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8930;}}}s:1:"p";a:1:{s:1:"e";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8931;}}}}}}s:1:"u";a:3:{s:1:"b";a:3:{s:1:";";a:1:{s:9:"codepoint";i:8836;}s:1:"e";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8840;}}s:1:"s";a:1:{s:1:"e";a:1:{s:1:"t";a:1:{s:1:"e";a:1:{s:1:"q";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8840;}}}}}}}s:1:"c";a:1:{s:1:"c";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8833;}}}s:1:"p";a:3:{s:1:";";a:1:{s:9:"codepoint";i:8837;}s:1:"e";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8841;}}s:1:"s";a:1:{s:1:"e";a:1:{s:1:"t";a:1:{s:1:"e";a:1:{s:1:"q";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8841;}}}}}}}}}s:1:"t";a:4:{s:1:"g";a:1:{s:1:"l";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8825;}}}s:1:"i";a:1:{s:1:"l";a:1:{s:1:"d";a:1:{s:1:"e";a:2:{s:1:";";a:1:{s:9:"codepoint";i:241;}s:9:"codepoint";i:241;}}}}s:1:"l";a:1:{s:1:"g";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8824;}}}s:1:"r";a:1:{s:1:"i";a:1:{s:1:"a";a:1:{s:1:"n";a:1:{s:1:"g";a:1:{s:1:"l";a:1:{s:1:"e";a:2:{s:1:"l";a:1:{s:1:"e";a:1:{s:1:"f";a:1:{s:1:"t";a:2:{s:1:";";a:1:{s:9:"codepoint";i:8938;}s:1:"e";a:1:{s:1:"q";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8940;}}}}}}}s:1:"r";a:1:{s:1:"i";a:1:{s:1:"g";a:1:{s:1:"h";a:1:{s:1:"t";a:2:{s:1:";";a:1:{s:9:"codepoint";i:8939;}s:1:"e";a:1:{s:1:"q";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8941;}}}}}}}}}}}}}}}}s:1:"u";a:2:{s:1:";";a:1:{s:9:"codepoint";i:957;}s:1:"m";a:3:{s:1:";";a:1:{s:9:"codepoint";i:35;}s:1:"e";a:1:{s:1:"r";a:1:{s:1:"o";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8470;}}}}s:1:"s";a:1:{s:1:"p";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8199;}}}}}s:1:"v";a:6:{s:1:"D";a:1:{s:1:"a";a:1:{s:1:"s";a:1:{s:1:"h";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8877;}}}}}s:1:"H";a:1:{s:1:"a";a:1:{s:1:"r";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10500;}}}}}s:1:"d";a:1:{s:1:"a";a:1:{s:1:"s";a:1:{s:1:"h";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8876;}}}}}s:1:"i";a:1:{s:1:"n";a:1:{s:1:"f";a:1:{s:1:"i";a:1:{s:1:"n";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10718;}}}}}}s:1:"l";a:1:{s:1:"A";a:1:{s:1:"r";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10498;}}}}}s:1:"r";a:1:{s:1:"A";a:1:{s:1:"r";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10499;}}}}}}s:1:"w";a:3:{s:1:"A";a:1:{s:1:"r";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8662;}}}}s:1:"a";a:1:{s:1:"r";a:2:{s:1:"h";a:1:{s:1:"k";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10531;}}}s:1:"r";a:2:{s:1:";";a:1:{s:9:"codepoint";i:8598;}s:1:"o";a:1:{s:1:"w";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8598;}}}}}}s:1:"n";a:1:{s:1:"e";a:1:{s:1:"a";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10535;}}}}}}}s:1:"o";a:18:{s:1:"S";a:1:{s:1:";";a:1:{s:9:"codepoint";i:9416;}}s:1:"a";a:2:{s:1:"c";a:1:{s:1:"u";a:1:{s:1:"t";a:1:{s:1:"e";a:2:{s:1:";";a:1:{s:9:"codepoint";i:243;}s:9:"codepoint";i:243;}}}}s:1:"s";a:1:{s:1:"t";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8859;}}}}s:1:"c";a:2:{s:1:"i";a:1:{s:1:"r";a:2:{s:1:";";a:1:{s:9:"codepoint";i:8858;}s:1:"c";a:2:{s:1:";";a:1:{s:9:"codepoint";i:244;}s:9:"codepoint";i:244;}}}s:1:"y";a:1:{s:1:";";a:1:{s:9:"codepoint";i:1086;}}}s:1:"d";a:5:{s:1:"a";a:1:{s:1:"s";a:1:{s:1:"h";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8861;}}}}s:1:"b";a:1:{s:1:"l";a:1:{s:1:"a";a:1:{s:1:"c";a:1:{s:1:";";a:1:{s:9:"codepoint";i:337;}}}}}s:1:"i";a:1:{s:1:"v";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10808;}}}s:1:"o";a:1:{s:1:"t";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8857;}}}s:1:"s";a:1:{s:1:"o";a:1:{s:1:"l";a:1:{s:1:"d";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10684;}}}}}}s:1:"e";a:1:{s:1:"l";a:1:{s:1:"i";a:1:{s:1:"g";a:1:{s:1:";";a:1:{s:9:"codepoint";i:339;}}}}}s:1:"f";a:2:{s:1:"c";a:1:{s:1:"i";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10687;}}}}s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:120108;}}}s:1:"g";a:3:{s:1:"o";a:1:{s:1:"n";a:1:{s:1:";";a:1:{s:9:"codepoint";i:731;}}}s:1:"r";a:1:{s:1:"a";a:1:{s:1:"v";a:1:{s:1:"e";a:2:{s:1:";";a:1:{s:9:"codepoint";i:242;}s:9:"codepoint";i:242;}}}}s:1:"t";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10689;}}}s:1:"h";a:2:{s:1:"b";a:1:{s:1:"a";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10677;}}}}s:1:"m";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8486;}}}s:1:"i";a:1:{s:1:"n";a:1:{s:1:"t";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8750;}}}}s:1:"l";a:4:{s:1:"a";a:1:{s:1:"r";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8634;}}}}s:1:"c";a:2:{s:1:"i";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10686;}}}s:1:"r";a:1:{s:1:"o";a:1:{s:1:"s";a:1:{s:1:"s";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10683;}}}}}}s:1:"i";a:1:{s:1:"n";a:1:{s:1:"e";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8254;}}}}s:1:"t";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10688;}}}s:1:"m";a:3:{s:1:"a";a:1:{s:1:"c";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:333;}}}}s:1:"e";a:1:{s:1:"g";a:1:{s:1:"a";a:1:{s:1:";";a:1:{s:9:"codepoint";i:969;}}}}s:1:"i";a:3:{s:1:"c";a:1:{s:1:"r";a:1:{s:1:"o";a:1:{s:1:"n";a:1:{s:1:";";a:1:{s:9:"codepoint";i:959;}}}}}s:1:"d";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10678;}}s:1:"n";a:1:{s:1:"u";a:1:{s:1:"s";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8854;}}}}}}s:1:"o";a:1:{s:1:"p";a:1:{s:1:"f";a:1:{s:1:";";a:1:{s:9:"codepoint";i:120160;}}}}s:1:"p";a:3:{s:1:"a";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10679;}}}s:1:"e";a:1:{s:1:"r";a:1:{s:1:"p";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10681;}}}}s:1:"l";a:1:{s:1:"u";a:1:{s:1:"s";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8853;}}}}}s:1:"r";a:7:{s:1:";";a:1:{s:9:"codepoint";i:8744;}s:1:"a";a:1:{s:1:"r";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8635;}}}}s:1:"d";a:4:{s:1:";";a:1:{s:9:"codepoint";i:10845;}s:1:"e";a:1:{s:1:"r";a:2:{s:1:";";a:1:{s:9:"codepoint";i:8500;}s:1:"o";a:1:{s:1:"f";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8500;}}}}}s:1:"f";a:2:{s:1:";";a:1:{s:9:"codepoint";i:170;}s:9:"codepoint";i:170;}s:1:"m";a:2:{s:1:";";a:1:{s:9:"codepoint";i:186;}s:9:"codepoint";i:186;}}s:1:"i";a:1:{s:1:"g";a:1:{s:1:"o";a:1:{s:1:"f";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8886;}}}}}s:1:"o";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10838;}}}s:1:"s";a:1:{s:1:"l";a:1:{s:1:"o";a:1:{s:1:"p";a:1:{s:1:"e";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10839;}}}}}}s:1:"v";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10843;}}}s:1:"s";a:3:{s:1:"c";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8500;}}}s:1:"l";a:1:{s:1:"a";a:1:{s:1:"s";a:1:{s:1:"h";a:2:{s:1:";";a:1:{s:9:"codepoint";i:248;}s:9:"codepoint";i:248;}}}}s:1:"o";a:1:{s:1:"l";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8856;}}}}s:1:"t";a:1:{s:1:"i";a:2:{s:1:"l";a:1:{s:1:"d";a:1:{s:1:"e";a:2:{s:1:";";a:1:{s:9:"codepoint";i:245;}s:9:"codepoint";i:245;}}}s:1:"m";a:1:{s:1:"e";a:1:{s:1:"s";a:2:{s:1:";";a:1:{s:9:"codepoint";i:8855;}s:1:"a";a:1:{s:1:"s";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10806;}}}}}}}}s:1:"u";a:1:{s:1:"m";a:1:{s:1:"l";a:2:{s:1:";";a:1:{s:9:"codepoint";i:246;}s:9:"codepoint";i:246;}}}s:1:"v";a:1:{s:1:"b";a:1:{s:1:"a";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:9021;}}}}}}s:1:"p";a:12:{s:1:"a";a:1:{s:1:"r";a:4:{s:1:";";a:1:{s:9:"codepoint";i:8741;}s:1:"a";a:3:{s:1:";";a:1:{s:9:"codepoint";i:182;}s:9:"codepoint";i:182;s:1:"l";a:1:{s:1:"l";a:1:{s:1:"e";a:1:{s:1:"l";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8741;}}}}}}s:1:"s";a:2:{s:1:"i";a:1:{s:1:"m";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10995;}}}s:1:"l";a:1:{s:1:";";a:1:{s:9:"codepoint";i:11005;}}}s:1:"t";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8706;}}}}s:1:"c";a:1:{s:1:"y";a:1:{s:1:";";a:1:{s:9:"codepoint";i:1087;}}}s:1:"e";a:1:{s:1:"r";a:5:{s:1:"c";a:1:{s:1:"n";a:1:{s:1:"t";a:1:{s:1:";";a:1:{s:9:"codepoint";i:37;}}}}s:1:"i";a:1:{s:1:"o";a:1:{s:1:"d";a:1:{s:1:";";a:1:{s:9:"codepoint";i:46;}}}}s:1:"m";a:1:{s:1:"i";a:1:{s:1:"l";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8240;}}}}s:1:"p";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8869;}}s:1:"t";a:1:{s:1:"e";a:1:{s:1:"n";a:1:{s:1:"k";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8241;}}}}}}}s:1:"f";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:120109;}}}s:1:"h";a:3:{s:1:"i";a:2:{s:1:";";a:1:{s:9:"codepoint";i:966;}s:1:"v";a:1:{s:1:";";a:1:{s:9:"codepoint";i:966;}}}s:1:"m";a:1:{s:1:"m";a:1:{s:1:"a";a:1:{s:1:"t";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8499;}}}}}s:1:"o";a:1:{s:1:"n";a:1:{s:1:"e";a:1:{s:1:";";a:1:{s:9:"codepoint";i:9742;}}}}}s:1:"i";a:3:{s:1:";";a:1:{s:9:"codepoint";i:960;}s:1:"t";a:1:{s:1:"c";a:1:{s:1:"h";a:1:{s:1:"f";a:1:{s:1:"o";a:1:{s:1:"r";a:1:{s:1:"k";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8916;}}}}}}}}s:1:"v";a:1:{s:1:";";a:1:{s:9:"codepoint";i:982;}}}s:1:"l";a:2:{s:1:"a";a:1:{s:1:"n";a:2:{s:1:"c";a:1:{s:1:"k";a:2:{s:1:";";a:1:{s:9:"codepoint";i:8463;}s:1:"h";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8462;}}}}s:1:"k";a:1:{s:1:"v";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8463;}}}}}s:1:"u";a:1:{s:1:"s";a:9:{s:1:";";a:1:{s:9:"codepoint";i:43;}s:1:"a";a:1:{s:1:"c";a:1:{s:1:"i";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10787;}}}}}s:1:"b";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8862;}}s:1:"c";a:1:{s:1:"i";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10786;}}}}s:1:"d";a:2:{s:1:"o";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8724;}}s:1:"u";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10789;}}}s:1:"e";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10866;}}s:1:"m";a:1:{s:1:"n";a:2:{s:1:";";a:1:{s:9:"codepoint";i:177;}s:9:"codepoint";i:177;}}s:1:"s";a:1:{s:1:"i";a:1:{s:1:"m";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10790;}}}}s:1:"t";a:1:{s:1:"w";a:1:{s:1:"o";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10791;}}}}}}}s:1:"m";a:1:{s:1:";";a:1:{s:9:"codepoint";i:177;}}s:1:"o";a:3:{s:1:"i";a:1:{s:1:"n";a:1:{s:1:"t";a:1:{s:1:"i";a:1:{s:1:"n";a:1:{s:1:"t";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10773;}}}}}}}s:1:"p";a:1:{s:1:"f";a:1:{s:1:";";a:1:{s:9:"codepoint";i:120161;}}}s:1:"u";a:1:{s:1:"n";a:1:{s:1:"d";a:2:{s:1:";";a:1:{s:9:"codepoint";i:163;}s:9:"codepoint";i:163;}}}}s:1:"r";a:10:{s:1:";";a:1:{s:9:"codepoint";i:8826;}s:1:"E";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10931;}}s:1:"a";a:1:{s:1:"p";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10935;}}}s:1:"c";a:1:{s:1:"u";a:1:{s:1:"e";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8828;}}}}s:1:"e";a:2:{s:1:";";a:1:{s:9:"codepoint";i:10927;}s:1:"c";a:6:{s:1:";";a:1:{s:9:"codepoint";i:8826;}s:1:"a";a:1:{s:1:"p";a:1:{s:1:"p";a:1:{s:1:"r";a:1:{s:1:"o";a:1:{s:1:"x";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10935;}}}}}}}s:1:"c";a:1:{s:1:"u";a:1:{s:1:"r";a:1:{s:1:"l";a:1:{s:1:"y";a:1:{s:1:"e";a:1:{s:1:"q";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8828;}}}}}}}}s:1:"e";a:1:{s:1:"q";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10927;}}}s:1:"n";a:3:{s:1:"a";a:1:{s:1:"p";a:1:{s:1:"p";a:1:{s:1:"r";a:1:{s:1:"o";a:1:{s:1:"x";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10937;}}}}}}}s:1:"e";a:1:{s:1:"q";a:1:{s:1:"q";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10933;}}}}s:1:"s";a:1:{s:1:"i";a:1:{s:1:"m";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8936;}}}}}s:1:"s";a:1:{s:1:"i";a:1:{s:1:"m";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8830;}}}}}}s:1:"i";a:1:{s:1:"m";a:1:{s:1:"e";a:2:{s:1:";";a:1:{s:9:"codepoint";i:8242;}s:1:"s";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8473;}}}}}s:1:"n";a:3:{s:1:"E";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10933;}}s:1:"a";a:1:{s:1:"p";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10937;}}}s:1:"s";a:1:{s:1:"i";a:1:{s:1:"m";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8936;}}}}}s:1:"o";a:3:{s:1:"d";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8719;}}s:1:"f";a:3:{s:1:"a";a:1:{s:1:"l";a:1:{s:1:"a";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:9006;}}}}}s:1:"l";a:1:{s:1:"i";a:1:{s:1:"n";a:1:{s:1:"e";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8978;}}}}}s:1:"s";a:1:{s:1:"u";a:1:{s:1:"r";a:1:{s:1:"f";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8979;}}}}}}s:1:"p";a:2:{s:1:";";a:1:{s:9:"codepoint";i:8733;}s:1:"t";a:1:{s:1:"o";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8733;}}}}}s:1:"s";a:1:{s:1:"i";a:1:{s:1:"m";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8830;}}}}s:1:"u";a:1:{s:1:"r";a:1:{s:1:"e";a:1:{s:1:"l";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8880;}}}}}}s:1:"s";a:2:{s:1:"c";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:120005;}}}s:1:"i";a:1:{s:1:";";a:1:{s:9:"codepoint";i:968;}}}s:1:"u";a:1:{s:1:"n";a:1:{s:1:"c";a:1:{s:1:"s";a:1:{s:1:"p";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8200;}}}}}}}s:1:"q";a:6:{s:1:"f";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:120110;}}}s:1:"i";a:1:{s:1:"n";a:1:{s:1:"t";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10764;}}}}s:1:"o";a:1:{s:1:"p";a:1:{s:1:"f";a:1:{s:1:";";a:1:{s:9:"codepoint";i:120162;}}}}s:1:"p";a:1:{s:1:"r";a:1:{s:1:"i";a:1:{s:1:"m";a:1:{s:1:"e";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8279;}}}}}}s:1:"s";a:1:{s:1:"c";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:120006;}}}}s:1:"u";a:3:{s:1:"a";a:1:{s:1:"t";a:2:{s:1:"e";a:1:{s:1:"r";a:1:{s:1:"n";a:1:{s:1:"i";a:1:{s:1:"o";a:1:{s:1:"n";a:1:{s:1:"s";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8461;}}}}}}}}s:1:"i";a:1:{s:1:"n";a:1:{s:1:"t";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10774;}}}}}}s:1:"e";a:1:{s:1:"s";a:1:{s:1:"t";a:2:{s:1:";";a:1:{s:9:"codepoint";i:63;}s:1:"e";a:1:{s:1:"q";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8799;}}}}}}s:1:"o";a:1:{s:1:"t";a:2:{s:1:";";a:1:{s:9:"codepoint";i:34;}s:9:"codepoint";i:34;}}}}s:1:"r";a:21:{s:1:"A";a:3:{s:1:"a";a:1:{s:1:"r";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8667;}}}}s:1:"r";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8658;}}}s:1:"t";a:1:{s:1:"a";a:1:{s:1:"i";a:1:{s:1:"l";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10524;}}}}}}s:1:"B";a:1:{s:1:"a";a:1:{s:1:"r";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10511;}}}}}s:1:"H";a:1:{s:1:"a";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10596;}}}}s:1:"a";a:7:{s:1:"c";a:2:{s:1:"e";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10714;}}s:1:"u";a:1:{s:1:"t";a:1:{s:1:"e";a:1:{s:1:";";a:1:{s:9:"codepoint";i:341;}}}}}s:1:"d";a:1:{s:1:"i";a:1:{s:1:"c";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8730;}}}}s:1:"e";a:1:{s:1:"m";a:1:{s:1:"p";a:1:{s:1:"t";a:1:{s:1:"y";a:1:{s:1:"v";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10675;}}}}}}}s:1:"n";a:1:{s:1:"g";a:4:{s:1:";";a:1:{s:9:"codepoint";i:10217;}s:1:"d";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10642;}}s:1:"e";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10661;}}s:1:"l";a:1:{s:1:"e";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10217;}}}}}s:1:"q";a:1:{s:1:"u";a:1:{s:1:"o";a:2:{s:1:";";a:1:{s:9:"codepoint";i:187;}s:9:"codepoint";i:187;}}}s:1:"r";a:1:{s:1:"r";a:11:{s:1:";";a:1:{s:9:"codepoint";i:8594;}s:1:"a";a:1:{s:1:"p";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10613;}}}s:1:"b";a:2:{s:1:";";a:1:{s:9:"codepoint";i:8677;}s:1:"f";a:1:{s:1:"s";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10528;}}}}s:1:"c";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10547;}}s:1:"f";a:1:{s:1:"s";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10526;}}}s:1:"h";a:1:{s:1:"k";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8618;}}}s:1:"l";a:1:{s:1:"p";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8620;}}}s:1:"p";a:1:{s:1:"l";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10565;}}}s:1:"s";a:1:{s:1:"i";a:1:{s:1:"m";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10612;}}}}s:1:"t";a:1:{s:1:"l";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8611;}}}s:1:"w";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8605;}}}}s:1:"t";a:2:{s:1:"a";a:1:{s:1:"i";a:1:{s:1:"l";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10522;}}}}s:1:"i";a:1:{s:1:"o";a:2:{s:1:";";a:1:{s:9:"codepoint";i:8758;}s:1:"n";a:1:{s:1:"a";a:1:{s:1:"l";a:1:{s:1:"s";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8474;}}}}}}}}}s:1:"b";a:3:{s:1:"a";a:1:{s:1:"r";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10509;}}}}s:1:"b";a:1:{s:1:"r";a:1:{s:1:"k";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10099;}}}}s:1:"r";a:2:{s:1:"a";a:1:{s:1:"c";a:2:{s:1:"e";a:1:{s:1:";";a:1:{s:9:"codepoint";i:125;}}s:1:"k";a:1:{s:1:";";a:1:{s:9:"codepoint";i:93;}}}}s:1:"k";a:2:{s:1:"e";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10636;}}s:1:"s";a:1:{s:1:"l";a:2:{s:1:"d";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10638;}}s:1:"u";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10640;}}}}}}}s:1:"c";a:4:{s:1:"a";a:1:{s:1:"r";a:1:{s:1:"o";a:1:{s:1:"n";a:1:{s:1:";";a:1:{s:9:"codepoint";i:345;}}}}}s:1:"e";a:2:{s:1:"d";a:1:{s:1:"i";a:1:{s:1:"l";a:1:{s:1:";";a:1:{s:9:"codepoint";i:343;}}}}s:1:"i";a:1:{s:1:"l";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8969;}}}}s:1:"u";a:1:{s:1:"b";a:1:{s:1:";";a:1:{s:9:"codepoint";i:125;}}}s:1:"y";a:1:{s:1:";";a:1:{s:9:"codepoint";i:1088;}}}s:1:"d";a:4:{s:1:"c";a:1:{s:1:"a";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10551;}}}s:1:"l";a:1:{s:1:"d";a:1:{s:1:"h";a:1:{s:1:"a";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10601;}}}}}}s:1:"q";a:1:{s:1:"u";a:1:{s:1:"o";a:2:{s:1:";";a:1:{s:9:"codepoint";i:8221;}s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8221;}}}}}s:1:"s";a:1:{s:1:"h";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8627;}}}}s:1:"e";a:3:{s:1:"a";a:1:{s:1:"l";a:4:{s:1:";";a:1:{s:9:"codepoint";i:8476;}s:1:"i";a:1:{s:1:"n";a:1:{s:1:"e";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8475;}}}}s:1:"p";a:1:{s:1:"a";a:1:{s:1:"r";a:1:{s:1:"t";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8476;}}}}}s:1:"s";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8477;}}}}s:1:"c";a:1:{s:1:"t";a:1:{s:1:";";a:1:{s:9:"codepoint";i:9645;}}}s:1:"g";a:2:{s:1:";";a:1:{s:9:"codepoint";i:174;}s:9:"codepoint";i:174;}}s:1:"f";a:3:{s:1:"i";a:1:{s:1:"s";a:1:{s:1:"h";a:1:{s:1:"t";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10621;}}}}}s:1:"l";a:1:{s:1:"o";a:1:{s:1:"o";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8971;}}}}}s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:120111;}}}s:1:"h";a:2:{s:1:"a";a:1:{s:1:"r";a:2:{s:1:"d";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8641;}}s:1:"u";a:2:{s:1:";";a:1:{s:9:"codepoint";i:8640;}s:1:"l";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10604;}}}}}s:1:"o";a:2:{s:1:";";a:1:{s:9:"codepoint";i:961;}s:1:"v";a:1:{s:1:";";a:1:{s:9:"codepoint";i:1009;}}}}s:1:"i";a:3:{s:1:"g";a:1:{s:1:"h";a:1:{s:1:"t";a:6:{s:1:"a";a:1:{s:1:"r";a:1:{s:1:"r";a:1:{s:1:"o";a:1:{s:1:"w";a:2:{s:1:";";a:1:{s:9:"codepoint";i:8594;}s:1:"t";a:1:{s:1:"a";a:1:{s:1:"i";a:1:{s:1:"l";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8611;}}}}}}}}}}s:1:"h";a:1:{s:1:"a";a:1:{s:1:"r";a:1:{s:1:"p";a:1:{s:1:"o";a:1:{s:1:"o";a:1:{s:1:"n";a:2:{s:1:"d";a:1:{s:1:"o";a:1:{s:1:"w";a:1:{s:1:"n";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8641;}}}}}s:1:"u";a:1:{s:1:"p";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8640;}}}}}}}}}}s:1:"l";a:1:{s:1:"e";a:1:{s:1:"f";a:1:{s:1:"t";a:2:{s:1:"a";a:1:{s:1:"r";a:1:{s:1:"r";a:1:{s:1:"o";a:1:{s:1:"w";a:1:{s:1:"s";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8644;}}}}}}}s:1:"h";a:1:{s:1:"a";a:1:{s:1:"r";a:1:{s:1:"p";a:1:{s:1:"o";a:1:{s:1:"o";a:1:{s:1:"n";a:1:{s:1:"s";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8652;}}}}}}}}}}}}}s:1:"r";a:1:{s:1:"i";a:1:{s:1:"g";a:1:{s:1:"h";a:1:{s:1:"t";a:1:{s:1:"a";a:1:{s:1:"r";a:1:{s:1:"r";a:1:{s:1:"o";a:1:{s:1:"w";a:1:{s:1:"s";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8649;}}}}}}}}}}}}s:1:"s";a:1:{s:1:"q";a:1:{s:1:"u";a:1:{s:1:"i";a:1:{s:1:"g";a:1:{s:1:"a";a:1:{s:1:"r";a:1:{s:1:"r";a:1:{s:1:"o";a:1:{s:1:"w";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8605;}}}}}}}}}}}s:1:"t";a:1:{s:1:"h";a:1:{s:1:"r";a:1:{s:1:"e";a:1:{s:1:"e";a:1:{s:1:"t";a:1:{s:1:"i";a:1:{s:1:"m";a:1:{s:1:"e";a:1:{s:1:"s";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8908;}}}}}}}}}}}}}}s:1:"n";a:1:{s:1:"g";a:1:{s:1:";";a:1:{s:9:"codepoint";i:730;}}}s:1:"s";a:1:{s:1:"i";a:1:{s:1:"n";a:1:{s:1:"g";a:1:{s:1:"d";a:1:{s:1:"o";a:1:{s:1:"t";a:1:{s:1:"s";a:1:{s:1:"e";a:1:{s:1:"q";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8787;}}}}}}}}}}}}s:1:"l";a:3:{s:1:"a";a:1:{s:1:"r";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8644;}}}}s:1:"h";a:1:{s:1:"a";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8652;}}}}s:1:"m";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8207;}}}s:1:"m";a:1:{s:1:"o";a:1:{s:1:"u";a:1:{s:1:"s";a:1:{s:1:"t";a:2:{s:1:";";a:1:{s:9:"codepoint";i:9137;}s:1:"a";a:1:{s:1:"c";a:1:{s:1:"h";a:1:{s:1:"e";a:1:{s:1:";";a:1:{s:9:"codepoint";i:9137;}}}}}}}}}}s:1:"n";a:1:{s:1:"m";a:1:{s:1:"i";a:1:{s:1:"d";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10990;}}}}}s:1:"o";a:4:{s:1:"a";a:2:{s:1:"n";a:1:{s:1:"g";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10221;}}}s:1:"r";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8702;}}}}s:1:"b";a:1:{s:1:"r";a:1:{s:1:"k";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10215;}}}}s:1:"p";a:3:{s:1:"a";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10630;}}}s:1:"f";a:1:{s:1:";";a:1:{s:9:"codepoint";i:120163;}}s:1:"l";a:1:{s:1:"u";a:1:{s:1:"s";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10798;}}}}}s:1:"t";a:1:{s:1:"i";a:1:{s:1:"m";a:1:{s:1:"e";a:1:{s:1:"s";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10805;}}}}}}}s:1:"p";a:2:{s:1:"a";a:1:{s:1:"r";a:2:{s:1:";";a:1:{s:9:"codepoint";i:41;}s:1:"g";a:1:{s:1:"t";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10644;}}}}}s:1:"p";a:1:{s:1:"o";a:1:{s:1:"l";a:1:{s:1:"i";a:1:{s:1:"n";a:1:{s:1:"t";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10770;}}}}}}}}s:1:"r";a:1:{s:1:"a";a:1:{s:1:"r";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8649;}}}}}s:1:"s";a:4:{s:1:"a";a:1:{s:1:"q";a:1:{s:1:"u";a:1:{s:1:"o";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8250;}}}}}s:1:"c";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:120007;}}}s:1:"h";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8625;}}s:1:"q";a:2:{s:1:"b";a:1:{s:1:";";a:1:{s:9:"codepoint";i:93;}}s:1:"u";a:1:{s:1:"o";a:2:{s:1:";";a:1:{s:9:"codepoint";i:8217;}s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8217;}}}}}}s:1:"t";a:3:{s:1:"h";a:1:{s:1:"r";a:1:{s:1:"e";a:1:{s:1:"e";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8908;}}}}}s:1:"i";a:1:{s:1:"m";a:1:{s:1:"e";a:1:{s:1:"s";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8906;}}}}}s:1:"r";a:1:{s:1:"i";a:4:{s:1:";";a:1:{s:9:"codepoint";i:9657;}s:1:"e";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8885;}}s:1:"f";a:1:{s:1:";";a:1:{s:9:"codepoint";i:9656;}}s:1:"l";a:1:{s:1:"t";a:1:{s:1:"r";a:1:{s:1:"i";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10702;}}}}}}}}s:1:"u";a:1:{s:1:"l";a:1:{s:1:"u";a:1:{s:1:"h";a:1:{s:1:"a";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10600;}}}}}}}s:1:"x";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8478;}}}s:1:"s";a:19:{s:1:"a";a:1:{s:1:"c";a:1:{s:1:"u";a:1:{s:1:"t";a:1:{s:1:"e";a:1:{s:1:";";a:1:{s:9:"codepoint";i:347;}}}}}}s:1:"b";a:1:{s:1:"q";a:1:{s:1:"u";a:1:{s:1:"o";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8218;}}}}}s:1:"c";a:10:{s:1:";";a:1:{s:9:"codepoint";i:8827;}s:1:"E";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10932;}}s:1:"a";a:2:{s:1:"p";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10936;}}s:1:"r";a:1:{s:1:"o";a:1:{s:1:"n";a:1:{s:1:";";a:1:{s:9:"codepoint";i:353;}}}}}s:1:"c";a:1:{s:1:"u";a:1:{s:1:"e";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8829;}}}}s:1:"e";a:2:{s:1:";";a:1:{s:9:"codepoint";i:10928;}s:1:"d";a:1:{s:1:"i";a:1:{s:1:"l";a:1:{s:1:";";a:1:{s:9:"codepoint";i:351;}}}}}s:1:"i";a:1:{s:1:"r";a:1:{s:1:"c";a:1:{s:1:";";a:1:{s:9:"codepoint";i:349;}}}}s:1:"n";a:3:{s:1:"E";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10934;}}s:1:"a";a:1:{s:1:"p";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10938;}}}s:1:"s";a:1:{s:1:"i";a:1:{s:1:"m";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8937;}}}}}s:1:"p";a:1:{s:1:"o";a:1:{s:1:"l";a:1:{s:1:"i";a:1:{s:1:"n";a:1:{s:1:"t";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10771;}}}}}}}s:1:"s";a:1:{s:1:"i";a:1:{s:1:"m";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8831;}}}}s:1:"y";a:1:{s:1:";";a:1:{s:9:"codepoint";i:1089;}}}s:1:"d";a:1:{s:1:"o";a:1:{s:1:"t";a:3:{s:1:";";a:1:{s:9:"codepoint";i:8901;}s:1:"b";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8865;}}s:1:"e";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10854;}}}}}s:1:"e";a:7:{s:1:"A";a:1:{s:1:"r";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8664;}}}}s:1:"a";a:1:{s:1:"r";a:2:{s:1:"h";a:1:{s:1:"k";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10533;}}}s:1:"r";a:2:{s:1:";";a:1:{s:9:"codepoint";i:8600;}s:1:"o";a:1:{s:1:"w";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8600;}}}}}}s:1:"c";a:1:{s:1:"t";a:2:{s:1:";";a:1:{s:9:"codepoint";i:167;}s:9:"codepoint";i:167;}}s:1:"m";a:1:{s:1:"i";a:1:{s:1:";";a:1:{s:9:"codepoint";i:59;}}}s:1:"s";a:1:{s:1:"w";a:1:{s:1:"a";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10537;}}}}}s:1:"t";a:1:{s:1:"m";a:2:{s:1:"i";a:1:{s:1:"n";a:1:{s:1:"u";a:1:{s:1:"s";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8726;}}}}}s:1:"n";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8726;}}}}s:1:"x";a:1:{s:1:"t";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10038;}}}}s:1:"f";a:1:{s:1:"r";a:2:{s:1:";";a:1:{s:9:"codepoint";i:120112;}s:1:"o";a:1:{s:1:"w";a:1:{s:1:"n";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8994;}}}}}}s:1:"h";a:4:{s:1:"a";a:1:{s:1:"r";a:1:{s:1:"p";a:1:{s:1:";";a:1:{s:9:"codepoint";i:9839;}}}}s:1:"c";a:2:{s:1:"h";a:1:{s:1:"c";a:1:{s:1:"y";a:1:{s:1:";";a:1:{s:9:"codepoint";i:1097;}}}}s:1:"y";a:1:{s:1:";";a:1:{s:9:"codepoint";i:1096;}}}s:1:"o";a:1:{s:1:"r";a:1:{s:1:"t";a:2:{s:1:"m";a:1:{s:1:"i";a:1:{s:1:"d";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8739;}}}}s:1:"p";a:1:{s:1:"a";a:1:{s:1:"r";a:1:{s:1:"a";a:1:{s:1:"l";a:1:{s:1:"l";a:1:{s:1:"e";a:1:{s:1:"l";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8741;}}}}}}}}}}}}s:1:"y";a:2:{s:1:";";a:1:{s:9:"codepoint";i:173;}s:9:"codepoint";i:173;}}s:1:"i";a:2:{s:1:"g";a:1:{s:1:"m";a:1:{s:1:"a";a:3:{s:1:";";a:1:{s:9:"codepoint";i:963;}s:1:"f";a:1:{s:1:";";a:1:{s:9:"codepoint";i:962;}}s:1:"v";a:1:{s:1:";";a:1:{s:9:"codepoint";i:962;}}}}}s:1:"m";a:8:{s:1:";";a:1:{s:9:"codepoint";i:8764;}s:1:"d";a:1:{s:1:"o";a:1:{s:1:"t";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10858;}}}}s:1:"e";a:2:{s:1:";";a:1:{s:9:"codepoint";i:8771;}s:1:"q";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8771;}}}s:1:"g";a:2:{s:1:";";a:1:{s:9:"codepoint";i:10910;}s:1:"E";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10912;}}}s:1:"l";a:2:{s:1:";";a:1:{s:9:"codepoint";i:10909;}s:1:"E";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10911;}}}s:1:"n";a:1:{s:1:"e";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8774;}}}s:1:"p";a:1:{s:1:"l";a:1:{s:1:"u";a:1:{s:1:"s";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10788;}}}}}s:1:"r";a:1:{s:1:"a";a:1:{s:1:"r";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10610;}}}}}}}s:1:"l";a:1:{s:1:"a";a:1:{s:1:"r";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8592;}}}}}s:1:"m";a:4:{s:1:"a";a:2:{s:1:"l";a:1:{s:1:"l";a:1:{s:1:"s";a:1:{s:1:"e";a:1:{s:1:"t";a:1:{s:1:"m";a:1:{s:1:"i";a:1:{s:1:"n";a:1:{s:1:"u";a:1:{s:1:"s";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8726;}}}}}}}}}}}s:1:"s";a:1:{s:1:"h";a:1:{s:1:"p";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10803;}}}}}s:1:"e";a:1:{s:1:"p";a:1:{s:1:"a";a:1:{s:1:"r";a:1:{s:1:"s";a:1:{s:1:"l";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10724;}}}}}}}s:1:"i";a:2:{s:1:"d";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8739;}}s:1:"l";a:1:{s:1:"e";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8995;}}}}s:1:"t";a:2:{s:1:";";a:1:{s:9:"codepoint";i:10922;}s:1:"e";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10924;}}}}s:1:"o";a:3:{s:1:"f";a:1:{s:1:"t";a:1:{s:1:"c";a:1:{s:1:"y";a:1:{s:1:";";a:1:{s:9:"codepoint";i:1100;}}}}}s:1:"l";a:2:{s:1:";";a:1:{s:9:"codepoint";i:47;}s:1:"b";a:2:{s:1:";";a:1:{s:9:"codepoint";i:10692;}s:1:"a";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:9023;}}}}}s:1:"p";a:1:{s:1:"f";a:1:{s:1:";";a:1:{s:9:"codepoint";i:120164;}}}}s:1:"p";a:1:{s:1:"a";a:2:{s:1:"d";a:1:{s:1:"e";a:1:{s:1:"s";a:2:{s:1:";";a:1:{s:9:"codepoint";i:9824;}s:1:"u";a:1:{s:1:"i";a:1:{s:1:"t";a:1:{s:1:";";a:1:{s:9:"codepoint";i:9824;}}}}}}}s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8741;}}}}s:1:"q";a:3:{s:1:"c";a:2:{s:1:"a";a:1:{s:1:"p";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8851;}}}s:1:"u";a:1:{s:1:"p";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8852;}}}}s:1:"s";a:1:{s:1:"u";a:2:{s:1:"b";a:3:{s:1:";";a:1:{s:9:"codepoint";i:8847;}s:1:"e";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8849;}}s:1:"s";a:1:{s:1:"e";a:1:{s:1:"t";a:2:{s:1:";";a:1:{s:9:"codepoint";i:8847;}s:1:"e";a:1:{s:1:"q";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8849;}}}}}}}s:1:"p";a:3:{s:1:";";a:1:{s:9:"codepoint";i:8848;}s:1:"e";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8850;}}s:1:"s";a:1:{s:1:"e";a:1:{s:1:"t";a:2:{s:1:";";a:1:{s:9:"codepoint";i:8848;}s:1:"e";a:1:{s:1:"q";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8850;}}}}}}}}}s:1:"u";a:3:{s:1:";";a:1:{s:9:"codepoint";i:9633;}s:1:"a";a:1:{s:1:"r";a:2:{s:1:"e";a:1:{s:1:";";a:1:{s:9:"codepoint";i:9633;}}s:1:"f";a:1:{s:1:";";a:1:{s:9:"codepoint";i:9642;}}}}s:1:"f";a:1:{s:1:";";a:1:{s:9:"codepoint";i:9642;}}}}s:1:"r";a:1:{s:1:"a";a:1:{s:1:"r";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8594;}}}}}s:1:"s";a:4:{s:1:"c";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:120008;}}}s:1:"e";a:1:{s:1:"t";a:1:{s:1:"m";a:1:{s:1:"n";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8726;}}}}}s:1:"m";a:1:{s:1:"i";a:1:{s:1:"l";a:1:{s:1:"e";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8995;}}}}}s:1:"t";a:1:{s:1:"a";a:1:{s:1:"r";a:1:{s:1:"f";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8902;}}}}}}s:1:"t";a:2:{s:1:"a";a:1:{s:1:"r";a:2:{s:1:";";a:1:{s:9:"codepoint";i:9734;}s:1:"f";a:1:{s:1:";";a:1:{s:9:"codepoint";i:9733;}}}}s:1:"r";a:2:{s:1:"a";a:1:{s:1:"i";a:1:{s:1:"g";a:1:{s:1:"h";a:1:{s:1:"t";a:2:{s:1:"e";a:1:{s:1:"p";a:1:{s:1:"s";a:1:{s:1:"i";a:1:{s:1:"l";a:1:{s:1:"o";a:1:{s:1:"n";a:1:{s:1:";";a:1:{s:9:"codepoint";i:1013;}}}}}}}}s:1:"p";a:1:{s:1:"h";a:1:{s:1:"i";a:1:{s:1:";";a:1:{s:9:"codepoint";i:981;}}}}}}}}}s:1:"n";a:1:{s:1:"s";a:1:{s:1:";";a:1:{s:9:"codepoint";i:175;}}}}}s:1:"u";a:5:{s:1:"b";a:9:{s:1:";";a:1:{s:9:"codepoint";i:8834;}s:1:"E";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10949;}}s:1:"d";a:1:{s:1:"o";a:1:{s:1:"t";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10941;}}}}s:1:"e";a:2:{s:1:";";a:1:{s:9:"codepoint";i:8838;}s:1:"d";a:1:{s:1:"o";a:1:{s:1:"t";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10947;}}}}}s:1:"m";a:1:{s:1:"u";a:1:{s:1:"l";a:1:{s:1:"t";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10945;}}}}}s:1:"n";a:2:{s:1:"E";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10955;}}s:1:"e";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8842;}}}s:1:"p";a:1:{s:1:"l";a:1:{s:1:"u";a:1:{s:1:"s";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10943;}}}}}s:1:"r";a:1:{s:1:"a";a:1:{s:1:"r";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10617;}}}}}s:1:"s";a:3:{s:1:"e";a:1:{s:1:"t";a:3:{s:1:";";a:1:{s:9:"codepoint";i:8834;}s:1:"e";a:1:{s:1:"q";a:2:{s:1:";";a:1:{s:9:"codepoint";i:8838;}s:1:"q";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10949;}}}}s:1:"n";a:1:{s:1:"e";a:1:{s:1:"q";a:2:{s:1:";";a:1:{s:9:"codepoint";i:8842;}s:1:"q";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10955;}}}}}}}s:1:"i";a:1:{s:1:"m";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10951;}}}s:1:"u";a:2:{s:1:"b";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10965;}}s:1:"p";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10963;}}}}}s:1:"c";a:1:{s:1:"c";a:6:{s:1:";";a:1:{s:9:"codepoint";i:8827;}s:1:"a";a:1:{s:1:"p";a:1:{s:1:"p";a:1:{s:1:"r";a:1:{s:1:"o";a:1:{s:1:"x";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10936;}}}}}}}s:1:"c";a:1:{s:1:"u";a:1:{s:1:"r";a:1:{s:1:"l";a:1:{s:1:"y";a:1:{s:1:"e";a:1:{s:1:"q";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8829;}}}}}}}}s:1:"e";a:1:{s:1:"q";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10928;}}}s:1:"n";a:3:{s:1:"a";a:1:{s:1:"p";a:1:{s:1:"p";a:1:{s:1:"r";a:1:{s:1:"o";a:1:{s:1:"x";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10938;}}}}}}}s:1:"e";a:1:{s:1:"q";a:1:{s:1:"q";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10934;}}}}s:1:"s";a:1:{s:1:"i";a:1:{s:1:"m";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8937;}}}}}s:1:"s";a:1:{s:1:"i";a:1:{s:1:"m";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8831;}}}}}}s:1:"m";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8721;}}s:1:"n";a:1:{s:1:"g";a:1:{s:1:";";a:1:{s:9:"codepoint";i:9834;}}}s:1:"p";a:13:{i:1;a:2:{s:1:";";a:1:{s:9:"codepoint";i:185;}s:9:"codepoint";i:185;}i:2;a:2:{s:1:";";a:1:{s:9:"codepoint";i:178;}s:9:"codepoint";i:178;}i:3;a:2:{s:1:";";a:1:{s:9:"codepoint";i:179;}s:9:"codepoint";i:179;}s:1:";";a:1:{s:9:"codepoint";i:8835;}s:1:"E";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10950;}}s:1:"d";a:2:{s:1:"o";a:1:{s:1:"t";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10942;}}}s:1:"s";a:1:{s:1:"u";a:1:{s:1:"b";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10968;}}}}}s:1:"e";a:2:{s:1:";";a:1:{s:9:"codepoint";i:8839;}s:1:"d";a:1:{s:1:"o";a:1:{s:1:"t";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10948;}}}}}s:1:"h";a:1:{s:1:"s";a:1:{s:1:"u";a:1:{s:1:"b";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10967;}}}}}s:1:"l";a:1:{s:1:"a";a:1:{s:1:"r";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10619;}}}}}s:1:"m";a:1:{s:1:"u";a:1:{s:1:"l";a:1:{s:1:"t";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10946;}}}}}s:1:"n";a:2:{s:1:"E";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10956;}}s:1:"e";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8843;}}}s:1:"p";a:1:{s:1:"l";a:1:{s:1:"u";a:1:{s:1:"s";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10944;}}}}}s:1:"s";a:3:{s:1:"e";a:1:{s:1:"t";a:3:{s:1:";";a:1:{s:9:"codepoint";i:8835;}s:1:"e";a:1:{s:1:"q";a:2:{s:1:";";a:1:{s:9:"codepoint";i:8839;}s:1:"q";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10950;}}}}s:1:"n";a:1:{s:1:"e";a:1:{s:1:"q";a:2:{s:1:";";a:1:{s:9:"codepoint";i:8843;}s:1:"q";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10956;}}}}}}}s:1:"i";a:1:{s:1:"m";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10952;}}}s:1:"u";a:2:{s:1:"b";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10964;}}s:1:"p";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10966;}}}}}}s:1:"w";a:3:{s:1:"A";a:1:{s:1:"r";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8665;}}}}s:1:"a";a:1:{s:1:"r";a:2:{s:1:"h";a:1:{s:1:"k";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10534;}}}s:1:"r";a:2:{s:1:";";a:1:{s:9:"codepoint";i:8601;}s:1:"o";a:1:{s:1:"w";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8601;}}}}}}s:1:"n";a:1:{s:1:"w";a:1:{s:1:"a";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10538;}}}}}}s:1:"z";a:1:{s:1:"l";a:1:{s:1:"i";a:1:{s:1:"g";a:2:{s:1:";";a:1:{s:9:"codepoint";i:223;}s:9:"codepoint";i:223;}}}}}s:1:"t";a:13:{s:1:"a";a:2:{s:1:"r";a:1:{s:1:"g";a:1:{s:1:"e";a:1:{s:1:"t";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8982;}}}}}s:1:"u";a:1:{s:1:";";a:1:{s:9:"codepoint";i:964;}}}s:1:"b";a:1:{s:1:"r";a:1:{s:1:"k";a:1:{s:1:";";a:1:{s:9:"codepoint";i:9140;}}}}s:1:"c";a:3:{s:1:"a";a:1:{s:1:"r";a:1:{s:1:"o";a:1:{s:1:"n";a:1:{s:1:";";a:1:{s:9:"codepoint";i:357;}}}}}s:1:"e";a:1:{s:1:"d";a:1:{s:1:"i";a:1:{s:1:"l";a:1:{s:1:";";a:1:{s:9:"codepoint";i:355;}}}}}s:1:"y";a:1:{s:1:";";a:1:{s:9:"codepoint";i:1090;}}}s:1:"d";a:1:{s:1:"o";a:1:{s:1:"t";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8411;}}}}s:1:"e";a:1:{s:1:"l";a:1:{s:1:"r";a:1:{s:1:"e";a:1:{s:1:"c";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8981;}}}}}}s:1:"f";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:120113;}}}s:1:"h";a:4:{s:1:"e";a:2:{s:1:"r";a:1:{s:1:"e";a:2:{i:4;a:1:{s:1:";";a:1:{s:9:"codepoint";i:8756;}}s:1:"f";a:1:{s:1:"o";a:1:{s:1:"r";a:1:{s:1:"e";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8756;}}}}}}}s:1:"t";a:1:{s:1:"a";a:3:{s:1:";";a:1:{s:9:"codepoint";i:952;}s:1:"s";a:1:{s:1:"y";a:1:{s:1:"m";a:1:{s:1:";";a:1:{s:9:"codepoint";i:977;}}}}s:1:"v";a:1:{s:1:";";a:1:{s:9:"codepoint";i:977;}}}}}s:1:"i";a:2:{s:1:"c";a:1:{s:1:"k";a:2:{s:1:"a";a:1:{s:1:"p";a:1:{s:1:"p";a:1:{s:1:"r";a:1:{s:1:"o";a:1:{s:1:"x";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8776;}}}}}}}s:1:"s";a:1:{s:1:"i";a:1:{s:1:"m";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8764;}}}}}}s:1:"n";a:1:{s:1:"s";a:1:{s:1:"p";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8201;}}}}}s:1:"k";a:2:{s:1:"a";a:1:{s:1:"p";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8776;}}}s:1:"s";a:1:{s:1:"i";a:1:{s:1:"m";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8764;}}}}}s:1:"o";a:1:{s:1:"r";a:1:{s:1:"n";a:2:{s:1:";";a:1:{s:9:"codepoint";i:254;}s:9:"codepoint";i:254;}}}}s:1:"i";a:3:{s:1:"l";a:1:{s:1:"d";a:1:{s:1:"e";a:1:{s:1:";";a:1:{s:9:"codepoint";i:732;}}}}s:1:"m";a:1:{s:1:"e";a:1:{s:1:"s";a:4:{s:1:";";a:1:{s:9:"codepoint";i:215;}s:9:"codepoint";i:215;s:1:"b";a:2:{s:1:";";a:1:{s:9:"codepoint";i:8864;}s:1:"a";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10801;}}}}s:1:"d";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10800;}}}}}s:1:"n";a:1:{s:1:"t";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8749;}}}}s:1:"o";a:3:{s:1:"e";a:1:{s:1:"a";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10536;}}}s:1:"p";a:4:{s:1:";";a:1:{s:9:"codepoint";i:8868;}s:1:"b";a:1:{s:1:"o";a:1:{s:1:"t";a:1:{s:1:";";a:1:{s:9:"codepoint";i:9014;}}}}s:1:"c";a:1:{s:1:"i";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10993;}}}}s:1:"f";a:2:{s:1:";";a:1:{s:9:"codepoint";i:120165;}s:1:"o";a:1:{s:1:"r";a:1:{s:1:"k";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10970;}}}}}}s:1:"s";a:1:{s:1:"a";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10537;}}}}s:1:"p";a:1:{s:1:"r";a:1:{s:1:"i";a:1:{s:1:"m";a:1:{s:1:"e";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8244;}}}}}}s:1:"r";a:3:{s:1:"a";a:1:{s:1:"d";a:1:{s:1:"e";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8482;}}}}s:1:"i";a:7:{s:1:"a";a:1:{s:1:"n";a:1:{s:1:"g";a:1:{s:1:"l";a:1:{s:1:"e";a:5:{s:1:";";a:1:{s:9:"codepoint";i:9653;}s:1:"d";a:1:{s:1:"o";a:1:{s:1:"w";a:1:{s:1:"n";a:1:{s:1:";";a:1:{s:9:"codepoint";i:9663;}}}}}s:1:"l";a:1:{s:1:"e";a:1:{s:1:"f";a:1:{s:1:"t";a:2:{s:1:";";a:1:{s:9:"codepoint";i:9667;}s:1:"e";a:1:{s:1:"q";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8884;}}}}}}}s:1:"q";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8796;}}s:1:"r";a:1:{s:1:"i";a:1:{s:1:"g";a:1:{s:1:"h";a:1:{s:1:"t";a:2:{s:1:";";a:1:{s:9:"codepoint";i:9657;}s:1:"e";a:1:{s:1:"q";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8885;}}}}}}}}}}}}}s:1:"d";a:1:{s:1:"o";a:1:{s:1:"t";a:1:{s:1:";";a:1:{s:9:"codepoint";i:9708;}}}}s:1:"e";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8796;}}s:1:"m";a:1:{s:1:"i";a:1:{s:1:"n";a:1:{s:1:"u";a:1:{s:1:"s";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10810;}}}}}}s:1:"p";a:1:{s:1:"l";a:1:{s:1:"u";a:1:{s:1:"s";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10809;}}}}}s:1:"s";a:1:{s:1:"b";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10701;}}}s:1:"t";a:1:{s:1:"i";a:1:{s:1:"m";a:1:{s:1:"e";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10811;}}}}}}s:1:"p";a:1:{s:1:"e";a:1:{s:1:"z";a:1:{s:1:"i";a:1:{s:1:"u";a:1:{s:1:"m";a:1:{s:1:";";a:1:{s:9:"codepoint";i:9186;}}}}}}}}s:1:"s";a:3:{s:1:"c";a:2:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:120009;}}s:1:"y";a:1:{s:1:";";a:1:{s:9:"codepoint";i:1094;}}}s:1:"h";a:1:{s:1:"c";a:1:{s:1:"y";a:1:{s:1:";";a:1:{s:9:"codepoint";i:1115;}}}}s:1:"t";a:1:{s:1:"r";a:1:{s:1:"o";a:1:{s:1:"k";a:1:{s:1:";";a:1:{s:9:"codepoint";i:359;}}}}}}s:1:"w";a:2:{s:1:"i";a:1:{s:1:"x";a:1:{s:1:"t";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8812;}}}}s:1:"o";a:1:{s:1:"h";a:1:{s:1:"e";a:1:{s:1:"a";a:1:{s:1:"d";a:2:{s:1:"l";a:1:{s:1:"e";a:1:{s:1:"f";a:1:{s:1:"t";a:1:{s:1:"a";a:1:{s:1:"r";a:1:{s:1:"r";a:1:{s:1:"o";a:1:{s:1:"w";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8606;}}}}}}}}}}s:1:"r";a:1:{s:1:"i";a:1:{s:1:"g";a:1:{s:1:"h";a:1:{s:1:"t";a:1:{s:1:"a";a:1:{s:1:"r";a:1:{s:1:"r";a:1:{s:1:"o";a:1:{s:1:"w";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8608;}}}}}}}}}}}}}}}}}}s:1:"u";a:18:{s:1:"A";a:1:{s:1:"r";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8657;}}}}s:1:"H";a:1:{s:1:"a";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10595;}}}}s:1:"a";a:2:{s:1:"c";a:1:{s:1:"u";a:1:{s:1:"t";a:1:{s:1:"e";a:2:{s:1:";";a:1:{s:9:"codepoint";i:250;}s:9:"codepoint";i:250;}}}}s:1:"r";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8593;}}}}s:1:"b";a:1:{s:1:"r";a:2:{s:1:"c";a:1:{s:1:"y";a:1:{s:1:";";a:1:{s:9:"codepoint";i:1118;}}}s:1:"e";a:1:{s:1:"v";a:1:{s:1:"e";a:1:{s:1:";";a:1:{s:9:"codepoint";i:365;}}}}}}s:1:"c";a:2:{s:1:"i";a:1:{s:1:"r";a:1:{s:1:"c";a:2:{s:1:";";a:1:{s:9:"codepoint";i:251;}s:9:"codepoint";i:251;}}}s:1:"y";a:1:{s:1:";";a:1:{s:9:"codepoint";i:1091;}}}s:1:"d";a:3:{s:1:"a";a:1:{s:1:"r";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8645;}}}}s:1:"b";a:1:{s:1:"l";a:1:{s:1:"a";a:1:{s:1:"c";a:1:{s:1:";";a:1:{s:9:"codepoint";i:369;}}}}}s:1:"h";a:1:{s:1:"a";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10606;}}}}}s:1:"f";a:2:{s:1:"i";a:1:{s:1:"s";a:1:{s:1:"h";a:1:{s:1:"t";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10622;}}}}}s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:120114;}}}s:1:"g";a:1:{s:1:"r";a:1:{s:1:"a";a:1:{s:1:"v";a:1:{s:1:"e";a:2:{s:1:";";a:1:{s:9:"codepoint";i:249;}s:9:"codepoint";i:249;}}}}}s:1:"h";a:2:{s:1:"a";a:1:{s:1:"r";a:2:{s:1:"l";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8639;}}s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8638;}}}}s:1:"b";a:1:{s:1:"l";a:1:{s:1:"k";a:1:{s:1:";";a:1:{s:9:"codepoint";i:9600;}}}}}s:1:"l";a:2:{s:1:"c";a:2:{s:1:"o";a:1:{s:1:"r";a:1:{s:1:"n";a:2:{s:1:";";a:1:{s:9:"codepoint";i:8988;}s:1:"e";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8988;}}}}}}s:1:"r";a:1:{s:1:"o";a:1:{s:1:"p";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8975;}}}}}s:1:"t";a:1:{s:1:"r";a:1:{s:1:"i";a:1:{s:1:";";a:1:{s:9:"codepoint";i:9720;}}}}}s:1:"m";a:2:{s:1:"a";a:1:{s:1:"c";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:363;}}}}s:1:"l";a:2:{s:1:";";a:1:{s:9:"codepoint";i:168;}s:9:"codepoint";i:168;}}s:1:"o";a:2:{s:1:"g";a:1:{s:1:"o";a:1:{s:1:"n";a:1:{s:1:";";a:1:{s:9:"codepoint";i:371;}}}}s:1:"p";a:1:{s:1:"f";a:1:{s:1:";";a:1:{s:9:"codepoint";i:120166;}}}}s:1:"p";a:6:{s:1:"a";a:1:{s:1:"r";a:1:{s:1:"r";a:1:{s:1:"o";a:1:{s:1:"w";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8593;}}}}}}s:1:"d";a:1:{s:1:"o";a:1:{s:1:"w";a:1:{s:1:"n";a:1:{s:1:"a";a:1:{s:1:"r";a:1:{s:1:"r";a:1:{s:1:"o";a:1:{s:1:"w";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8597;}}}}}}}}}}s:1:"h";a:1:{s:1:"a";a:1:{s:1:"r";a:1:{s:1:"p";a:1:{s:1:"o";a:1:{s:1:"o";a:1:{s:1:"n";a:2:{s:1:"l";a:1:{s:1:"e";a:1:{s:1:"f";a:1:{s:1:"t";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8639;}}}}}s:1:"r";a:1:{s:1:"i";a:1:{s:1:"g";a:1:{s:1:"h";a:1:{s:1:"t";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8638;}}}}}}}}}}}}}s:1:"l";a:1:{s:1:"u";a:1:{s:1:"s";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8846;}}}}s:1:"s";a:1:{s:1:"i";a:3:{s:1:";";a:1:{s:9:"codepoint";i:965;}s:1:"h";a:1:{s:1:";";a:1:{s:9:"codepoint";i:978;}}s:1:"l";a:1:{s:1:"o";a:1:{s:1:"n";a:1:{s:1:";";a:1:{s:9:"codepoint";i:965;}}}}}}s:1:"u";a:1:{s:1:"p";a:1:{s:1:"a";a:1:{s:1:"r";a:1:{s:1:"r";a:1:{s:1:"o";a:1:{s:1:"w";a:1:{s:1:"s";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8648;}}}}}}}}}}s:1:"r";a:3:{s:1:"c";a:2:{s:1:"o";a:1:{s:1:"r";a:1:{s:1:"n";a:2:{s:1:";";a:1:{s:9:"codepoint";i:8989;}s:1:"e";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8989;}}}}}}s:1:"r";a:1:{s:1:"o";a:1:{s:1:"p";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8974;}}}}}s:1:"i";a:1:{s:1:"n";a:1:{s:1:"g";a:1:{s:1:";";a:1:{s:9:"codepoint";i:367;}}}}s:1:"t";a:1:{s:1:"r";a:1:{s:1:"i";a:1:{s:1:";";a:1:{s:9:"codepoint";i:9721;}}}}}s:1:"s";a:1:{s:1:"c";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:120010;}}}}s:1:"t";a:3:{s:1:"d";a:1:{s:1:"o";a:1:{s:1:"t";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8944;}}}}s:1:"i";a:1:{s:1:"l";a:1:{s:1:"d";a:1:{s:1:"e";a:1:{s:1:";";a:1:{s:9:"codepoint";i:361;}}}}}s:1:"r";a:1:{s:1:"i";a:2:{s:1:";";a:1:{s:9:"codepoint";i:9653;}s:1:"f";a:1:{s:1:";";a:1:{s:9:"codepoint";i:9652;}}}}}s:1:"u";a:2:{s:1:"a";a:1:{s:1:"r";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8648;}}}}s:1:"m";a:1:{s:1:"l";a:2:{s:1:";";a:1:{s:9:"codepoint";i:252;}s:9:"codepoint";i:252;}}}s:1:"w";a:1:{s:1:"a";a:1:{s:1:"n";a:1:{s:1:"g";a:1:{s:1:"l";a:1:{s:1:"e";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10663;}}}}}}}}s:1:"v";a:14:{s:1:"A";a:1:{s:1:"r";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8661;}}}}s:1:"B";a:1:{s:1:"a";a:1:{s:1:"r";a:2:{s:1:";";a:1:{s:9:"codepoint";i:10984;}s:1:"v";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10985;}}}}}s:1:"D";a:1:{s:1:"a";a:1:{s:1:"s";a:1:{s:1:"h";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8872;}}}}}s:1:"a";a:2:{s:1:"n";a:1:{s:1:"g";a:1:{s:1:"r";a:1:{s:1:"t";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10652;}}}}}s:1:"r";a:7:{s:1:"e";a:1:{s:1:"p";a:1:{s:1:"s";a:1:{s:1:"i";a:1:{s:1:"l";a:1:{s:1:"o";a:1:{s:1:"n";a:1:{s:1:";";a:1:{s:9:"codepoint";i:949;}}}}}}}}s:1:"k";a:1:{s:1:"a";a:1:{s:1:"p";a:1:{s:1:"p";a:1:{s:1:"a";a:1:{s:1:";";a:1:{s:9:"codepoint";i:1008;}}}}}}s:1:"n";a:1:{s:1:"o";a:1:{s:1:"t";a:1:{s:1:"h";a:1:{s:1:"i";a:1:{s:1:"n";a:1:{s:1:"g";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8709;}}}}}}}}s:1:"p";a:3:{s:1:"h";a:1:{s:1:"i";a:1:{s:1:";";a:1:{s:9:"codepoint";i:966;}}}s:1:"i";a:1:{s:1:";";a:1:{s:9:"codepoint";i:982;}}s:1:"r";a:1:{s:1:"o";a:1:{s:1:"p";a:1:{s:1:"t";a:1:{s:1:"o";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8733;}}}}}}}s:1:"r";a:2:{s:1:";";a:1:{s:9:"codepoint";i:8597;}s:1:"h";a:1:{s:1:"o";a:1:{s:1:";";a:1:{s:9:"codepoint";i:1009;}}}}s:1:"s";a:1:{s:1:"i";a:1:{s:1:"g";a:1:{s:1:"m";a:1:{s:1:"a";a:1:{s:1:";";a:1:{s:9:"codepoint";i:962;}}}}}}s:1:"t";a:2:{s:1:"h";a:1:{s:1:"e";a:1:{s:1:"t";a:1:{s:1:"a";a:1:{s:1:";";a:1:{s:9:"codepoint";i:977;}}}}}s:1:"r";a:1:{s:1:"i";a:1:{s:1:"a";a:1:{s:1:"n";a:1:{s:1:"g";a:1:{s:1:"l";a:1:{s:1:"e";a:2:{s:1:"l";a:1:{s:1:"e";a:1:{s:1:"f";a:1:{s:1:"t";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8882;}}}}}s:1:"r";a:1:{s:1:"i";a:1:{s:1:"g";a:1:{s:1:"h";a:1:{s:1:"t";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8883;}}}}}}}}}}}}}}}}s:1:"c";a:1:{s:1:"y";a:1:{s:1:";";a:1:{s:9:"codepoint";i:1074;}}}s:1:"d";a:1:{s:1:"a";a:1:{s:1:"s";a:1:{s:1:"h";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8866;}}}}}s:1:"e";a:3:{s:1:"e";a:3:{s:1:";";a:1:{s:9:"codepoint";i:8744;}s:1:"b";a:1:{s:1:"a";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8891;}}}}s:1:"e";a:1:{s:1:"q";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8794;}}}}s:1:"l";a:1:{s:1:"l";a:1:{s:1:"i";a:1:{s:1:"p";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8942;}}}}}s:1:"r";a:2:{s:1:"b";a:1:{s:1:"a";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:124;}}}}s:1:"t";a:1:{s:1:";";a:1:{s:9:"codepoint";i:124;}}}}s:1:"f";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:120115;}}}s:1:"l";a:1:{s:1:"t";a:1:{s:1:"r";a:1:{s:1:"i";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8882;}}}}}s:1:"o";a:1:{s:1:"p";a:1:{s:1:"f";a:1:{s:1:";";a:1:{s:9:"codepoint";i:120167;}}}}s:1:"p";a:1:{s:1:"r";a:1:{s:1:"o";a:1:{s:1:"p";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8733;}}}}}s:1:"r";a:1:{s:1:"t";a:1:{s:1:"r";a:1:{s:1:"i";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8883;}}}}}s:1:"s";a:1:{s:1:"c";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:120011;}}}}s:1:"z";a:1:{s:1:"i";a:1:{s:1:"g";a:1:{s:1:"z";a:1:{s:1:"a";a:1:{s:1:"g";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10650;}}}}}}}}s:1:"w";a:7:{s:1:"c";a:1:{s:1:"i";a:1:{s:1:"r";a:1:{s:1:"c";a:1:{s:1:";";a:1:{s:9:"codepoint";i:373;}}}}}s:1:"e";a:2:{s:1:"d";a:2:{s:1:"b";a:1:{s:1:"a";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10847;}}}}s:1:"g";a:1:{s:1:"e";a:2:{s:1:";";a:1:{s:9:"codepoint";i:8743;}s:1:"q";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8793;}}}}}s:1:"i";a:1:{s:1:"e";a:1:{s:1:"r";a:1:{s:1:"p";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8472;}}}}}}s:1:"f";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:120116;}}}s:1:"o";a:1:{s:1:"p";a:1:{s:1:"f";a:1:{s:1:";";a:1:{s:9:"codepoint";i:120168;}}}}s:1:"p";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8472;}}s:1:"r";a:2:{s:1:";";a:1:{s:9:"codepoint";i:8768;}s:1:"e";a:1:{s:1:"a";a:1:{s:1:"t";a:1:{s:1:"h";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8768;}}}}}}s:1:"s";a:1:{s:1:"c";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:120012;}}}}}s:1:"x";a:14:{s:1:"c";a:3:{s:1:"a";a:1:{s:1:"p";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8898;}}}s:1:"i";a:1:{s:1:"r";a:1:{s:1:"c";a:1:{s:1:";";a:1:{s:9:"codepoint";i:9711;}}}}s:1:"u";a:1:{s:1:"p";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8899;}}}}s:1:"d";a:1:{s:1:"t";a:1:{s:1:"r";a:1:{s:1:"i";a:1:{s:1:";";a:1:{s:9:"codepoint";i:9661;}}}}}s:1:"f";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:120117;}}}s:1:"h";a:2:{s:1:"A";a:1:{s:1:"r";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10234;}}}}s:1:"a";a:1:{s:1:"r";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10231;}}}}}s:1:"i";a:1:{s:1:";";a:1:{s:9:"codepoint";i:958;}}s:1:"l";a:2:{s:1:"A";a:1:{s:1:"r";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10232;}}}}s:1:"a";a:1:{s:1:"r";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10229;}}}}}s:1:"m";a:1:{s:1:"a";a:1:{s:1:"p";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10236;}}}}s:1:"n";a:1:{s:1:"i";a:1:{s:1:"s";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8955;}}}}s:1:"o";a:3:{s:1:"d";a:1:{s:1:"o";a:1:{s:1:"t";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10752;}}}}s:1:"p";a:2:{s:1:"f";a:1:{s:1:";";a:1:{s:9:"codepoint";i:120169;}}s:1:"l";a:1:{s:1:"u";a:1:{s:1:"s";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10753;}}}}}s:1:"t";a:1:{s:1:"i";a:1:{s:1:"m";a:1:{s:1:"e";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10754;}}}}}}s:1:"r";a:2:{s:1:"A";a:1:{s:1:"r";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10233;}}}}s:1:"a";a:1:{s:1:"r";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10230;}}}}}s:1:"s";a:2:{s:1:"c";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:120013;}}}s:1:"q";a:1:{s:1:"c";a:1:{s:1:"u";a:1:{s:1:"p";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10758;}}}}}}s:1:"u";a:2:{s:1:"p";a:1:{s:1:"l";a:1:{s:1:"u";a:1:{s:1:"s";a:1:{s:1:";";a:1:{s:9:"codepoint";i:10756;}}}}}s:1:"t";a:1:{s:1:"r";a:1:{s:1:"i";a:1:{s:1:";";a:1:{s:9:"codepoint";i:9651;}}}}}s:1:"v";a:1:{s:1:"e";a:1:{s:1:"e";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8897;}}}}s:1:"w";a:1:{s:1:"e";a:1:{s:1:"d";a:1:{s:1:"g";a:1:{s:1:"e";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8896;}}}}}}}s:1:"y";a:8:{s:1:"a";a:1:{s:1:"c";a:2:{s:1:"u";a:1:{s:1:"t";a:1:{s:1:"e";a:2:{s:1:";";a:1:{s:9:"codepoint";i:253;}s:9:"codepoint";i:253;}}}s:1:"y";a:1:{s:1:";";a:1:{s:9:"codepoint";i:1103;}}}}s:1:"c";a:2:{s:1:"i";a:1:{s:1:"r";a:1:{s:1:"c";a:1:{s:1:";";a:1:{s:9:"codepoint";i:375;}}}}s:1:"y";a:1:{s:1:";";a:1:{s:9:"codepoint";i:1099;}}}s:1:"e";a:1:{s:1:"n";a:2:{s:1:";";a:1:{s:9:"codepoint";i:165;}s:9:"codepoint";i:165;}}s:1:"f";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:120118;}}}s:1:"i";a:1:{s:1:"c";a:1:{s:1:"y";a:1:{s:1:";";a:1:{s:9:"codepoint";i:1111;}}}}s:1:"o";a:1:{s:1:"p";a:1:{s:1:"f";a:1:{s:1:";";a:1:{s:9:"codepoint";i:120170;}}}}s:1:"s";a:1:{s:1:"c";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:120014;}}}}s:1:"u";a:2:{s:1:"c";a:1:{s:1:"y";a:1:{s:1:";";a:1:{s:9:"codepoint";i:1102;}}}s:1:"m";a:1:{s:1:"l";a:2:{s:1:";";a:1:{s:9:"codepoint";i:255;}s:9:"codepoint";i:255;}}}}s:1:"z";a:10:{s:1:"a";a:1:{s:1:"c";a:1:{s:1:"u";a:1:{s:1:"t";a:1:{s:1:"e";a:1:{s:1:";";a:1:{s:9:"codepoint";i:378;}}}}}}s:1:"c";a:2:{s:1:"a";a:1:{s:1:"r";a:1:{s:1:"o";a:1:{s:1:"n";a:1:{s:1:";";a:1:{s:9:"codepoint";i:382;}}}}}s:1:"y";a:1:{s:1:";";a:1:{s:9:"codepoint";i:1079;}}}s:1:"d";a:1:{s:1:"o";a:1:{s:1:"t";a:1:{s:1:";";a:1:{s:9:"codepoint";i:380;}}}}s:1:"e";a:2:{s:1:"e";a:1:{s:1:"t";a:1:{s:1:"r";a:1:{s:1:"f";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8488;}}}}}s:1:"t";a:1:{s:1:"a";a:1:{s:1:";";a:1:{s:9:"codepoint";i:950;}}}}s:1:"f";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:120119;}}}s:1:"h";a:1:{s:1:"c";a:1:{s:1:"y";a:1:{s:1:";";a:1:{s:9:"codepoint";i:1078;}}}}s:1:"i";a:1:{s:1:"g";a:1:{s:1:"r";a:1:{s:1:"a";a:1:{s:1:"r";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8669;}}}}}}}s:1:"o";a:1:{s:1:"p";a:1:{s:1:"f";a:1:{s:1:";";a:1:{s:9:"codepoint";i:120171;}}}}s:1:"s";a:1:{s:1:"c";a:1:{s:1:"r";a:1:{s:1:";";a:1:{s:9:"codepoint";i:120015;}}}}s:1:"w";a:2:{s:1:"j";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8205;}}s:1:"n";a:1:{s:1:"j";a:1:{s:1:";";a:1:{s:9:"codepoint";i:8204;}}}}}}
 
vendor/dompdf/dompdf/lib/res/html.css CHANGED
@@ -10,7 +10,7 @@
10
  *
11
  * Portions from Mozilla
12
  * @link https://dxr.mozilla.org/mozilla-central/source/layout/style/res/html.css
13
- * @license http://mozilla.org/MPL/2.0/ Mozilla Public License, v. 2.0
14
  *
15
  * Portions from W3C
16
  * @link https://drafts.csswg.org/css-ui-3/#default-style-sheet
10
  *
11
  * Portions from Mozilla
12
  * @link https://dxr.mozilla.org/mozilla-central/source/layout/style/res/html.css
13
+ * @license http://mozilla.org/MPL/2.0/ Mozilla Public License, v. 2.0
14
  *
15
  * Portions from W3C
16
  * @link https://drafts.csswg.org/css-ui-3/#default-style-sheet
vendor/dompdf/dompdf/src/Adapter/CPDF.php CHANGED
@@ -14,10 +14,10 @@ namespace Dompdf\Adapter;
14
 
15
  use Dompdf\Canvas;
16
  use Dompdf\Dompdf;
17
- use Dompdf\Helpers;
18
  use Dompdf\Exception;
 
 
19
  use Dompdf\Image\Cache;
20
- use Dompdf\PhpEvaluator;
21
  use FontLib\Exception\FontNotFoundException;
22
 
23
  /**
@@ -41,66 +41,66 @@ class CPDF implements Canvas
41
  /**
42
  * Dimensions of paper sizes in points
43
  *
44
- * @var array;
45
  */
46
  static $PAPER_SIZES = [
47
- "4a0" => [0, 0, 4767.87, 6740.79],
48
- "2a0" => [0, 0, 3370.39, 4767.87],
49
- "a0" => [0, 0, 2383.94, 3370.39],
50
- "a1" => [0, 0, 1683.78, 2383.94],
51
- "a2" => [0, 0, 1190.55, 1683.78],
52
- "a3" => [0, 0, 841.89, 1190.55],
53
- "a4" => [0, 0, 595.28, 841.89],
54
- "a5" => [0, 0, 419.53, 595.28],
55
- "a6" => [0, 0, 297.64, 419.53],
56
- "a7" => [0, 0, 209.76, 297.64],
57
- "a8" => [0, 0, 147.40, 209.76],
58
- "a9" => [0, 0, 104.88, 147.40],
59
- "a10" => [0, 0, 73.70, 104.88],
60
- "b0" => [0, 0, 2834.65, 4008.19],
61
- "b1" => [0, 0, 2004.09, 2834.65],
62
- "b2" => [0, 0, 1417.32, 2004.09],
63
- "b3" => [0, 0, 1000.63, 1417.32],
64
- "b4" => [0, 0, 708.66, 1000.63],
65
- "b5" => [0, 0, 498.90, 708.66],
66
- "b6" => [0, 0, 354.33, 498.90],
67
- "b7" => [0, 0, 249.45, 354.33],
68
- "b8" => [0, 0, 175.75, 249.45],
69
- "b9" => [0, 0, 124.72, 175.75],
70
- "b10" => [0, 0, 87.87, 124.72],
71
- "c0" => [0, 0, 2599.37, 3676.54],
72
- "c1" => [0, 0, 1836.85, 2599.37],
73
- "c2" => [0, 0, 1298.27, 1836.85],
74
- "c3" => [0, 0, 918.43, 1298.27],
75
- "c4" => [0, 0, 649.13, 918.43],
76
- "c5" => [0, 0, 459.21, 649.13],
77
- "c6" => [0, 0, 323.15, 459.21],
78
- "c7" => [0, 0, 229.61, 323.15],
79
- "c8" => [0, 0, 161.57, 229.61],
80
- "c9" => [0, 0, 113.39, 161.57],
81
- "c10" => [0, 0, 79.37, 113.39],
82
- "ra0" => [0, 0, 2437.80, 3458.27],
83
- "ra1" => [0, 0, 1729.13, 2437.80],
84
- "ra2" => [0, 0, 1218.90, 1729.13],
85
- "ra3" => [0, 0, 864.57, 1218.90],
86
- "ra4" => [0, 0, 609.45, 864.57],
87
- "sra0" => [0, 0, 2551.18, 3628.35],
88
- "sra1" => [0, 0, 1814.17, 2551.18],
89
- "sra2" => [0, 0, 1275.59, 1814.17],
90
- "sra3" => [0, 0, 907.09, 1275.59],
91
- "sra4" => [0, 0, 637.80, 907.09],
92
- "letter" => [0, 0, 612.00, 792.00],
93
- "half-letter" => [0, 0, 396.00, 612.00],
94
- "legal" => [0, 0, 612.00, 1008.00],
95
- "ledger" => [0, 0, 1224.00, 792.00],
96
- "tabloid" => [0, 0, 792.00, 1224.00],
97
- "executive" => [0, 0, 521.86, 756.00],
98
- "folio" => [0, 0, 612.00, 936.00],
99
- "commercial #10 envelope" => [0, 0, 684, 297],
100
- "catalog #10 1/2 envelope" => [0, 0, 648, 864],
101
- "8.5x11" => [0, 0, 612.00, 792.00],
102
- "8.5x14" => [0, 0, 612.00, 1008.0],
103
- "11x17" => [0, 0, 792.00, 1224.00],
104
  ];
105
 
106
  /**
@@ -145,13 +145,6 @@ class CPDF implements Canvas
145
  */
146
  protected $_page_count;
147
 
148
- /**
149
- * Text to display on every page
150
- *
151
- * @var array
152
- */
153
- protected $_page_text;
154
-
155
  /**
156
  * Array of pages for accessing after rendering is initially complete
157
  *
@@ -166,24 +159,16 @@ class CPDF implements Canvas
166
  */
167
  protected $_current_opacity = 1;
168
 
169
- /**
170
- * Class constructor
171
- *
172
- * @param mixed $paper The size of paper to use in this PDF ({@link CPDF::$PAPER_SIZES})
173
- * @param string $orientation The orientation of the document (either 'landscape' or 'portrait')
174
- * @param Dompdf $dompdf The Dompdf instance
175
- */
176
- public function __construct($paper = "letter", $orientation = "portrait", Dompdf $dompdf = null)
177
  {
178
  if (is_array($paper)) {
179
- $size = $paper;
180
- } else if (isset(self::$PAPER_SIZES[mb_strtolower($paper)])) {
181
- $size = self::$PAPER_SIZES[mb_strtolower($paper)];
182
  } else {
183
- $size = self::$PAPER_SIZES["letter"];
 
184
  }
185
 
186
- if (mb_strtolower($orientation) === "landscape") {
187
  [$size[2], $size[3]] = [$size[3], $size[2]];
188
  }
189
 
@@ -209,14 +194,10 @@ class CPDF implements Canvas
209
  $this->_height = $size[3] - $size[1];
210
 
211
  $this->_page_number = $this->_page_count = 1;
212
- $this->_page_text = [];
213
 
214
  $this->_pages = [$this->_pdf->getFirstPageId()];
215
  }
216
 
217
- /**
218
- * @return Dompdf
219
- */
220
  public function get_dompdf()
221
  {
222
  return $this->_dompdf;
@@ -232,13 +213,7 @@ class CPDF implements Canvas
232
  return $this->_pdf;
233
  }
234
 
235
- /**
236
- * Add meta information to the PDF
237
- *
238
- * @param string $label label of the value (Creator, Producer, etc.)
239
- * @param string $value the text to set
240
- */
241
- public function add_info($label, $value)
242
  {
243
  $this->_pdf->addInfo($label, $value);
244
  }
@@ -342,37 +317,21 @@ class CPDF implements Canvas
342
 
343
  //........................................................................
344
 
345
- /**
346
- * Returns the PDF's width in points
347
- * @return float
348
- */
349
  public function get_width()
350
  {
351
  return $this->_width;
352
  }
353
 
354
- /**
355
- * Returns the PDF's height in points
356
- * @return float
357
- */
358
  public function get_height()
359
  {
360
  return $this->_height;
361
  }
362
 
363
- /**
364
- * Returns the current page number
365
- * @return int
366
- */
367
  public function get_page_number()
368
  {
369
  return $this->_page_number;
370
  }
371
 
372
- /**
373
- * Returns the total number of pages in the document
374
- * @return int
375
- */
376
  public function get_page_count()
377
  {
378
  return $this->_page_count;
@@ -388,11 +347,6 @@ class CPDF implements Canvas
388
  $this->_page_number = $num;
389
  }
390
 
391
- /**
392
- * Sets the page count
393
- *
394
- * @param int $count
395
- */
396
  public function set_page_count($count)
397
  {
398
  $this->_page_count = $count;
@@ -402,15 +356,14 @@ class CPDF implements Canvas
402
  * Sets the stroke color
403
  *
404
  * See {@link Style::set_color()} for the format of the color array.
 
405
  * @param array $color
406
  */
407
  protected function _set_stroke_color($color)
408
  {
409
  $this->_pdf->setStrokeColor($color);
410
  $alpha = isset($color["alpha"]) ? $color["alpha"] : 1;
411
- if ($this->_current_opacity != 1) {
412
- $alpha *= $this->_current_opacity;
413
- }
414
  $this->_set_line_transparency("Normal", $alpha);
415
  }
416
 
@@ -418,15 +371,14 @@ class CPDF implements Canvas
418
  * Sets the fill colour
419
  *
420
  * See {@link Style::set_color()} for the format of the colour array.
 
421
  * @param array $color
422
  */
423
  protected function _set_fill_color($color)
424
  {
425
  $this->_pdf->setColor($color);
426
  $alpha = isset($color["alpha"]) ? $color["alpha"] : 1;
427
- if ($this->_current_opacity) {
428
- $alpha *= $this->_current_opacity;
429
- }
430
  $this->_set_fill_transparency("Normal", $alpha);
431
  }
432
 
@@ -440,8 +392,8 @@ class CPDF implements Canvas
440
  * ColorDodge, ColorBurn, HardLight, SoftLight, Difference,
441
  * Exclusion
442
  *
443
- * @param string $mode the blending mode to use
444
- * @param float $opacity 0.0 fully transparent, 1.0 fully opaque
445
  */
446
  protected function _set_line_transparency($mode, $opacity)
447
  {
@@ -458,8 +410,8 @@ class CPDF implements Canvas
458
  * ColorDogde, ColorBurn, HardLight, SoftLight, Difference,
459
  * Exclusion
460
  *
461
- * @param string $mode the blending mode to use
462
- * @param float $opacity 0.0 fully transparent, 1.0 fully opaque
463
  */
464
  protected function _set_fill_transparency($mode, $opacity)
465
  {
@@ -471,23 +423,17 @@ class CPDF implements Canvas
471
  *
472
  * @see Cpdf::setLineStyle()
473
  *
474
- * @param float $width
475
  * @param string $cap
476
  * @param string $join
477
- * @param array $dash
478
  */
479
  protected function _set_line_style($width, $cap, $join, $dash)
480
  {
481
  $this->_pdf->setLineStyle($width, $cap, $join, $dash);
482
  }
483
 
484
- /**
485
- * Sets the opacity
486
- *
487
- * @param $opacity
488
- * @param $mode
489
- */
490
- public function set_opacity($opacity, $mode = "Normal")
491
  {
492
  $this->_set_line_transparency($mode, $opacity);
493
  $this->_set_fill_transparency($mode, $opacity);
@@ -511,90 +457,33 @@ class CPDF implements Canvas
511
  return $this->_height - $y;
512
  }
513
 
514
- /**
515
- * Canvas implementation
516
- *
517
- * @param float $x1
518
- * @param float $y1
519
- * @param float $x2
520
- * @param float $y2
521
- * @param array $color
522
- * @param float $width
523
- * @param array $style
524
- */
525
- public function line($x1, $y1, $x2, $y2, $color, $width, $style = [])
526
  {
527
  $this->_set_stroke_color($color);
528
- $this->_set_line_style($width, "butt", "", $style);
529
 
530
  $this->_pdf->line($x1, $this->y($y1),
531
  $x2, $this->y($y2));
532
  $this->_set_line_transparency("Normal", $this->_current_opacity);
533
  }
534
 
535
- /**
536
- * Draw line at the specified coordinates on every page.
537
- *
538
- * See {@link Style::munge_color()} for the format of the colour array.
539
- *
540
- * @param float $x1
541
- * @param float $y1
542
- * @param float $x2
543
- * @param float $y2
544
- * @param array $color
545
- * @param float $width
546
- * @param array $style optional
547
- */
548
- public function page_line($x1, $y1, $x2, $y2, $color, $width, $style = [])
549
- {
550
- $_t = 'line';
551
- $this->_page_text[] = compact('_t', 'x1', 'y1', 'x2', 'y2', 'color', 'width', 'style');
552
- }
553
-
554
- /**
555
- * @param float $x
556
- * @param float $y
557
- * @param float $r1
558
- * @param float $r2
559
- * @param float $astart
560
- * @param float $aend
561
- * @param array $color
562
- * @param float $width
563
- * @param array $style
564
- */
565
- public function arc($x, $y, $r1, $r2, $astart, $aend, $color, $width, $style = [])
566
  {
567
  $this->_set_stroke_color($color);
568
- $this->_set_line_style($width, "butt", "", $style);
569
 
570
  $this->_pdf->ellipse($x, $this->y($y), $r1, $r2, 0, 8, $astart, $aend, false, false, true, false);
571
  $this->_set_line_transparency("Normal", $this->_current_opacity);
572
  }
573
 
574
- /**
575
- * @param float $x1
576
- * @param float $y1
577
- * @param float $w
578
- * @param float $h
579
- * @param array $color
580
- * @param float $width
581
- * @param array $style
582
- */
583
- public function rectangle($x1, $y1, $w, $h, $color, $width, $style = [])
584
  {
585
  $this->_set_stroke_color($color);
586
- $this->_set_line_style($width, "butt", "", $style);
587
  $this->_pdf->rectangle($x1, $this->y($y1) - $h, $w, $h);
588
  $this->_set_line_transparency("Normal", $this->_current_opacity);
589
  }
590
 
591
- /**
592
- * @param float $x1
593
- * @param float $y1
594
- * @param float $w
595
- * @param float $h
596
- * @param array $color
597
- */
598
  public function filled_rectangle($x1, $y1, $w, $h, $color)
599
  {
600
  $this->_set_fill_color($color);
@@ -602,143 +491,87 @@ class CPDF implements Canvas
602
  $this->_set_fill_transparency("Normal", $this->_current_opacity);
603
  }
604
 
605
- /**
606
- * @param float $x1
607
- * @param float $y1
608
- * @param float $w
609
- * @param float $h
610
- */
611
  public function clipping_rectangle($x1, $y1, $w, $h)
612
  {
613
  $this->_pdf->clippingRectangle($x1, $this->y($y1) - $h, $w, $h);
614
  }
615
 
616
- /**
617
- * @param float $x1
618
- * @param float $y1
619
- * @param float $w
620
- * @param float $h
621
- * @param float $rTL
622
- * @param float $rTR
623
- * @param float $rBR
624
- * @param float $rBL
625
- */
626
  public function clipping_roundrectangle($x1, $y1, $w, $h, $rTL, $rTR, $rBR, $rBL)
627
  {
628
  $this->_pdf->clippingRectangleRounded($x1, $this->y($y1) - $h, $w, $h, $rTL, $rTR, $rBR, $rBL);
629
  }
630
 
631
- /**
632
- *
633
- */
 
 
 
 
 
 
 
634
  public function clipping_end()
635
  {
636
  $this->_pdf->clippingEnd();
637
  }
638
 
639
- /**
640
- *
641
- */
642
  public function save()
643
  {
644
  $this->_pdf->saveState();
645
  }
646
 
647
- /**
648
- *
649
- */
650
  public function restore()
651
  {
652
  $this->_pdf->restoreState();
653
  }
654
 
655
- /**
656
- * @param $angle
657
- * @param $x
658
- * @param $y
659
- */
660
  public function rotate($angle, $x, $y)
661
  {
662
  $this->_pdf->rotate($angle, $x, $y);
663
  }
664
 
665
- /**
666
- * @param $angle_x
667
- * @param $angle_y
668
- * @param $x
669
- * @param $y
670
- */
671
  public function skew($angle_x, $angle_y, $x, $y)
672
  {
673
  $this->_pdf->skew($angle_x, $angle_y, $x, $y);
674
  }
675
 
676
- /**
677
- * @param $s_x
678
- * @param $s_y
679
- * @param $x
680
- * @param $y
681
- */
682
  public function scale($s_x, $s_y, $x, $y)
683
  {
684
  $this->_pdf->scale($s_x, $s_y, $x, $y);
685
  }
686
 
687
- /**
688
- * @param $t_x
689
- * @param $t_y
690
- */
691
  public function translate($t_x, $t_y)
692
  {
693
  $this->_pdf->translate($t_x, $t_y);
694
  }
695
 
696
- /**
697
- * @param $a
698
- * @param $b
699
- * @param $c
700
- * @param $d
701
- * @param $e
702
- * @param $f
703
- */
704
  public function transform($a, $b, $c, $d, $e, $f)
705
  {
706
  $this->_pdf->transform([$a, $b, $c, $d, $e, $f]);
707
  }
708
 
709
- /**
710
- * @param array $points
711
- * @param array $color
712
- * @param null $width
713
- * @param array $style
714
- * @param bool $fill
715
- */
716
  public function polygon($points, $color, $width = null, $style = [], $fill = false)
717
  {
718
  $this->_set_fill_color($color);
719
  $this->_set_stroke_color($color);
720
 
 
 
 
 
721
  // Adjust y values
722
  for ($i = 1; $i < count($points); $i += 2) {
723
  $points[$i] = $this->y($points[$i]);
724
  }
725
 
726
- $this->_pdf->polygon($points, count($points) / 2, $fill);
727
 
728
  $this->_set_fill_transparency("Normal", $this->_current_opacity);
729
  $this->_set_line_transparency("Normal", $this->_current_opacity);
730
  }
731
 
732
- /**
733
- * @param float $x
734
- * @param float $y
735
- * @param float $r1
736
- * @param array $color
737
- * @param null $width
738
- * @param null $style
739
- * @param bool $fill
740
- */
741
- public function circle($x, $y, $r1, $color, $width = null, $style = null, $fill = false)
742
  {
743
  $this->_set_fill_color($color);
744
  $this->_set_stroke_color($color);
@@ -747,7 +580,7 @@ class CPDF implements Canvas
747
  $this->_set_line_style($width, "round", "round", $style);
748
  }
749
 
750
- $this->_pdf->ellipse($x, $this->y($y), $r1, 0, 0, 8, 0, 360, 1, $fill);
751
 
752
  $this->_set_fill_transparency("Normal", $this->_current_opacity);
753
  $this->_set_line_transparency("Normal", $this->_current_opacity);
@@ -759,8 +592,7 @@ class CPDF implements Canvas
759
  * @param string $image_url
760
  * @param string $type
761
  *
762
- * @throws Exception
763
- * @return string The url of the newly converted image
764
  */
765
  protected function _convert_to_png($image_url, $type)
766
  {
@@ -772,6 +604,8 @@ class CPDF implements Canvas
772
 
773
  $func_name = "imagecreatefrom$type";
774
 
 
 
775
  if (!function_exists($func_name)) {
776
  if (!method_exists(Helpers::class, $func_name)) {
777
  throw new Exception("Function $func_name() not found. Cannot convert $type image: $image_url. Please install the image PHP extension.");
@@ -779,8 +613,6 @@ class CPDF implements Canvas
779
  $func_name = [Helpers::class, $func_name];
780
  }
781
 
782
- set_error_handler([Helpers::class, "record_warnings"]);
783
-
784
  try {
785
  $im = call_user_func($func_name, $image_url);
786
 
@@ -795,25 +627,19 @@ class CPDF implements Canvas
795
  imagepng($im, $filename);
796
  imagedestroy($im);
797
  } else {
798
- $filename = Cache::$broken_image;
799
  }
800
  } finally {
801
  restore_error_handler();
802
  }
803
 
804
- Cache::addTempImage($image_url, $filename);
 
 
805
 
806
  return $filename;
807
  }
808
 
809
- /**
810
- * @param string $img
811
- * @param float $x
812
- * @param float $y
813
- * @param int $w
814
- * @param int $h
815
- * @param string $resolution
816
- */
817
  public function image($img, $x, $y, $w, $h, $resolution = "normal")
818
  {
819
  [$width, $height, $type] = Helpers::dompdf_getimagesize($img, $this->get_dompdf()->getHttpContext());
@@ -839,6 +665,11 @@ class CPDF implements Canvas
839
  case "bmp":
840
  if ($debug_png) print "!!!{$type}!!!";
841
  $img = $this->_convert_to_png($img, $type);
 
 
 
 
 
842
 
843
  case "png":
844
  if ($debug_png) print '!!!png!!!';
@@ -922,17 +753,6 @@ class CPDF implements Canvas
922
  $pdf->addFormField($ft, rand(), $x, $this->y($y) - $h, $x + $w, $this->y($y), $ff, $size, $color);
923
  }
924
 
925
- /**
926
- * @param float $x
927
- * @param float $y
928
- * @param string $text
929
- * @param string $font
930
- * @param float $size
931
- * @param array $color
932
- * @param float $word_space
933
- * @param float $char_space
934
- * @param float $angle
935
- */
936
  public function text($x, $y, $text, $font, $size, $color = [0, 0, 0], $word_space = 0.0, $char_space = 0.0, $angle = 0.0)
937
  {
938
  $pdf = $this->_pdf;
@@ -947,9 +767,6 @@ class CPDF implements Canvas
947
  $this->_set_fill_transparency("Normal", $this->_current_opacity);
948
  }
949
 
950
- /**
951
- * @param string $code
952
- */
953
  public function javascript($code)
954
  {
955
  $this->_pdf->addJavascript($code);
@@ -957,25 +774,11 @@ class CPDF implements Canvas
957
 
958
  //........................................................................
959
 
960
- /**
961
- * Add a named destination (similar to <a name="foo">...</a> in html)
962
- *
963
- * @param string $anchorname The name of the named destination
964
- */
965
  public function add_named_dest($anchorname)
966
  {
967
  $this->_pdf->addDestination($anchorname, "Fit");
968
  }
969
 
970
- /**
971
- * Add a link to the pdf
972
- *
973
- * @param string $url The url to link to
974
- * @param float $x The x position of the link
975
- * @param float $y The y position of the link
976
- * @param float $width The width of the link
977
- * @param float $height The height of the link
978
- */
979
  public function add_link($url, $x, $y, $width, $height)
980
  {
981
  $y = $this->y($y) - $height;
@@ -987,28 +790,20 @@ class CPDF implements Canvas
987
  $this->_pdf->addInternalLink($name, $x, $y, $x + $width, $y + $height);
988
  }
989
  } else {
990
- $this->_pdf->addLink(rawurldecode($url), $x, $y, $x + $width, $y + $height);
991
  }
992
  }
993
 
994
  /**
995
- * @param string $text
996
- * @param string $font
997
- * @param float $size
998
- * @param float $word_spacing
999
- * @param float $char_spacing
1000
- * @return float
1001
  */
1002
- public function get_text_width($text, $font, $size, $word_spacing = 0, $char_spacing = 0)
1003
  {
1004
  $this->_pdf->selectFont($font, '', true, $this->_dompdf->getOptions()->getIsFontSubsettingEnabled());
1005
  return $this->_pdf->getTextWidth($size, $text, $word_spacing, $char_spacing);
1006
  }
1007
 
1008
  /**
1009
- * @param string $font
1010
- * @param float $size
1011
- * @return float|int
1012
  * @throws FontNotFoundException
1013
  */
1014
  public function get_font_height($font, $size)
@@ -1026,9 +821,7 @@ class CPDF implements Canvas
1026
  }*/
1027
 
1028
  /**
1029
- * @param string $font
1030
- * @param float $size
1031
- * @return float
1032
  */
1033
  public function get_font_baseline($font, $size)
1034
  {
@@ -1037,53 +830,53 @@ class CPDF implements Canvas
1037
  }
1038
 
1039
  /**
1040
- * Writes text at the specified x and y coordinates on every page
1041
  *
1042
- * The strings '{PAGE_NUM}' and '{PAGE_COUNT}' are automatically replaced
1043
- * with their current values.
 
 
 
1044
  *
1045
- * See {@link Style::munge_color()} for the format of the colour array.
 
1046
  *
1047
- * @param float $x
1048
- * @param float $y
1049
- * @param string $text the text to write
1050
- * @param string $font the font file to use
1051
- * @param float $size the font size, in points
1052
- * @param array $color
1053
- * @param float $word_space word spacing adjustment
1054
- * @param float $char_space char spacing adjustment
1055
- * @param float $angle angle to write the text at, measured CW starting from the x-axis
1056
  */
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1057
  public function page_text($x, $y, $text, $font, $size, $color = [0, 0, 0], $word_space = 0.0, $char_space = 0.0, $angle = 0.0)
1058
  {
1059
- $_t = "text";
1060
- $this->_page_text[] = compact("_t", "x", "y", "text", "font", "size", "color", "word_space", "char_space", "angle");
 
 
 
 
 
 
1061
  }
1062
 
1063
- /**
1064
- * Processes a callback or script on every page
1065
- *
1066
- * The callback function receives the four parameters `$pageNumber`,
1067
- * `$pageCount`, `$pdf`, and `$fontMetrics`, in that order. If a script is
1068
- * passed as string, the variables `$PAGE_NUM`, `$PAGE_COUNT`, `$pdf`, and
1069
- * `$fontMetrics` are available instead.
1070
- *
1071
- * This function can be used to add page numbers to all pages after the
1072
- * first one, for example.
1073
- *
1074
- * @param callable|string $code The callback function or PHP script to process on every page
1075
- */
1076
- public function page_script($code)
1077
  {
1078
- if (is_callable($code)) {
1079
- $this->_page_text[] = [
1080
- "_t" => "callback",
1081
- "callback" => $code
1082
- ];
1083
- } else {
1084
- $_t = "script";
1085
- $this->_page_text[] = compact("_t", "code");
1086
- }
1087
  }
1088
 
1089
  /**
@@ -1099,60 +892,21 @@ class CPDF implements Canvas
1099
  return $ret;
1100
  }
1101
 
1102
- /**
1103
- * Add text to each page after rendering is complete
1104
- */
1105
- protected function _add_page_text()
1106
  {
1107
- if (!count($this->_page_text)) {
1108
- return;
1109
- }
1110
-
1111
- $page_number = 1;
1112
- $eval = null;
1113
 
1114
  foreach ($this->_pages as $pid) {
1115
  $this->reopen_object($pid);
1116
 
1117
- foreach ($this->_page_text as $pt) {
1118
- extract($pt);
1119
-
1120
- switch ($_t) {
1121
- case "text":
1122
- $text = str_replace(["{PAGE_NUM}", "{PAGE_COUNT}"],
1123
- [$page_number, $this->_page_count], $text);
1124
- $this->text($x, $y, $text, $font, $size, $color, $word_space, $char_space, $angle);
1125
- break;
1126
-
1127
- case "callback":
1128
- $fontMetrics = $this->get_dompdf()->getFontMetrics();
1129
- $callback($page_number, $this->_page_count, $this, $fontMetrics);
1130
- break;
1131
-
1132
- case "script":
1133
- if (!$eval) {
1134
- $eval = new PhpEvaluator($this);
1135
- }
1136
- $eval->evaluate($code, ["PAGE_NUM" => $page_number, "PAGE_COUNT" => $this->_page_count]);
1137
- break;
1138
-
1139
- case "line":
1140
- $this->line($x1, $y1, $x2, $y2, $color, $width, $style);
1141
- break;
1142
- }
1143
- }
1144
 
1145
  $this->close_object();
1146
- $page_number++;
1147
  }
1148
  }
1149
 
1150
- /**
1151
- * Streams the PDF to the client.
1152
- *
1153
- * @param string $filename The filename to present to the client.
1154
- * @param array $options Associative array: 'compress' => 1 or 0 (default 1); 'Attachment' => 1 or 0 (default 1).
1155
- */
1156
  public function stream($filename = "document.pdf", $options = [])
1157
  {
1158
  if (headers_sent()) {
@@ -1162,8 +916,6 @@ class CPDF implements Canvas
1162
  if (!isset($options["compress"])) $options["compress"] = true;
1163
  if (!isset($options["Attachment"])) $options["Attachment"] = true;
1164
 
1165
- $this->_add_page_text();
1166
-
1167
  $debug = !$options['compress'];
1168
  $tmp = ltrim($this->_pdf->output($debug));
1169
 
@@ -1179,18 +931,10 @@ class CPDF implements Canvas
1179
  flush();
1180
  }
1181
 
1182
- /**
1183
- * Returns the PDF as a string.
1184
- *
1185
- * @param array $options Associative array: 'compress' => 1 or 0 (default 1).
1186
- * @return string
1187
- */
1188
  public function output($options = [])
1189
  {
1190
  if (!isset($options["compress"])) $options["compress"] = true;
1191
 
1192
- $this->_add_page_text();
1193
-
1194
  $debug = !$options['compress'];
1195
 
1196
  return $this->_pdf->output($debug);
14
 
15
  use Dompdf\Canvas;
16
  use Dompdf\Dompdf;
 
17
  use Dompdf\Exception;
18
+ use Dompdf\FontMetrics;
19
+ use Dompdf\Helpers;
20
  use Dompdf\Image\Cache;
 
21
  use FontLib\Exception\FontNotFoundException;
22
 
23
  /**
41
  /**
42
  * Dimensions of paper sizes in points
43
  *
44
+ * @var array
45
  */
46
  static $PAPER_SIZES = [
47
+ "4a0" => [0.0, 0.0, 4767.87, 6740.79],
48
+ "2a0" => [0.0, 0.0, 3370.39, 4767.87],
49
+ "a0" => [0.0, 0.0, 2383.94, 3370.39],
50
+ "a1" => [0.0, 0.0, 1683.78, 2383.94],
51
+ "a2" => [0.0, 0.0, 1190.55, 1683.78],
52
+ "a3" => [0.0, 0.0, 841.89, 1190.55],
53
+ "a4" => [0.0, 0.0, 595.28, 841.89],
54
+ "a5" => [0.0, 0.0, 419.53, 595.28],
55
+ "a6" => [0.0, 0.0, 297.64, 419.53],
56
+ "a7" => [0.0, 0.0, 209.76, 297.64],
57
+ "a8" => [0.0, 0.0, 147.40, 209.76],
58
+ "a9" => [0.0, 0.0, 104.88, 147.40],
59
+ "a10" => [0.0, 0.0, 73.70, 104.88],
60
+ "b0" => [0.0, 0.0, 2834.65, 4008.19],
61
+ "b1" => [0.0, 0.0, 2004.09, 2834.65],
62
+ "b2" => [0.0, 0.0, 1417.32, 2004.09],
63
+ "b3" => [0.0, 0.0, 1000.63, 1417.32],
64
+ "b4" => [0.0, 0.0, 708.66, 1000.63],
65
+ "b5" => [0.0, 0.0, 498.90, 708.66],
66
+ "b6" => [0.0, 0.0, 354.33, 498.90],
67
+ "b7" => [0.0, 0.0, 249.45, 354.33],
68
+ "b8" => [0.0, 0.0, 175.75, 249.45],
69
+ "b9" => [0.0, 0.0, 124.72, 175.75],
70
+ "b10" => [0.0, 0.0, 87.87, 124.72],
71
+ "c0" => [0.0, 0.0, 2599.37, 3676.54],
72
+ "c1" => [0.0, 0.0, 1836.85, 2599.37],
73
+ "c2" => [0.0, 0.0, 1298.27, 1836.85],
74
+ "c3" => [0.0, 0.0, 918.43, 1298.27],
75
+ "c4" => [0.0, 0.0, 649.13, 918.43],
76
+ "c5" => [0.0, 0.0, 459.21, 649.13],
77
+ "c6" => [0.0, 0.0, 323.15, 459.21],
78
+ "c7" => [0.0, 0.0, 229.61, 323.15],
79
+ "c8" => [0.0, 0.0, 161.57, 229.61],
80
+ "c9" => [0.0, 0.0, 113.39, 161.57],
81
+ "c10" => [0.0, 0.0, 79.37, 113.39],
82
+ "ra0" => [0.0, 0.0, 2437.80, 3458.27],
83
+ "ra1" => [0.0, 0.0, 1729.13, 2437.80],
84
+ "ra2" => [0.0, 0.0, 1218.90, 1729.13],
85
+ "ra3" => [0.0, 0.0, 864.57, 1218.90],
86
+ "ra4" => [0.0, 0.0, 609.45, 864.57],
87
+ "sra0" => [0.0, 0.0, 2551.18, 3628.35],
88
+ "sra1" => [0.0, 0.0, 1814.17, 2551.18],
89
+ "sra2" => [0.0, 0.0, 1275.59, 1814.17],
90
+ "sra3" => [0.0, 0.0, 907.09, 1275.59],
91
+ "sra4" => [0.0, 0.0, 637.80, 907.09],
92
+ "letter" => [0.0, 0.0, 612.00, 792.00],
93
+ "half-letter" => [0.0, 0.0, 396.00, 612.00],
94
+ "legal" => [0.0, 0.0, 612.00, 1008.00],
95
+ "ledger" => [0.0, 0.0, 1224.00, 792.00],
96
+ "tabloid" => [0.0, 0.0, 792.00, 1224.00],
97
+ "executive" => [0.0, 0.0, 521.86, 756.00],
98
+ "folio" => [0.0, 0.0, 612.00, 936.00],
99
+ "commercial #10 envelope" => [0.0, 0.0, 684.00, 297.00],
100
+ "catalog #10 1/2 envelope" => [0.0, 0.0, 648.00, 864.00],
101
+ "8.5x11" => [0.0, 0.0, 612.00, 792.00],
102
+ "8.5x14" => [0.0, 0.0, 612.00, 1008.00],
103
+ "11x17" => [0.0, 0.0, 792.00, 1224.00],
104
  ];
105
 
106
  /**
145
  */
146
  protected $_page_count;
147
 
 
 
 
 
 
 
 
148
  /**
149
  * Array of pages for accessing after rendering is initially complete
150
  *
159
  */
160
  protected $_current_opacity = 1;
161
 
162
+ public function __construct($paper = "letter", $orientation = "portrait", ?Dompdf $dompdf = null)
 
 
 
 
 
 
 
163
  {
164
  if (is_array($paper)) {
165
+ $size = array_map("floatval", $paper);
 
 
166
  } else {
167
+ $paper = strtolower($paper);
168
+ $size = self::$PAPER_SIZES[$paper] ?? self::$PAPER_SIZES["letter"];
169
  }
170
 
171
+ if (strtolower($orientation) === "landscape") {
172
  [$size[2], $size[3]] = [$size[3], $size[2]];
173
  }
174
 
194
  $this->_height = $size[3] - $size[1];
195
 
196
  $this->_page_number = $this->_page_count = 1;
 
197
 
198
  $this->_pages = [$this->_pdf->getFirstPageId()];
199
  }
200
 
 
 
 
201
  public function get_dompdf()
202
  {
203
  return $this->_dompdf;
213
  return $this->_pdf;
214
  }
215
 
216
+ public function add_info(string $label, string $value): void
 
 
 
 
 
 
217
  {
218
  $this->_pdf->addInfo($label, $value);
219
  }
317
 
318
  //........................................................................
319
 
 
 
 
 
320
  public function get_width()
321
  {
322
  return $this->_width;
323
  }
324
 
 
 
 
 
325
  public function get_height()
326
  {
327
  return $this->_height;
328
  }
329
 
 
 
 
 
330
  public function get_page_number()
331
  {
332
  return $this->_page_number;
333
  }
334
 
 
 
 
 
335
  public function get_page_count()
336
  {
337
  return $this->_page_count;
347
  $this->_page_number = $num;
348
  }
349
 
 
 
 
 
 
350
  public function set_page_count($count)
351
  {
352
  $this->_page_count = $count;
356
  * Sets the stroke color
357
  *
358
  * See {@link Style::set_color()} for the format of the color array.
359
+ *
360
  * @param array $color
361
  */
362
  protected function _set_stroke_color($color)
363
  {
364
  $this->_pdf->setStrokeColor($color);
365
  $alpha = isset($color["alpha"]) ? $color["alpha"] : 1;
366
+ $alpha *= $this->_current_opacity;
 
 
367
  $this->_set_line_transparency("Normal", $alpha);
368
  }
369
 
371
  * Sets the fill colour
372
  *
373
  * See {@link Style::set_color()} for the format of the colour array.
374
+ *
375
  * @param array $color
376
  */
377
  protected function _set_fill_color($color)
378
  {
379
  $this->_pdf->setColor($color);
380
  $alpha = isset($color["alpha"]) ? $color["alpha"] : 1;
381
+ $alpha *= $this->_current_opacity;
 
 
382
  $this->_set_fill_transparency("Normal", $alpha);
383
  }
384
 
392
  * ColorDodge, ColorBurn, HardLight, SoftLight, Difference,
393
  * Exclusion
394
  *
395
+ * @param string $mode the blending mode to use
396
+ * @param float $opacity 0.0 fully transparent, 1.0 fully opaque
397
  */
398
  protected function _set_line_transparency($mode, $opacity)
399
  {
410
  * ColorDogde, ColorBurn, HardLight, SoftLight, Difference,
411
  * Exclusion
412
  *
413
+ * @param string $mode the blending mode to use
414
+ * @param float $opacity 0.0 fully transparent, 1.0 fully opaque
415
  */
416
  protected function _set_fill_transparency($mode, $opacity)
417
  {
423
  *
424
  * @see Cpdf::setLineStyle()
425
  *
426
+ * @param float $width
427
  * @param string $cap
428
  * @param string $join
429
+ * @param array $dash
430
  */
431
  protected function _set_line_style($width, $cap, $join, $dash)
432
  {
433
  $this->_pdf->setLineStyle($width, $cap, $join, $dash);
434
  }
435
 
436
+ public function set_opacity(float $opacity, string $mode = "Normal"): void
 
 
 
 
 
 
437
  {
438
  $this->_set_line_transparency($mode, $opacity);
439
  $this->_set_fill_transparency($mode, $opacity);
457
  return $this->_height - $y;
458
  }
459
 
460
+ public function line($x1, $y1, $x2, $y2, $color, $width, $style = [], $cap = "butt")
 
 
 
 
 
 
 
 
 
 
 
461
  {
462
  $this->_set_stroke_color($color);
463
+ $this->_set_line_style($width, $cap, "", $style);
464
 
465
  $this->_pdf->line($x1, $this->y($y1),
466
  $x2, $this->y($y2));
467
  $this->_set_line_transparency("Normal", $this->_current_opacity);
468
  }
469
 
470
+ public function arc($x, $y, $r1, $r2, $astart, $aend, $color, $width, $style = [], $cap = "butt")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
471
  {
472
  $this->_set_stroke_color($color);
473
+ $this->_set_line_style($width, $cap, "", $style);
474
 
475
  $this->_pdf->ellipse($x, $this->y($y), $r1, $r2, 0, 8, $astart, $aend, false, false, true, false);
476
  $this->_set_line_transparency("Normal", $this->_current_opacity);
477
  }
478
 
479
+ public function rectangle($x1, $y1, $w, $h, $color, $width, $style = [], $cap = "butt")
 
 
 
 
 
 
 
 
 
480
  {
481
  $this->_set_stroke_color($color);
482
+ $this->_set_line_style($width, $cap, "", $style);
483
  $this->_pdf->rectangle($x1, $this->y($y1) - $h, $w, $h);
484
  $this->_set_line_transparency("Normal", $this->_current_opacity);
485
  }
486
 
 
 
 
 
 
 
 
487
  public function filled_rectangle($x1, $y1, $w, $h, $color)
488
  {
489
  $this->_set_fill_color($color);
491
  $this->_set_fill_transparency("Normal", $this->_current_opacity);
492
  }
493
 
 
 
 
 
 
 
494
  public function clipping_rectangle($x1, $y1, $w, $h)
495
  {
496
  $this->_pdf->clippingRectangle($x1, $this->y($y1) - $h, $w, $h);
497
  }
498
 
 
 
 
 
 
 
 
 
 
 
499
  public function clipping_roundrectangle($x1, $y1, $w, $h, $rTL, $rTR, $rBR, $rBL)
500
  {
501
  $this->_pdf->clippingRectangleRounded($x1, $this->y($y1) - $h, $w, $h, $rTL, $rTR, $rBR, $rBL);
502
  }
503
 
504
+ public function clipping_polygon(array $points): void
505
+ {
506
+ // Adjust y values
507
+ for ($i = 1; $i < count($points); $i += 2) {
508
+ $points[$i] = $this->y($points[$i]);
509
+ }
510
+
511
+ $this->_pdf->clippingPolygon($points);
512
+ }
513
+
514
  public function clipping_end()
515
  {
516
  $this->_pdf->clippingEnd();
517
  }
518
 
 
 
 
519
  public function save()
520
  {
521
  $this->_pdf->saveState();
522
  }
523
 
 
 
 
524
  public function restore()
525
  {
526
  $this->_pdf->restoreState();
527
  }
528
 
 
 
 
 
 
529
  public function rotate($angle, $x, $y)
530
  {
531
  $this->_pdf->rotate($angle, $x, $y);
532
  }
533
 
 
 
 
 
 
 
534
  public function skew($angle_x, $angle_y, $x, $y)
535
  {
536
  $this->_pdf->skew($angle_x, $angle_y, $x, $y);
537
  }
538
 
 
 
 
 
 
 
539
  public function scale($s_x, $s_y, $x, $y)
540
  {
541
  $this->_pdf->scale($s_x, $s_y, $x, $y);
542
  }
543
 
 
 
 
 
544
  public function translate($t_x, $t_y)
545
  {
546
  $this->_pdf->translate($t_x, $t_y);
547
  }
548
 
 
 
 
 
 
 
 
 
549
  public function transform($a, $b, $c, $d, $e, $f)
550
  {
551
  $this->_pdf->transform([$a, $b, $c, $d, $e, $f]);
552
  }
553
 
 
 
 
 
 
 
 
554
  public function polygon($points, $color, $width = null, $style = [], $fill = false)
555
  {
556
  $this->_set_fill_color($color);
557
  $this->_set_stroke_color($color);
558
 
559
+ if (!$fill && isset($width)) {
560
+ $this->_set_line_style($width, "square", "miter", $style);
561
+ }
562
+
563
  // Adjust y values
564
  for ($i = 1; $i < count($points); $i += 2) {
565
  $points[$i] = $this->y($points[$i]);
566
  }
567
 
568
+ $this->_pdf->polygon($points, $fill);
569
 
570
  $this->_set_fill_transparency("Normal", $this->_current_opacity);
571
  $this->_set_line_transparency("Normal", $this->_current_opacity);
572
  }
573
 
574
+ public function circle($x, $y, $r, $color, $width = null, $style = [], $fill = false)
 
 
 
 
 
 
 
 
 
575
  {
576
  $this->_set_fill_color($color);
577
  $this->_set_stroke_color($color);
580
  $this->_set_line_style($width, "round", "round", $style);
581
  }
582
 
583
+ $this->_pdf->ellipse($x, $this->y($y), $r, 0, 0, 8, 0, 360, 1, $fill);
584
 
585
  $this->_set_fill_transparency("Normal", $this->_current_opacity);
586
  $this->_set_line_transparency("Normal", $this->_current_opacity);
592
  * @param string $image_url
593
  * @param string $type
594
  *
595
+ * @return string|null The url of the newly converted image
 
596
  */
597
  protected function _convert_to_png($image_url, $type)
598
  {
604
 
605
  $func_name = "imagecreatefrom$type";
606
 
607
+ set_error_handler([Helpers::class, "record_warnings"]);
608
+
609
  if (!function_exists($func_name)) {
610
  if (!method_exists(Helpers::class, $func_name)) {
611
  throw new Exception("Function $func_name() not found. Cannot convert $type image: $image_url. Please install the image PHP extension.");
613
  $func_name = [Helpers::class, $func_name];
614
  }
615
 
 
 
616
  try {
617
  $im = call_user_func($func_name, $image_url);
618
 
627
  imagepng($im, $filename);
628
  imagedestroy($im);
629
  } else {
630
+ $filename = null;
631
  }
632
  } finally {
633
  restore_error_handler();
634
  }
635
 
636
+ if ($filename !== null) {
637
+ Cache::addTempImage($image_url, $filename);
638
+ }
639
 
640
  return $filename;
641
  }
642
 
 
 
 
 
 
 
 
 
643
  public function image($img, $x, $y, $w, $h, $resolution = "normal")
644
  {
645
  [$width, $height, $type] = Helpers::dompdf_getimagesize($img, $this->get_dompdf()->getHttpContext());
665
  case "bmp":
666
  if ($debug_png) print "!!!{$type}!!!";
667
  $img = $this->_convert_to_png($img, $type);
668
+ if ($img === null) {
669
+ if ($debug_png) print '!!!conversion to PDF failed!!!';
670
+ $this->image(Cache::$broken_image, $x, $y, $w, $h, $resolution);
671
+ break;
672
+ }
673
 
674
  case "png":
675
  if ($debug_png) print '!!!png!!!';
753
  $pdf->addFormField($ft, rand(), $x, $this->y($y) - $h, $x + $w, $this->y($y), $ff, $size, $color);
754
  }
755
 
 
 
 
 
 
 
 
 
 
 
 
756
  public function text($x, $y, $text, $font, $size, $color = [0, 0, 0], $word_space = 0.0, $char_space = 0.0, $angle = 0.0)
757
  {
758
  $pdf = $this->_pdf;
767
  $this->_set_fill_transparency("Normal", $this->_current_opacity);
768
  }
769
 
 
 
 
770
  public function javascript($code)
771
  {
772
  $this->_pdf->addJavascript($code);
774
 
775
  //........................................................................
776
 
 
 
 
 
 
777
  public function add_named_dest($anchorname)
778
  {
779
  $this->_pdf->addDestination($anchorname, "Fit");
780
  }
781
 
 
 
 
 
 
 
 
 
 
782
  public function add_link($url, $x, $y, $width, $height)
783
  {
784
  $y = $this->y($y) - $height;
790
  $this->_pdf->addInternalLink($name, $x, $y, $x + $width, $y + $height);
791
  }
792
  } else {
793
+ $this->_pdf->addLink($url, $x, $y, $x + $width, $y + $height);
794
  }
795
  }
796
 
797
  /**
798
+ * @throws FontNotFoundException
 
 
 
 
 
799
  */
800
+ public function get_text_width($text, $font, $size, $word_spacing = 0.0, $char_spacing = 0.0)
801
  {
802
  $this->_pdf->selectFont($font, '', true, $this->_dompdf->getOptions()->getIsFontSubsettingEnabled());
803
  return $this->_pdf->getTextWidth($size, $text, $word_spacing, $char_spacing);
804
  }
805
 
806
  /**
 
 
 
807
  * @throws FontNotFoundException
808
  */
809
  public function get_font_height($font, $size)
821
  }*/
822
 
823
  /**
824
+ * @throws FontNotFoundException
 
 
825
  */
826
  public function get_font_baseline($font, $size)
827
  {
830
  }
831
 
832
  /**
833
+ * Processes a callback or script on every page.
834
  *
835
+ * The callback function receives the four parameters `int $pageNumber`,
836
+ * `int $pageCount`, `Canvas $canvas`, and `FontMetrics $fontMetrics`, in
837
+ * that order. If a script is passed as string, the variables `$PAGE_NUM`,
838
+ * `$PAGE_COUNT`, `$pdf`, and `$fontMetrics` are available instead. Passing
839
+ * a script as string is deprecated and will be removed in a future version.
840
  *
841
+ * This function can be used to add page numbers to all pages after the
842
+ * first one, for example.
843
  *
844
+ * @param callable|string $callback The callback function or PHP script to process on every page
 
 
 
 
 
 
 
 
845
  */
846
+ public function page_script($callback): void
847
+ {
848
+ if (is_string($callback)) {
849
+ $this->processPageScript(function (
850
+ int $PAGE_NUM,
851
+ int $PAGE_COUNT,
852
+ self $pdf,
853
+ FontMetrics $fontMetrics
854
+ ) use ($callback) {
855
+ eval($callback);
856
+ });
857
+ return;
858
+ }
859
+
860
+ $this->processPageScript($callback);
861
+ }
862
+
863
  public function page_text($x, $y, $text, $font, $size, $color = [0, 0, 0], $word_space = 0.0, $char_space = 0.0, $angle = 0.0)
864
  {
865
+ $this->processPageScript(function (int $pageNumber, int $pageCount) use ($x, $y, $text, $font, $size, $color, $word_space, $char_space, $angle) {
866
+ $text = str_replace(
867
+ ["{PAGE_NUM}", "{PAGE_COUNT}"],
868
+ [$pageNumber, $pageCount],
869
+ $text
870
+ );
871
+ $this->text($x, $y, $text, $font, $size, $color, $word_space, $char_space, $angle);
872
+ });
873
  }
874
 
875
+ public function page_line($x1, $y1, $x2, $y2, $color, $width, $style = [])
 
 
 
 
 
 
 
 
 
 
 
 
 
876
  {
877
+ $this->processPageScript(function () use ($x1, $y1, $x2, $y2, $color, $width, $style) {
878
+ $this->line($x1, $y1, $x2, $y2, $color, $width, $style);
879
+ });
 
 
 
 
 
 
880
  }
881
 
882
  /**
892
  return $ret;
893
  }
894
 
895
+ protected function processPageScript(callable $callback): void
 
 
 
896
  {
897
+ $pageNumber = 1;
 
 
 
 
 
898
 
899
  foreach ($this->_pages as $pid) {
900
  $this->reopen_object($pid);
901
 
902
+ $fontMetrics = $this->_dompdf->getFontMetrics();
903
+ $callback($pageNumber, $this->_page_count, $this, $fontMetrics);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
904
 
905
  $this->close_object();
906
+ $pageNumber++;
907
  }
908
  }
909
 
 
 
 
 
 
 
910
  public function stream($filename = "document.pdf", $options = [])
911
  {
912
  if (headers_sent()) {
916
  if (!isset($options["compress"])) $options["compress"] = true;
917
  if (!isset($options["Attachment"])) $options["Attachment"] = true;
918
 
 
 
919
  $debug = !$options['compress'];
920
  $tmp = ltrim($this->_pdf->output($debug));
921
 
931
  flush();
932
  }
933
 
 
 
 
 
 
 
934
  public function output($options = [])
935
  {
936
  if (!isset($options["compress"])) $options["compress"] = true;
937
 
 
 
938
  $debug = !$options['compress'];
939
 
940
  return $this->_pdf->output($debug);
vendor/dompdf/dompdf/src/Adapter/GD.php CHANGED
@@ -10,8 +10,8 @@ namespace Dompdf\Adapter;
10
 
11
  use Dompdf\Canvas;
12
  use Dompdf\Dompdf;
13
- use Dompdf\Image\Cache;
14
  use Dompdf\Helpers;
 
15
 
16
  /**
17
  * Image rendering interface
@@ -130,29 +130,24 @@ class GD implements Canvas
130
  const FONT_SCALE = 0.75;
131
 
132
  /**
133
- * Class constructor
134
- *
135
- * @param mixed $size The size of image to create: array(x1,y1,x2,y2) or "letter", "legal", etc.
136
- * @param string $orientation The orientation of the document (either 'landscape' or 'portrait')
137
- * @param Dompdf $dompdf
138
- * @param float $aa_factor Anti-aliasing factor, 1 for no AA
139
- * @param array $bg_color Image background color: array(r,g,b,a), 0 <= r,g,b,a <= 1
140
  */
141
- public function __construct($size = 'letter', $orientation = "portrait", Dompdf $dompdf = null, $aa_factor = 1.0, $bg_color = [1, 1, 1, 0])
142
  {
143
-
144
- if (!is_array($size)) {
145
- $size = strtolower($size);
146
-
147
- if (isset(CPDF::$PAPER_SIZES[$size])) {
148
- $size = CPDF::$PAPER_SIZES[$size];
149
- } else {
150
- $size = CPDF::$PAPER_SIZES["letter"];
151
- }
152
  }
153
 
154
  if (strtolower($orientation) === "landscape") {
155
- list($size[2], $size[3]) = [$size[3], $size[2]];
156
  }
157
 
158
  if ($dompdf === null) {
@@ -190,9 +185,6 @@ class GD implements Canvas
190
  $this->new_page();
191
  }
192
 
193
- /**
194
- * @return Dompdf
195
- */
196
  public function get_dompdf()
197
  {
198
  return $this->_dompdf;
@@ -228,21 +220,11 @@ class GD implements Canvas
228
  return round($this->_height / $this->_aa_factor);
229
  }
230
 
231
- /**
232
- * Returns the current page number
233
- *
234
- * @return int
235
- */
236
  public function get_page_number()
237
  {
238
  return $this->_page_number;
239
  }
240
 
241
- /**
242
- * Returns the total number of pages in the document
243
- *
244
- * @return int
245
- */
246
  public function get_page_count()
247
  {
248
  return $this->_page_count;
@@ -258,23 +240,12 @@ class GD implements Canvas
258
  $this->_page_number = $num;
259
  }
260
 
261
- /**
262
- * Sets the page count
263
- *
264
- * @param int $count
265
- */
266
  public function set_page_count($count)
267
  {
268
  $this->_page_count = $count;
269
  }
270
 
271
- /**
272
- * Sets the opacity
273
- *
274
- * @param $opacity
275
- * @param $mode
276
- */
277
- public function set_opacity($opacity, $mode = "Normal")
278
  {
279
  // FIXME
280
  }
@@ -284,7 +255,7 @@ class GD implements Canvas
284
  * previously allocated colors in $this->_colors.
285
  *
286
  * @param array $color The new current color
287
- * @return int The allocated color
288
  */
289
  protected function _allocate_color($color)
290
  {
@@ -342,30 +313,61 @@ class GD implements Canvas
342
  * Scales value down from the current canvas DPI to 72 DPI
343
  *
344
  * @param float $length
345
- * @return int
346
  */
347
  protected function _downscale($length)
348
  {
349
  return round(($length / $this->dpi * 72) / $this->_aa_factor);
350
  }
351
 
352
- /**
353
- * Draws a line from x1,y1 to x2,y2
354
- *
355
- * See {@link Style::munge_color()} for the format of the color array.
356
- * See {@link Cpdf::setLineStyle()} for a description of the format of the
357
- * $style parameter (aka dash).
358
- *
359
- * @param float $x1
360
- * @param float $y1
361
- * @param float $x2
362
- * @param float $y2
363
- * @param array $color
364
- * @param float $width
365
- * @param array $style
366
- */
367
- public function line($x1, $y1, $x2, $y2, $color, $width, $style = null)
 
 
 
 
 
 
 
 
368
  {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
369
 
370
  // Scale by the AA factor and DPI
371
  $x1 = $this->_upscale($x1);
@@ -378,34 +380,7 @@ class GD implements Canvas
378
 
379
  // Convert the style array if required
380
  if (is_array($style) && count($style) > 0) {
381
- $gd_style = [];
382
-
383
- if (count($style) == 1) {
384
- for ($i = 0; $i < $style[0] * $this->_aa_factor; $i++) {
385
- $gd_style[] = $c;
386
- }
387
-
388
- for ($i = 0; $i < $style[0] * $this->_aa_factor; $i++) {
389
- $gd_style[] = $this->_bg_color;
390
- }
391
- } else {
392
- $i = 0;
393
- foreach ($style as $length) {
394
- if ($i % 2 == 0) {
395
- // 'On' pattern
396
- for ($i = 0; $i < $style[0] * $this->_aa_factor; $i++) {
397
- $gd_style[] = $c;
398
- }
399
-
400
- } else {
401
- // Off pattern
402
- for ($i = 0; $i < $style[0] * $this->_aa_factor; $i++) {
403
- $gd_style[] = $this->_bg_color;
404
- }
405
- }
406
- $i++;
407
- }
408
- }
409
 
410
  if (!empty($gd_style)) {
411
  imagesetstyle($this->get_image(), $gd_style);
@@ -418,39 +393,59 @@ class GD implements Canvas
418
  imageline($this->get_image(), $x1, $y1, $x2, $y2, $c);
419
  }
420
 
421
- /**
422
- * @param float $x1
423
- * @param float $y1
424
- * @param float $r1
425
- * @param float $r2
426
- * @param float $astart
427
- * @param float $aend
428
- * @param array $color
429
- * @param float $width
430
- * @param array $style
431
- */
432
- public function arc($x1, $y1, $r1, $r2, $astart, $aend, $color, $width, $style = [])
433
  {
434
- // @todo
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
435
  }
436
 
437
- /**
438
- * Draws a rectangle at x1,y1 with width w and height h
439
- *
440
- * See {@link Style::munge_color()} for the format of the color array.
441
- * See {@link Cpdf::setLineStyle()} for a description of the $style
442
- * parameter (aka dash)
443
- *
444
- * @param float $x1
445
- * @param float $y1
446
- * @param float $w
447
- * @param float $h
448
- * @param array $color
449
- * @param float $width
450
- * @param array $style
451
- */
452
- public function rectangle($x1, $y1, $w, $h, $color, $width, $style = null)
453
  {
 
 
 
 
 
 
 
 
 
 
454
 
455
  // Scale by the AA factor and DPI
456
  $x1 = $this->_upscale($x1);
@@ -463,13 +458,7 @@ class GD implements Canvas
463
 
464
  // Convert the style array if required
465
  if (is_array($style) && count($style) > 0) {
466
- $gd_style = [];
467
-
468
- foreach ($style as $length) {
469
- for ($i = 0; $i < $length; $i++) {
470
- $gd_style[] = $c;
471
- }
472
- }
473
 
474
  if (!empty($gd_style)) {
475
  imagesetstyle($this->get_image(), $gd_style);
@@ -479,20 +468,18 @@ class GD implements Canvas
479
 
480
  imagesetthickness($this->get_image(), $width);
481
 
482
- imagerectangle($this->get_image(), $x1, $y1, $x1 + $w, $y1 + $h, $c);
 
 
 
 
 
 
 
 
 
483
  }
484
 
485
- /**
486
- * Draws a filled rectangle at x1,y1 with width w and height h
487
- *
488
- * See {@link Style::munge_color()} for the format of the color array.
489
- *
490
- * @param float $x1
491
- * @param float $y1
492
- * @param float $w
493
- * @param float $h
494
- * @param array $color
495
- */
496
  public function filled_rectangle($x1, $y1, $w, $h, $color)
497
  {
498
  // Scale by the AA factor and DPI
@@ -506,14 +493,6 @@ class GD implements Canvas
506
  imagefilledrectangle($this->get_image(), $x1, $y1, $x1 + $w, $y1 + $h, $c);
507
  }
508
 
509
- /**
510
- * Starts a clipping rectangle at x1,y1 with width w and height h
511
- *
512
- * @param float $x1
513
- * @param float $y1
514
- * @param float $w
515
- * @param float $h
516
- */
517
  public function clipping_rectangle($x1, $y1, $w, $h)
518
  {
519
  // @todo
@@ -524,127 +503,65 @@ class GD implements Canvas
524
  // @todo
525
  }
526
 
527
- /**
528
- * Ends the last clipping shape
529
- */
 
 
530
  public function clipping_end()
531
  {
532
  // @todo
533
  }
534
 
535
- /**
536
- *
537
- */
538
  public function save()
539
  {
540
  $this->get_dompdf()->getOptions()->setDpi(72);
541
  }
542
 
543
- /**
544
- *
545
- */
546
  public function restore()
547
  {
548
  $this->get_dompdf()->getOptions()->setDpi($this->dpi);
549
  }
550
 
551
- /**
552
- * @param $angle
553
- * @param $x
554
- * @param $y
555
- */
556
  public function rotate($angle, $x, $y)
557
  {
558
  // @todo
559
  }
560
 
561
- /**
562
- * @param $angle_x
563
- * @param $angle_y
564
- * @param $x
565
- * @param $y
566
- */
567
  public function skew($angle_x, $angle_y, $x, $y)
568
  {
569
  // @todo
570
  }
571
 
572
- /**
573
- * @param $s_x
574
- * @param $s_y
575
- * @param $x
576
- * @param $y
577
- */
578
  public function scale($s_x, $s_y, $x, $y)
579
  {
580
  // @todo
581
  }
582
 
583
- /**
584
- * @param $t_x
585
- * @param $t_y
586
- */
587
  public function translate($t_x, $t_y)
588
  {
589
  // @todo
590
  }
591
 
592
- /**
593
- * @param $a
594
- * @param $b
595
- * @param $c
596
- * @param $d
597
- * @param $e
598
- * @param $f
599
- */
600
  public function transform($a, $b, $c, $d, $e, $f)
601
  {
602
  // @todo
603
  }
604
 
605
- /**
606
- * Draws a polygon
607
- *
608
- * The polygon is formed by joining all the points stored in the $points
609
- * array. $points has the following structure:
610
- * <code>
611
- * array(0 => x1,
612
- * 1 => y1,
613
- * 2 => x2,
614
- * 3 => y2,
615
- * ...
616
- * );
617
- * </code>
618
- *
619
- * See {@link Style::munge_color()} for the format of the color array.
620
- * See {@link Cpdf::setLineStyle()} for a description of the $style
621
- * parameter (aka dash)
622
- *
623
- * @param array $points
624
- * @param array $color
625
- * @param float $width
626
- * @param array $style
627
- * @param bool $fill Fills the polygon if true
628
- */
629
- public function polygon($points, $color, $width = null, $style = null, $fill = false)
630
  {
631
-
632
  // Scale each point by the AA factor and DPI
633
  foreach (array_keys($points) as $i) {
634
  $points[$i] = $this->_upscale($points[$i]);
635
  }
636
 
 
 
637
  $c = $this->_allocate_color($color);
638
 
639
  // Convert the style array if required
640
- if (is_array($style) && count($style) > 0 && !$fill) {
641
- $gd_style = [];
642
-
643
- foreach ($style as $length) {
644
- for ($i = 0; $i < $length; $i++) {
645
- $gd_style[] = $c;
646
- }
647
- }
648
 
649
  if (!empty($gd_style)) {
650
  imagesetstyle($this->get_image(), $gd_style);
@@ -652,7 +569,7 @@ class GD implements Canvas
652
  }
653
  }
654
 
655
- imagesetthickness($this->get_image(), isset($width) ? round($width) : 0);
656
 
657
  if ($fill) {
658
  imagefilledpolygon($this->get_image(), $points, $c);
@@ -661,39 +578,19 @@ class GD implements Canvas
661
  }
662
  }
663
 
664
- /**
665
- * Draws a circle at $x,$y with radius $r
666
- *
667
- * See {@link Style::munge_color()} for the format of the color array.
668
- * See {@link Cpdf::setLineStyle()} for a description of the $style
669
- * parameter (aka dash)
670
- *
671
- * @param float $x
672
- * @param float $y
673
- * @param float $r
674
- * @param array $color
675
- * @param float $width
676
- * @param array $style
677
- * @param bool $fill Fills the circle if true
678
- */
679
- public function circle($x, $y, $r, $color, $width = null, $style = null, $fill = false)
680
  {
681
  // Scale by the AA factor and DPI
682
  $x = $this->_upscale($x);
683
  $y = $this->_upscale($y);
684
  $d = $this->_upscale(2 * $r);
 
685
 
686
  $c = $this->_allocate_color($color);
687
 
688
  // Convert the style array if required
689
- if (is_array($style) && count($style) > 0 && !$fill) {
690
- $gd_style = [];
691
-
692
- foreach ($style as $length) {
693
- for ($i = 0; $i < $length; $i++) {
694
- $gd_style[] = $c;
695
- }
696
- }
697
 
698
  if (!empty($gd_style)) {
699
  imagesetstyle($this->get_image(), $gd_style);
@@ -701,7 +598,7 @@ class GD implements Canvas
701
  }
702
  }
703
 
704
- imagesetthickness($this->get_image(), isset($width) ? round($width) : 0);
705
 
706
  if ($fill) {
707
  imagefilledellipse($this->get_image(), $x, $y, $d, $d, $c);
@@ -711,24 +608,11 @@ class GD implements Canvas
711
  }
712
 
713
  /**
714
- * Add an image to the pdf.
715
- * The image is placed at the specified x and y coordinates with the
716
- * given width and height.
717
- *
718
- * @param string $img_url the path to the image
719
- * @param float $x x position
720
- * @param float $y y position
721
- * @param int $w width (in pixels)
722
- * @param int $h height (in pixels)
723
- * @param string $resolution
724
- * @return void
725
- *
726
  * @throws \Exception
727
- * @internal param string $img_type the type (e.g. extension) of the image
728
  */
729
- public function image($img_url, $x, $y, $w, $h, $resolution = "normal")
730
  {
731
- $img_type = Cache::detect_type($img_url, $this->get_dompdf()->getHttpContext());
732
 
733
  if (!$img_type) {
734
  return;
@@ -737,11 +621,11 @@ class GD implements Canvas
737
  $func_name = "imagecreatefrom$img_type";
738
  if (!function_exists($func_name)) {
739
  if (!method_exists(Helpers::class, $func_name)) {
740
- throw new \Exception("Function $func_name() not found. Cannot convert $img_type image: $img_url. Please install the image PHP extension.");
741
  }
742
  $func_name = [Helpers::class, $func_name];
743
  }
744
- $src = @call_user_func($func_name, $img_url);
745
 
746
  if (!$src) {
747
  return; // Probably should add to $_dompdf_errors or whatever here
@@ -760,22 +644,6 @@ class GD implements Canvas
760
  imagecopyresampled($this->get_image(), $src, $x, $y, 0, 0, $w, $h, $img_w, $img_h);
761
  }
762
 
763
- /**
764
- * Writes text at the specified x and y coordinates
765
- * See {@link Style::munge_color()} for the format of the color array.
766
- *
767
- * @param float $x
768
- * @param float $y
769
- * @param string $text the text to write
770
- * @param string $font the font file to use
771
- * @param float $size the font size, in points
772
- * @param array $color
773
- * @param float $word_spacing word spacing adjustment
774
- * @param float $char_spacing
775
- * @param float $angle Text angle
776
- *
777
- * @return void
778
- */
779
  public function text($x, $y, $text, $font, $size, $color = [0, 0, 0], $word_spacing = 0.0, $char_spacing = 0.0, $angle = 0.0)
780
  {
781
  // Scale by the AA factor and DPI
@@ -805,61 +673,26 @@ class GD implements Canvas
805
  // Not implemented
806
  }
807
 
808
- /**
809
- * Add a named destination (similar to <a name="foo">...</a> in html)
810
- *
811
- * @param string $anchorname The name of the named destination
812
- */
813
  public function add_named_dest($anchorname)
814
  {
815
  // Not implemented
816
  }
817
 
818
- /**
819
- * Add a link to the pdf
820
- *
821
- * @param string $url The url to link to
822
- * @param float $x The x position of the link
823
- * @param float $y The y position of the link
824
- * @param float $width The width of the link
825
- * @param float $height The height of the link
826
- */
827
  public function add_link($url, $x, $y, $width, $height)
828
  {
829
  // Not implemented
830
  }
831
 
832
- /**
833
- * Add meta information to the PDF
834
- *
835
- * @param string $label label of the value (Creator, Producer, etc.)
836
- * @param string $value the text to set
837
- */
838
- public function add_info($label, $value)
839
  {
840
  // N/A
841
  }
842
 
843
- /**
844
- * @param string $view
845
- * @param array $options
846
- */
847
  public function set_default_view($view, $options = [])
848
  {
849
  // N/A
850
  }
851
 
852
- /**
853
- * Calculates text size, in points
854
- *
855
- * @param string $text the text to be sized
856
- * @param string $font the desired font
857
- * @param float $size the desired font size
858
- * @param float $word_spacing word spacing, if any
859
- * @param float $char_spacing char spacing, if any
860
- *
861
- * @return float
862
- */
863
  public function get_text_width($text, $font, $size, $word_spacing = 0.0, $char_spacing = 0.0)
864
  {
865
  $font = $this->get_ttf_file($font);
@@ -913,13 +746,6 @@ class GD implements Canvas
913
  return $font;
914
  }
915
 
916
- /**
917
- * Calculates font height, in points
918
- *
919
- * @param string $font
920
- * @param float $size
921
- * @return int
922
- */
923
  public function get_font_height($font, $size)
924
  {
925
  $size = $this->_upscale($size) * self::FONT_SCALE;
@@ -929,6 +755,12 @@ class GD implements Canvas
929
  return $this->_downscale($height);
930
  }
931
 
 
 
 
 
 
 
932
  protected function get_font_height_actual($font, $size)
933
  {
934
  $font = $this->get_ttf_file($font);
@@ -939,22 +771,12 @@ class GD implements Canvas
939
  return ($y2 - $y1) * $ratio;
940
  }
941
 
942
- /**
943
- * @param string $font
944
- * @param float $size
945
- * @return float
946
- */
947
  public function get_font_baseline($font, $size)
948
  {
949
  $ratio = $this->_dompdf->getOptions()->getFontHeightRatio();
950
  return $this->get_font_height($font, $size) / $ratio;
951
  }
952
 
953
- /**
954
- * Starts a new page
955
- *
956
- * Subsequent drawing operations will appear on the new page.
957
- */
958
  public function new_page()
959
  {
960
  $this->_page_number++;
@@ -985,7 +807,7 @@ class GD implements Canvas
985
  // N/A
986
  }
987
 
988
- public function page_script($callback)
989
  {
990
  // N/A
991
  }
@@ -995,7 +817,7 @@ class GD implements Canvas
995
  // N/A
996
  }
997
 
998
- public function page_line()
999
  {
1000
  // N/A
1001
  }
@@ -1004,7 +826,7 @@ class GD implements Canvas
1004
  * Streams the image to the client.
1005
  *
1006
  * @param string $filename The filename to present to the client.
1007
- * @param array $options Associative array: 'type' => jpeg|jpg|png; 'quality' => 0 - 100 (JPEG only);
1008
  * 'page' => Number of the page to output (defaults to the first); 'Attachment': 1 or 0 (default 1).
1009
  */
1010
  public function stream($filename, $options = [])
10
 
11
  use Dompdf\Canvas;
12
  use Dompdf\Dompdf;
 
13
  use Dompdf\Helpers;
14
+ use Dompdf\Image\Cache;
15
 
16
  /**
17
  * Image rendering interface
130
  const FONT_SCALE = 0.75;
131
 
132
  /**
133
+ * @param string|float[] $paper The paper size to use as either a standard paper size (see {@link CPDF::$PAPER_SIZES}) or
134
+ * an array of the form `[x1, y1, x2, y2]` (typically `[0, 0, width, height]`).
135
+ * @param string $orientation The paper orientation, either `portrait` or `landscape`.
136
+ * @param Dompdf $dompdf The Dompdf instance.
137
+ * @param float $aa_factor Anti-aliasing factor, 1 for no AA
138
+ * @param array $bg_color Image background color: array(r,g,b,a), 0 <= r,g,b,a <= 1
 
139
  */
140
+ public function __construct($paper = "letter", $orientation = "portrait", ?Dompdf $dompdf = null, $aa_factor = 1.0, $bg_color = [1, 1, 1, 0])
141
  {
142
+ if (is_array($paper)) {
143
+ $size = array_map("floatval", $paper);
144
+ } else {
145
+ $paper = strtolower($paper);
146
+ $size = CPDF::$PAPER_SIZES[$paper] ?? CPDF::$PAPER_SIZES["letter"];
 
 
 
 
147
  }
148
 
149
  if (strtolower($orientation) === "landscape") {
150
+ [$size[2], $size[3]] = [$size[3], $size[2]];
151
  }
152
 
153
  if ($dompdf === null) {
185
  $this->new_page();
186
  }
187
 
 
 
 
188
  public function get_dompdf()
189
  {
190
  return $this->_dompdf;
220
  return round($this->_height / $this->_aa_factor);
221
  }
222
 
 
 
 
 
 
223
  public function get_page_number()
224
  {
225
  return $this->_page_number;
226
  }
227
 
 
 
 
 
 
228
  public function get_page_count()
229
  {
230
  return $this->_page_count;
240
  $this->_page_number = $num;
241
  }
242
 
 
 
 
 
 
243
  public function set_page_count($count)
244
  {
245
  $this->_page_count = $count;
246
  }
247
 
248
+ public function set_opacity(float $opacity, string $mode = "Normal"): void
 
 
 
 
 
 
249
  {
250
  // FIXME
251
  }
255
  * previously allocated colors in $this->_colors.
256
  *
257
  * @param array $color The new current color
258
+ * @return int The allocated color
259
  */
260
  protected function _allocate_color($color)
261
  {
313
  * Scales value down from the current canvas DPI to 72 DPI
314
  *
315
  * @param float $length
316
+ * @return float
317
  */
318
  protected function _downscale($length)
319
  {
320
  return round(($length / $this->dpi * 72) / $this->_aa_factor);
321
  }
322
 
323
+ protected function convertStyle(array $style, int $color, int $width): array
324
+ {
325
+ $gdStyle = [];
326
+
327
+ if (count($style) === 1) {
328
+ $style[] = $style[0];
329
+ }
330
+
331
+ foreach ($style as $index => $s) {
332
+ $d = $this->_upscale($s);
333
+
334
+ for ($i = 0; $i < $d; $i++) {
335
+ for ($j = 0; $j < $width; $j++) {
336
+ $gdStyle[] = $index % 2 === 0
337
+ ? $color
338
+ : IMG_COLOR_TRANSPARENT;
339
+ }
340
+ }
341
+ }
342
+
343
+ return $gdStyle;
344
+ }
345
+
346
+ public function line($x1, $y1, $x2, $y2, $color, $width, $style = [], $cap = "butt")
347
  {
348
+ // Account for the fact that round and square caps are expected to
349
+ // extend outwards
350
+ if ($cap === "round" || $cap === "square") {
351
+ // Shift line by half width
352
+ $w = $width / 2;
353
+ $a = $x2 - $x1;
354
+ $b = $y2 - $y1;
355
+ $c = sqrt($a ** 2 + $b ** 2);
356
+ $dx = $a * $w / $c;
357
+ $dy = $b * $w / $c;
358
+
359
+ $x1 -= $dx;
360
+ $x2 -= $dx;
361
+ $y1 -= $dy;
362
+ $y2 -= $dy;
363
+
364
+ // Adapt dash pattern
365
+ if (is_array($style)) {
366
+ foreach ($style as $index => &$s) {
367
+ $s = $index % 2 === 0 ? $s + $width : $s - $width;
368
+ }
369
+ }
370
+ }
371
 
372
  // Scale by the AA factor and DPI
373
  $x1 = $this->_upscale($x1);
380
 
381
  // Convert the style array if required
382
  if (is_array($style) && count($style) > 0) {
383
+ $gd_style = $this->convertStyle($style, $c, $width);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
384
 
385
  if (!empty($gd_style)) {
386
  imagesetstyle($this->get_image(), $gd_style);
393
  imageline($this->get_image(), $x1, $y1, $x2, $y2, $c);
394
  }
395
 
396
+ public function arc($x, $y, $r1, $r2, $astart, $aend, $color, $width, $style = [], $cap = "butt")
 
 
 
 
 
 
 
 
 
 
 
397
  {
398
+ // Account for the fact that round and square caps are expected to
399
+ // extend outwards
400
+ if ($cap === "round" || $cap === "square") {
401
+ // Adapt dash pattern
402
+ if (is_array($style)) {
403
+ foreach ($style as $index => &$s) {
404
+ $s = $index % 2 === 0 ? $s + $width : $s - $width;
405
+ }
406
+ }
407
+ }
408
+
409
+ // Scale by the AA factor and DPI
410
+ $x = $this->_upscale($x);
411
+ $y = $this->_upscale($y);
412
+ $w = $this->_upscale($r1 * 2);
413
+ $h = $this->_upscale($r2 * 2);
414
+ $width = $this->_upscale($width);
415
+
416
+ // Adapt angles as imagearc counts clockwise
417
+ $start = 360 - $aend;
418
+ $end = 360 - $astart;
419
+
420
+ $c = $this->_allocate_color($color);
421
+
422
+ // Convert the style array if required
423
+ if (is_array($style) && count($style) > 0) {
424
+ $gd_style = $this->convertStyle($style, $c, $width);
425
+
426
+ if (!empty($gd_style)) {
427
+ imagesetstyle($this->get_image(), $gd_style);
428
+ $c = IMG_COLOR_STYLED;
429
+ }
430
+ }
431
+
432
+ imagesetthickness($this->get_image(), $width);
433
+
434
+ imagearc($this->get_image(), $x, $y, $w, $h, $start, $end, $c);
435
  }
436
 
437
+ public function rectangle($x1, $y1, $w, $h, $color, $width, $style = [], $cap = "butt")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
438
  {
439
+ // Account for the fact that round and square caps are expected to
440
+ // extend outwards
441
+ if ($cap === "round" || $cap === "square") {
442
+ // Adapt dash pattern
443
+ if (is_array($style)) {
444
+ foreach ($style as $index => &$s) {
445
+ $s = $index % 2 === 0 ? $s + $width : $s - $width;
446
+ }
447
+ }
448
+ }
449
 
450
  // Scale by the AA factor and DPI
451
  $x1 = $this->_upscale($x1);
458
 
459
  // Convert the style array if required
460
  if (is_array($style) && count($style) > 0) {
461
+ $gd_style = $this->convertStyle($style, $c, $width);
 
 
 
 
 
 
462
 
463
  if (!empty($gd_style)) {
464
  imagesetstyle($this->get_image(), $gd_style);
468
 
469
  imagesetthickness($this->get_image(), $width);
470
 
471
+ if ($c === IMG_COLOR_STYLED) {
472
+ imagepolygon($this->get_image(), [
473
+ $x1, $y1,
474
+ $x1 + $w, $y1,
475
+ $x1 + $w, $y1 + $h,
476
+ $x1, $y1 + $h
477
+ ], $c);
478
+ } else {
479
+ imagerectangle($this->get_image(), $x1, $y1, $x1 + $w, $y1 + $h, $c);
480
+ }
481
  }
482
 
 
 
 
 
 
 
 
 
 
 
 
483
  public function filled_rectangle($x1, $y1, $w, $h, $color)
484
  {
485
  // Scale by the AA factor and DPI
493
  imagefilledrectangle($this->get_image(), $x1, $y1, $x1 + $w, $y1 + $h, $c);
494
  }
495
 
 
 
 
 
 
 
 
 
496
  public function clipping_rectangle($x1, $y1, $w, $h)
497
  {
498
  // @todo
503
  // @todo
504
  }
505
 
506
+ public function clipping_polygon(array $points): void
507
+ {
508
+ // @todo
509
+ }
510
+
511
  public function clipping_end()
512
  {
513
  // @todo
514
  }
515
 
 
 
 
516
  public function save()
517
  {
518
  $this->get_dompdf()->getOptions()->setDpi(72);
519
  }
520
 
 
 
 
521
  public function restore()
522
  {
523
  $this->get_dompdf()->getOptions()->setDpi($this->dpi);
524
  }
525
 
 
 
 
 
 
526
  public function rotate($angle, $x, $y)
527
  {
528
  // @todo
529
  }
530
 
 
 
 
 
 
 
531
  public function skew($angle_x, $angle_y, $x, $y)
532
  {
533
  // @todo
534
  }
535
 
 
 
 
 
 
 
536
  public function scale($s_x, $s_y, $x, $y)
537
  {
538
  // @todo
539
  }
540
 
 
 
 
 
541
  public function translate($t_x, $t_y)
542
  {
543
  // @todo
544
  }
545
 
 
 
 
 
 
 
 
 
546
  public function transform($a, $b, $c, $d, $e, $f)
547
  {
548
  // @todo
549
  }
550
 
551
+ public function polygon($points, $color, $width = null, $style = [], $fill = false)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
552
  {
 
553
  // Scale each point by the AA factor and DPI
554
  foreach (array_keys($points) as $i) {
555
  $points[$i] = $this->_upscale($points[$i]);
556
  }
557
 
558
+ $width = isset($width) ? $this->_upscale($width) : null;
559
+
560
  $c = $this->_allocate_color($color);
561
 
562
  // Convert the style array if required
563
+ if (is_array($style) && count($style) > 0 && isset($width) && !$fill) {
564
+ $gd_style = $this->convertStyle($style, $c, $width);
 
 
 
 
 
 
565
 
566
  if (!empty($gd_style)) {
567
  imagesetstyle($this->get_image(), $gd_style);
569
  }
570
  }
571
 
572
+ imagesetthickness($this->get_image(), isset($width) ? $width : 0);
573
 
574
  if ($fill) {
575
  imagefilledpolygon($this->get_image(), $points, $c);
578
  }
579
  }
580
 
581
+ public function circle($x, $y, $r, $color, $width = null, $style = [], $fill = false)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
582
  {
583
  // Scale by the AA factor and DPI
584
  $x = $this->_upscale($x);
585
  $y = $this->_upscale($y);
586
  $d = $this->_upscale(2 * $r);
587
+ $width = isset($width) ? $this->_upscale($width) : null;
588
 
589
  $c = $this->_allocate_color($color);
590
 
591
  // Convert the style array if required
592
+ if (is_array($style) && count($style) > 0 && isset($width) && !$fill) {
593
+ $gd_style = $this->convertStyle($style, $c, $width);
 
 
 
 
 
 
594
 
595
  if (!empty($gd_style)) {
596
  imagesetstyle($this->get_image(), $gd_style);
598
  }
599
  }
600
 
601
+ imagesetthickness($this->get_image(), isset($width) ? $width : 0);
602
 
603
  if ($fill) {
604
  imagefilledellipse($this->get_image(), $x, $y, $d, $d, $c);
608
  }
609
 
610
  /**
 
 
 
 
 
 
 
 
 
 
 
 
611
  * @throws \Exception
 
612
  */
613
+ public function image($img, $x, $y, $w, $h, $resolution = "normal")
614
  {
615
+ $img_type = Cache::detect_type($img, $this->get_dompdf()->getHttpContext());
616
 
617
  if (!$img_type) {
618
  return;
621
  $func_name = "imagecreatefrom$img_type";
622
  if (!function_exists($func_name)) {
623
  if (!method_exists(Helpers::class, $func_name)) {
624
+ throw new \Exception("Function $func_name() not found. Cannot convert $img_type image: $img. Please install the image PHP extension.");
625
  }
626
  $func_name = [Helpers::class, $func_name];
627
  }
628
+ $src = @call_user_func($func_name, $img);
629
 
630
  if (!$src) {
631
  return; // Probably should add to $_dompdf_errors or whatever here
644
  imagecopyresampled($this->get_image(), $src, $x, $y, 0, 0, $w, $h, $img_w, $img_h);
645
  }
646
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
647
  public function text($x, $y, $text, $font, $size, $color = [0, 0, 0], $word_spacing = 0.0, $char_spacing = 0.0, $angle = 0.0)
648
  {
649
  // Scale by the AA factor and DPI
673
  // Not implemented
674
  }
675
 
 
 
 
 
 
676
  public function add_named_dest($anchorname)
677
  {
678
  // Not implemented
679
  }
680
 
 
 
 
 
 
 
 
 
 
681
  public function add_link($url, $x, $y, $width, $height)
682
  {
683
  // Not implemented
684
  }
685
 
686
+ public function add_info(string $label, string $value): void
 
 
 
 
 
 
687
  {
688
  // N/A
689
  }
690
 
 
 
 
 
691
  public function set_default_view($view, $options = [])
692
  {
693
  // N/A
694
  }
695
 
 
 
 
 
 
 
 
 
 
 
 
696
  public function get_text_width($text, $font, $size, $word_spacing = 0.0, $char_spacing = 0.0)
697
  {
698
  $font = $this->get_ttf_file($font);
746
  return $font;
747
  }
748
 
 
 
 
 
 
 
 
749
  public function get_font_height($font, $size)
750
  {
751
  $size = $this->_upscale($size) * self::FONT_SCALE;
755
  return $this->_downscale($height);
756
  }
757
 
758
+ /**
759
+ * @param string $font
760
+ * @param float $size
761
+ *
762
+ * @return float
763
+ */
764
  protected function get_font_height_actual($font, $size)
765
  {
766
  $font = $this->get_ttf_file($font);
771
  return ($y2 - $y1) * $ratio;
772
  }
773
 
 
 
 
 
 
774
  public function get_font_baseline($font, $size)
775
  {
776
  $ratio = $this->_dompdf->getOptions()->getFontHeightRatio();
777
  return $this->get_font_height($font, $size) / $ratio;
778
  }
779
 
 
 
 
 
 
780
  public function new_page()
781
  {
782
  $this->_page_number++;
807
  // N/A
808
  }
809
 
810
+ public function page_script($callback): void
811
  {
812
  // N/A
813
  }
817
  // N/A
818
  }
819
 
820
+ public function page_line($x1, $y1, $x2, $y2, $color, $width, $style = [])
821
  {
822
  // N/A
823
  }
826
  * Streams the image to the client.
827
  *
828
  * @param string $filename The filename to present to the client.
829
+ * @param array $options Associative array: 'type' => jpeg|jpg|png; 'quality' => 0 - 100 (JPEG only);
830
  * 'page' => Number of the page to output (defaults to the first); 'Attachment': 1 or 0 (default 1).
831
  */
832
  public function stream($filename, $options = [])
vendor/dompdf/dompdf/src/Adapter/PDFLib.php CHANGED
@@ -11,10 +11,10 @@ namespace Dompdf\Adapter;
11
 
12
  use Dompdf\Canvas;
13
  use Dompdf\Dompdf;
14
- use Dompdf\Helpers;
15
  use Dompdf\Exception;
 
 
16
  use Dompdf\Image\Cache;
17
- use Dompdf\PhpEvaluator;
18
 
19
  /**
20
  * PDF rendering interface
@@ -37,7 +37,7 @@ class PDFLib implements Canvas
37
  /**
38
  * Dimensions of paper sizes in points
39
  *
40
- * @var array;
41
  */
42
  public static $PAPER_SIZES = []; // Set to Dompdf\Adapter\CPDF::$PAPER_SIZES below.
43
 
@@ -130,7 +130,7 @@ class PDFLib implements Canvas
130
  /**
131
  * The current opacity level
132
  *
133
- * @var array
134
  */
135
  protected $_current_opacity;
136
 
@@ -184,39 +184,23 @@ class PDFLib implements Canvas
184
  protected $_page_count;
185
 
186
  /**
187
- * Text to display on every page
188
- *
189
- * @var array
190
- */
191
- protected $_page_text;
192
-
193
- /**
194
- * Array of pages for accesing after rendering is initially complete
195
  *
196
  * @var array
197
  */
198
  protected $_pages;
199
 
200
- /**
201
- * Class constructor
202
- *
203
- * @param string|array $paper The size of paper to use either a string (see {@link Dompdf\Adapter\CPDF::$PAPER_SIZES}) or
204
- * an array(xmin,ymin,xmax,ymax)
205
- * @param string $orientation The orientation of the document (either 'landscape' or 'portrait')
206
- * @param Dompdf $dompdf
207
- */
208
- public function __construct($paper = "letter", $orientation = "portrait", Dompdf $dompdf = null)
209
  {
210
  if (is_array($paper)) {
211
- $size = $paper;
212
- } elseif (isset(self::$PAPER_SIZES[mb_strtolower($paper)])) {
213
- $size = self::$PAPER_SIZES[mb_strtolower($paper)];
214
  } else {
215
- $size = self::$PAPER_SIZES["letter"];
 
216
  }
217
 
218
- if (mb_strtolower($orientation) === "landscape") {
219
- list($size[2], $size[3]) = [$size[3], $size[2]];
220
  }
221
 
222
  $this->_width = $size[2] - $size[0];
@@ -235,7 +219,9 @@ class PDFLib implements Canvas
235
  $this->setPDFLibParameter("license", $license);
236
  }
237
 
238
- $this->setPDFLibParameter("textformat", "utf8");
 
 
239
  if ($this->getPDFLibMajorVersion() >= 7) {
240
  $this->setPDFLibParameter("errorpolicy", "return");
241
  // $this->_pdf->set_option('logging={filename=' . \APP_PATH . '/logs/pdflib.log classes={api=1 warning=2}}');
@@ -271,16 +257,12 @@ class PDFLib implements Canvas
271
  $this->_pdf->begin_page_ext($this->_width, $this->_height, "");
272
 
273
  $this->_page_number = $this->_page_count = 1;
274
- $this->_page_text = [];
275
 
276
  $this->_imgs = [];
277
  $this->_fonts = [];
278
  $this->_objs = [];
279
  }
280
 
281
- /**
282
- * @return Dompdf
283
- */
284
  function get_dompdf()
285
  {
286
  return $this->_dompdf;
@@ -314,13 +296,7 @@ class PDFLib implements Canvas
314
  return $this->_pdf;
315
  }
316
 
317
- /**
318
- * Add meta information to the PDF
319
- *
320
- * @param string $label label of the value (Creator, Producter, etc.)
321
- * @param string $value the text to set
322
- */
323
- public function add_info($label, $value)
324
  {
325
  $this->_pdf->set_info($label, $value);
326
  }
@@ -464,33 +440,21 @@ class PDFLib implements Canvas
464
  }
465
  }
466
 
467
- /**
468
- * @return float|mixed
469
- */
470
  public function get_width()
471
  {
472
  return $this->_width;
473
  }
474
 
475
- /**
476
- * @return float|mixed
477
- */
478
  public function get_height()
479
  {
480
  return $this->_height;
481
  }
482
 
483
- /**
484
- * @return int
485
- */
486
  public function get_page_number()
487
  {
488
  return $this->_page_number;
489
  }
490
 
491
- /**
492
- * @return int
493
- */
494
  public function get_page_count()
495
  {
496
  return $this->_page_count;
@@ -504,9 +468,6 @@ class PDFLib implements Canvas
504
  $this->_page_number = (int)$num;
505
  }
506
 
507
- /**
508
- * @param int $count
509
- */
510
  public function set_page_count($count)
511
  {
512
  $this->_page_count = (int)$count;
@@ -516,19 +477,25 @@ class PDFLib implements Canvas
516
  * Sets the line style
517
  *
518
  * @param float $width
519
- * @param $cap
520
  * @param string $join
521
  * @param array $dash
522
- *
523
- * @return void
524
  */
525
  protected function _set_line_style($width, $cap, $join, $dash)
526
  {
527
  if (!is_array($dash)) {
528
- $dash = array();
 
 
 
 
 
 
 
 
529
  }
530
 
531
- if (count($dash) == 1) {
532
  $dash[] = $dash[0];
533
  }
534
 
@@ -684,12 +651,12 @@ class PDFLib implements Canvas
684
  /**
685
  * Sets the fill opacity
686
  *
687
- * @param $opacity
688
- * @param $mode
689
  */
690
  public function _set_fill_opacity($opacity, $mode = "Normal")
691
  {
692
- if ($mode === "Normal" && is_null($opacity) === false) {
693
  $this->_set_gstate("opacityfill=$opacity");
694
  }
695
  }
@@ -697,25 +664,19 @@ class PDFLib implements Canvas
697
  /**
698
  * Sets the stroke opacity
699
  *
700
- * @param $opacity
701
- * @param $mode
702
  */
703
  public function _set_stroke_opacity($opacity, $mode = "Normal")
704
  {
705
- if ($mode === "Normal" && is_null($opacity) === false) {
706
  $this->_set_gstate("opacitystroke=$opacity");
707
  }
708
  }
709
 
710
- /**
711
- * Sets the opacity
712
- *
713
- * @param $opacity
714
- * @param $mode
715
- */
716
- public function set_opacity($opacity, $mode = "Normal")
717
  {
718
- if ($mode === "Normal" && is_null($opacity) === false) {
719
  $this->_set_gstate("opacityfill=$opacity opacitystroke=$opacity");
720
  $this->_current_opacity = $opacity;
721
  }
@@ -874,18 +835,9 @@ class PDFLib implements Canvas
874
  return $this->_height - $y;
875
  }
876
 
877
- /**
878
- * @param float $x1
879
- * @param float $y1
880
- * @param float $x2
881
- * @param float $y2
882
- * @param array $color
883
- * @param float $width
884
- * @param array $style
885
- */
886
- public function line($x1, $y1, $x2, $y2, $color, $width, $style = null)
887
  {
888
- $this->_set_line_style($width, "butt", "", $style);
889
  $this->_set_stroke_color($color);
890
 
891
  $y1 = $this->y($y1);
@@ -898,62 +850,23 @@ class PDFLib implements Canvas
898
  $this->_set_stroke_opacity($this->_current_opacity, "Normal");
899
  }
900
 
901
- /**
902
- * Draw line at the specified coordinates on every page.
903
- *
904
- * See {@link Style::munge_color()} for the format of the colour array.
905
- *
906
- * @param float $x1
907
- * @param float $y1
908
- * @param float $x2
909
- * @param float $y2
910
- * @param array $color
911
- * @param float $width
912
- * @param array $style optional
913
- */
914
- public function page_line($x1, $y1, $x2, $y2, $color, $width, $style = [])
915
  {
916
- $_t = 'line';
917
- $this->_page_text[] = compact('_t', 'x1', 'y1', 'x2', 'y2', 'color', 'width', 'style');
918
- }
919
-
920
- /**
921
- * @param float $x1
922
- * @param float $y1
923
- * @param float $r1
924
- * @param float $r2
925
- * @param float $astart
926
- * @param float $aend
927
- * @param array $color
928
- * @param float $width
929
- * @param array $style
930
- */
931
- public function arc($x1, $y1, $r1, $r2, $astart, $aend, $color, $width, $style = [])
932
- {
933
- $this->_set_line_style($width, "butt", "", $style);
934
  $this->_set_stroke_color($color);
935
 
936
- $y1 = $this->y($y1);
937
 
938
- $this->_pdf->arc($x1, $y1, $r1, $astart, $aend);
939
  $this->_pdf->stroke();
940
 
941
  $this->_set_stroke_opacity($this->_current_opacity, "Normal");
942
  }
943
 
944
- /**
945
- * @param float $x1
946
- * @param float $y1
947
- * @param float $w
948
- * @param float $h
949
- * @param array $color
950
- * @param float $width
951
- * @param null $style
952
- */
953
- public function rectangle($x1, $y1, $w, $h, $color, $width, $style = null)
954
  {
955
  $this->_set_stroke_color($color);
956
- $this->_set_line_style($width, "butt", "", $style);
957
 
958
  $y1 = $this->y($y1) - $h;
959
 
@@ -963,13 +876,6 @@ class PDFLib implements Canvas
963
  $this->_set_stroke_opacity($this->_current_opacity, "Normal");
964
  }
965
 
966
- /**
967
- * @param float $x1
968
- * @param float $y1
969
- * @param float $w
970
- * @param float $h
971
- * @param array $color
972
- */
973
  public function filled_rectangle($x1, $y1, $w, $h, $color)
974
  {
975
  $this->_set_fill_color($color);
@@ -982,12 +888,6 @@ class PDFLib implements Canvas
982
  $this->_set_fill_opacity($this->_current_opacity, "Normal");
983
  }
984
 
985
- /**
986
- * @param float $x1
987
- * @param float $y1
988
- * @param float $w
989
- * @param float $h
990
- */
991
  public function clipping_rectangle($x1, $y1, $w, $h)
992
  {
993
  $this->_pdf->save();
@@ -998,16 +898,6 @@ class PDFLib implements Canvas
998
  $this->_pdf->clip();
999
  }
1000
 
1001
- /**
1002
- * @param float $x1
1003
- * @param float $y1
1004
- * @param float $w
1005
- * @param float $h
1006
- * @param float $rTL
1007
- * @param float $rTR
1008
- * @param float $rBR
1009
- * @param float $rBL
1010
- */
1011
  public function clipping_roundrectangle($x1, $y1, $w, $h, $rTL, $rTR, $rBR, $rBL)
1012
  {
1013
  if ($this->getPDFLibMajorVersion() < 9) {
@@ -1027,33 +917,53 @@ class PDFLib implements Canvas
1027
  // line: left edge, bottom end
1028
  $path = $this->_pdf->add_path_point($path, 0, 0 + $rBL, "line", "");
1029
  // curve: bottom-left corner
1030
- $path = $this->_pdf->add_path_point($path, 0 + $rBL, 0, "elliptical", "radius=$rBL clockwise=false");
 
 
1031
  // line: bottom edge, left end
1032
  $path = $this->_pdf->add_path_point($path, 0 - $rBR + $w, 0, "line", "");
1033
  // curve: bottom-right corner
1034
- $path = $this->_pdf->add_path_point($path, 0 + $w, 0 + $rBR, "elliptical", "radius=$rBR clockwise=false");
 
 
1035
  // line: right edge, top end
1036
  $path = $this->_pdf->add_path_point($path, 0 + $w, 0 - $rTR + $h, "line", "");
1037
  // curve: top-right corner
1038
- $path = $this->_pdf->add_path_point($path, 0 - $rTR + $w, 0 +$h, "elliptical", "radius=$rTR clockwise=false");
 
 
1039
  // line: top edge, left end
1040
  $path = $this->_pdf->add_path_point($path, 0 + $rTL, 0 + $h, "line", "");
1041
  // curve: top-left corner
1042
- $path = $this->_pdf->add_path_point($path, 0, 0 - $rTL + $h, "elliptical", "radius=$rTL clockwise=false");
 
 
1043
  $this->_pdf->draw_path($path, $x1, $this->_height-$y1-$h, "clip=true");
1044
  }
1045
 
1046
- /**
1047
- *
1048
- */
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1049
  public function clipping_end()
1050
  {
1051
  $this->_pdf->restore();
1052
  }
1053
 
1054
- /**
1055
- *
1056
- */
1057
  public function save()
1058
  {
1059
  $this->_pdf->save();
@@ -1064,11 +974,6 @@ class PDFLib implements Canvas
1064
  $this->_pdf->restore();
1065
  }
1066
 
1067
- /**
1068
- * @param $angle
1069
- * @param $x
1070
- * @param $y
1071
- */
1072
  public function rotate($angle, $x, $y)
1073
  {
1074
  $pdf = $this->_pdf;
@@ -1077,12 +982,6 @@ class PDFLib implements Canvas
1077
  $pdf->translate(-$x, -$this->_height + $y);
1078
  }
1079
 
1080
- /**
1081
- * @param $angle_x
1082
- * @param $angle_y
1083
- * @param $x
1084
- * @param $y
1085
- */
1086
  public function skew($angle_x, $angle_y, $x, $y)
1087
  {
1088
  $pdf = $this->_pdf;
@@ -1091,12 +990,6 @@ class PDFLib implements Canvas
1091
  $pdf->translate(-$x, -$this->_height + $y);
1092
  }
1093
 
1094
- /**
1095
- * @param $s_x
1096
- * @param $s_y
1097
- * @param $x
1098
- * @param $y
1099
- */
1100
  public function scale($s_x, $s_y, $x, $y)
1101
  {
1102
  $pdf = $this->_pdf;
@@ -1105,36 +998,17 @@ class PDFLib implements Canvas
1105
  $pdf->translate(-$x, -$this->_height + $y);
1106
  }
1107
 
1108
- /**
1109
- * @param $t_x
1110
- * @param $t_y
1111
- */
1112
  public function translate($t_x, $t_y)
1113
  {
1114
  $this->_pdf->translate($t_x, -$t_y);
1115
  }
1116
 
1117
- /**
1118
- * @param $a
1119
- * @param $b
1120
- * @param $c
1121
- * @param $d
1122
- * @param $e
1123
- * @param $f
1124
- */
1125
  public function transform($a, $b, $c, $d, $e, $f)
1126
  {
1127
  $this->_pdf->concat($a, $b, $c, $d, $e, $f);
1128
  }
1129
 
1130
- /**
1131
- * @param array $points
1132
- * @param array $color
1133
- * @param null $width
1134
- * @param null $style
1135
- * @param bool $fill
1136
- */
1137
- public function polygon($points, $color, $width = null, $style = null, $fill = false)
1138
  {
1139
  $this->_set_fill_color($color);
1140
  $this->_set_stroke_color($color);
@@ -1163,16 +1037,7 @@ class PDFLib implements Canvas
1163
  $this->_set_stroke_opacity($this->_current_opacity, "Normal");
1164
  }
1165
 
1166
- /**
1167
- * @param float $x
1168
- * @param float $y
1169
- * @param float $r
1170
- * @param array $color
1171
- * @param null $width
1172
- * @param null $style
1173
- * @param bool $fill
1174
- */
1175
- public function circle($x, $y, $r, $color, $width = null, $style = null, $fill = false)
1176
  {
1177
  $this->_set_fill_color($color);
1178
  $this->_set_stroke_color($color);
@@ -1195,37 +1060,34 @@ class PDFLib implements Canvas
1195
  $this->_set_stroke_opacity($this->_current_opacity, "Normal");
1196
  }
1197
 
1198
- /**
1199
- * @param string $img_url
1200
- * @param float $x
1201
- * @param float $y
1202
- * @param int $w
1203
- * @param int $h
1204
- * @param string $resolution
1205
- */
1206
- public function image($img_url, $x, $y, $w, $h, $resolution = "normal")
1207
  {
1208
  $w = (int)$w;
1209
  $h = (int)$h;
1210
 
1211
- $img_type = Cache::detect_type($img_url, $this->get_dompdf()->getHttpContext());
 
 
 
 
 
1212
 
1213
- if (!isset($this->_imgs[$img_url])) {
1214
  if (strtolower($img_type) === "svg") {
1215
  //FIXME: PDFLib loads SVG but returns error message "Function must not be called in 'page' scope"
1216
- $image_load_response = $this->_pdf->load_graphics($img_type, $img_url, "");
1217
  } else {
1218
- $image_load_response = $this->_pdf->load_image($img_type, $img_url, "");
1219
  }
1220
  if ($image_load_response === 0) {
1221
  //TODO: should do something with the error message
1222
  $error = $this->_pdf->get_errmsg();
1223
  return;
1224
  }
1225
- $this->_imgs[$img_url] = $image_load_response;
1226
  }
1227
 
1228
- $img = $this->_imgs[$img_url];
1229
 
1230
  $y = $this->y($y) - $h;
1231
  if (strtolower($img_type) === "svg") {
@@ -1235,19 +1097,12 @@ class PDFLib implements Canvas
1235
  }
1236
  }
1237
 
1238
- /**
1239
- * @param float $x
1240
- * @param float $y
1241
- * @param string $text
1242
- * @param string $font
1243
- * @param float $size
1244
- * @param array $color
1245
- * @param int $word_spacing
1246
- * @param int $char_spacing
1247
- * @param int $angle
1248
- */
1249
  public function text($x, $y, $text, $font, $size, $color = [0, 0, 0], $word_spacing = 0, $char_spacing = 0, $angle = 0)
1250
  {
 
 
 
 
1251
  $fh = $this->_load_font($font);
1252
 
1253
  $this->_pdf->setfont($fh, $size);
@@ -1264,9 +1119,6 @@ class PDFLib implements Canvas
1264
  $this->_set_fill_opacity($this->_current_opacity, "Normal");
1265
  }
1266
 
1267
- /**
1268
- * @param string $code
1269
- */
1270
  public function javascript($code)
1271
  {
1272
  if (strlen($this->_dompdf->getOptions()->getPdflibLicense()) > 0) {
@@ -1274,25 +1126,11 @@ class PDFLib implements Canvas
1274
  }
1275
  }
1276
 
1277
- /**
1278
- * Add a named destination (similar to <a name="foo">...</a> in html)
1279
- *
1280
- * @param string $anchorname The name of the named destination
1281
- */
1282
  public function add_named_dest($anchorname)
1283
  {
1284
  $this->_pdf->add_nameddest($anchorname, "");
1285
  }
1286
 
1287
- /**
1288
- * Add a link to the pdf
1289
- *
1290
- * @param string $url The url to link to
1291
- * @param float $x The x position of the link
1292
- * @param float $y The y position of the link
1293
- * @param float $width The width of the link
1294
- * @param float $height The height of the link
1295
- */
1296
  public function add_link($url, $x, $y, $width, $height)
1297
  {
1298
  $y = $this->y($y) - $height;
@@ -1304,29 +1142,21 @@ class PDFLib implements Canvas
1304
  "contents={$url} destname=" . substr($url, 1) . " linewidth=0");
1305
  }
1306
  } else {
1307
- list($proto, $host, $path, $file) = Helpers::explode_url($url);
1308
-
1309
- if ($proto === "" || $proto === "file://") {
1310
- return; // Local links are not allowed
 
1311
  }
1312
- $url = Helpers::build_url($proto, $host, $path, $file);
1313
- $url = '{' . rawurldecode($url) . '}';
1314
-
1315
- $action = $this->_pdf->create_action("URI", "url=" . $url);
1316
- $this->_pdf->create_annotation($x, $y, $x + $width, $y + $height, 'Link', "contents={$url} action={activate=$action} linewidth=0");
1317
  }
1318
  }
1319
 
1320
- /**
1321
- * @param string $text
1322
- * @param string $font
1323
- * @param float $size
1324
- * @param float $word_spacing
1325
- * @param float $letter_spacing
1326
- * @return mixed
1327
- */
1328
- public function get_text_width($text, $font, $size, $word_spacing = 0, $letter_spacing = 0)
1329
  {
 
 
 
 
1330
  $fh = $this->_load_font($font);
1331
 
1332
  // Determine the additional width due to extra spacing
@@ -1341,13 +1171,12 @@ class PDFLib implements Canvas
1341
  return $this->_pdf->stringwidth($text, $fh, $size) + $delta;
1342
  }
1343
 
1344
- /**
1345
- * @param string $font
1346
- * @param float $size
1347
- * @return float
1348
- */
1349
  public function get_font_height($font, $size)
1350
  {
 
 
 
 
1351
  $fh = $this->_load_font($font);
1352
 
1353
  $this->_pdf->setfont($fh, $size);
@@ -1361,11 +1190,6 @@ class PDFLib implements Canvas
1361
  return (abs($asc) + abs($desc)) * $ratio;
1362
  }
1363
 
1364
- /**
1365
- * @param string $font
1366
- * @param float $size
1367
- * @return float
1368
- */
1369
  public function get_font_baseline($font, $size)
1370
  {
1371
  $ratio = $this->_dompdf->getOptions()->getFontHeightRatio();
@@ -1374,60 +1198,55 @@ class PDFLib implements Canvas
1374
  }
1375
 
1376
  /**
1377
- * Writes text at the specified x and y coordinates on every page
1378
  *
1379
- * The strings '{PAGE_NUM}' and '{PAGE_COUNT}' are automatically replaced
1380
- * with their current values.
 
 
 
1381
  *
1382
- * See {@link Style::munge_color()} for the format of the color array.
 
1383
  *
1384
- * @param float $x
1385
- * @param float $y
1386
- * @param string $text the text to write
1387
- * @param string $font the font file to use
1388
- * @param float $size the font size, in points
1389
- * @param array $color
1390
- * @param float $word_space word spacing adjustment
1391
- * @param float $char_space char spacing adjustment
1392
- * @param float $angle angle to write the text at, measured CW starting from the x-axis
1393
  */
1394
- public function page_text($x, $y, $text, $font, $size, $color = [0, 0, 0], $word_space = 0.0, $char_space = 0.0, $angle = 0.0)
1395
  {
1396
- $_t = "text";
1397
- $this->_page_text[] = compact("_t", "x", "y", "text", "font", "size", "color", "word_space", "char_space", "angle");
 
 
 
 
 
 
 
 
 
 
 
1398
  }
1399
 
1400
- //........................................................................
 
 
 
 
 
 
 
 
 
 
1401
 
1402
- /**
1403
- * Processes a callback or script on every page
1404
- *
1405
- * The callback function receives the four parameters `$pageNumber`,
1406
- * `$pageCount`, `$pdf`, and `$fontMetrics`, in that order. If a script is
1407
- * passed as string, the variables `$PAGE_NUM`, `$PAGE_COUNT`, `$pdf`, and
1408
- * `$fontMetrics` are available instead.
1409
- *
1410
- * This function can be used to add page numbers to all pages after the
1411
- * first one, for example.
1412
- *
1413
- * @param callable|string $code The callback function or PHP script to process on every page
1414
- */
1415
- public function page_script($code)
1416
  {
1417
- if (is_callable($code)) {
1418
- $this->_page_text[] = [
1419
- "_t" => "callback",
1420
- "callback" => $code
1421
- ];
1422
- } else {
1423
- $_t = "script";
1424
- $this->_page_text[] = compact("_t", "code");
1425
- }
1426
  }
1427
 
1428
- /**
1429
- *
1430
- */
1431
  public function new_page()
1432
  {
1433
  // Add objects to the current page
@@ -1438,49 +1257,15 @@ class PDFLib implements Canvas
1438
  $this->_page_number = ++$this->_page_count;
1439
  }
1440
 
1441
- /**
1442
- * Add text to each page after rendering is complete
1443
- */
1444
- protected function _add_page_text()
1445
  {
1446
- if (count($this->_page_text) === 0) {
1447
- return;
1448
- }
1449
-
1450
- $eval = null;
1451
  $this->_pdf->suspend_page("");
1452
 
1453
  for ($p = 1; $p <= $this->_page_count; $p++) {
1454
  $this->_pdf->resume_page("pagenumber=$p");
1455
 
1456
- foreach ($this->_page_text as $pt) {
1457
- extract($pt);
1458
-
1459
- switch ($_t) {
1460
- case "text":
1461
- $text = str_replace(["{PAGE_NUM}", "{PAGE_COUNT}"],
1462
- [$p, $this->_page_count], $text);
1463
- $this->text($x, $y, $text, $font, $size, $color, $word_space, $char_space, $angle);
1464
- break;
1465
-
1466
- case "callback":
1467
- $fontMetrics = $this->get_dompdf()->getFontMetrics();
1468
- $callback($p, $this->_page_count, $this, $fontMetrics);
1469
- break;
1470
-
1471
- case "script":
1472
- if (!$eval) {
1473
- $eval = new PHPEvaluator($this);
1474
- }
1475
- $eval->evaluate($code, ["PAGE_NUM" => $p, "PAGE_COUNT" => $this->_page_count]);
1476
- break;
1477
-
1478
- case "line":
1479
- $this->line($x1, $y1, $x2, $y2, $color, $width, $style);
1480
- break;
1481
-
1482
- }
1483
- }
1484
 
1485
  $this->_pdf->suspend_page("");
1486
  }
@@ -1489,10 +1274,6 @@ class PDFLib implements Canvas
1489
  }
1490
 
1491
  /**
1492
- * Streams the PDF to the client.
1493
- *
1494
- * @param string $filename The filename to present to the client.
1495
- * @param array $options Associative array: 'compress' => 1 or 0 (default 1); 'Attachment' => 1 or 0 (default 1).
1496
  * @throws Exception
1497
  */
1498
  public function stream($filename = "document.pdf", $options = [])
@@ -1508,8 +1289,6 @@ class PDFLib implements Canvas
1508
  $options["Attachment"] = true;
1509
  }
1510
 
1511
- $this->_add_page_text();
1512
-
1513
  if ($options["compress"]) {
1514
  $this->setPDFLibValue("compress", 6);
1515
  } else {
@@ -1564,20 +1343,12 @@ class PDFLib implements Canvas
1564
  flush();
1565
  }
1566
 
1567
- /**
1568
- * Returns the PDF as a string.
1569
- *
1570
- * @param array $options Associative array: 'compress' => 1 or 0 (default 1).
1571
- * @return string
1572
- */
1573
  public function output($options = [])
1574
  {
1575
  if (!isset($options["compress"])) {
1576
  $options["compress"] = true;
1577
  }
1578
 
1579
- $this->_add_page_text();
1580
-
1581
  if ($options["compress"]) {
1582
  $this->setPDFLibValue("compress", 6);
1583
  } else {
11
 
12
  use Dompdf\Canvas;
13
  use Dompdf\Dompdf;
 
14
  use Dompdf\Exception;
15
+ use Dompdf\FontMetrics;
16
+ use Dompdf\Helpers;
17
  use Dompdf\Image\Cache;
 
18
 
19
  /**
20
  * PDF rendering interface
37
  /**
38
  * Dimensions of paper sizes in points
39
  *
40
+ * @var array
41
  */
42
  public static $PAPER_SIZES = []; // Set to Dompdf\Adapter\CPDF::$PAPER_SIZES below.
43
 
130
  /**
131
  * The current opacity level
132
  *
133
+ * @var float|null
134
  */
135
  protected $_current_opacity;
136
 
184
  protected $_page_count;
185
 
186
  /**
187
+ * Array of pages for accessing after rendering is initially complete
 
 
 
 
 
 
 
188
  *
189
  * @var array
190
  */
191
  protected $_pages;
192
 
193
+ public function __construct($paper = "letter", $orientation = "portrait", ?Dompdf $dompdf = null)
 
 
 
 
 
 
 
 
194
  {
195
  if (is_array($paper)) {
196
+ $size = array_map("floatval", $paper);
 
 
197
  } else {
198
+ $paper = strtolower($paper);
199
+ $size = self::$PAPER_SIZES[$paper] ?? self::$PAPER_SIZES["letter"];
200
  }
201
 
202
+ if (strtolower($orientation) === "landscape") {
203
+ [$size[2], $size[3]] = [$size[3], $size[2]];
204
  }
205
 
206
  $this->_width = $size[2] - $size[0];
219
  $this->setPDFLibParameter("license", $license);
220
  }
221
 
222
+ if ($this->getPDFLibMajorVersion() < 10) {
223
+ $this->setPDFLibParameter("textformat", "utf8");
224
+ }
225
  if ($this->getPDFLibMajorVersion() >= 7) {
226
  $this->setPDFLibParameter("errorpolicy", "return");
227
  // $this->_pdf->set_option('logging={filename=' . \APP_PATH . '/logs/pdflib.log classes={api=1 warning=2}}');
257
  $this->_pdf->begin_page_ext($this->_width, $this->_height, "");
258
 
259
  $this->_page_number = $this->_page_count = 1;
 
260
 
261
  $this->_imgs = [];
262
  $this->_fonts = [];
263
  $this->_objs = [];
264
  }
265
 
 
 
 
266
  function get_dompdf()
267
  {
268
  return $this->_dompdf;
296
  return $this->_pdf;
297
  }
298
 
299
+ public function add_info(string $label, string $value): void
 
 
 
 
 
 
300
  {
301
  $this->_pdf->set_info($label, $value);
302
  }
440
  }
441
  }
442
 
 
 
 
443
  public function get_width()
444
  {
445
  return $this->_width;
446
  }
447
 
 
 
 
448
  public function get_height()
449
  {
450
  return $this->_height;
451
  }
452
 
 
 
 
453
  public function get_page_number()
454
  {
455
  return $this->_page_number;
456
  }
457
 
 
 
 
458
  public function get_page_count()
459
  {
460
  return $this->_page_count;
468
  $this->_page_number = (int)$num;
469
  }
470
 
 
 
 
471
  public function set_page_count($count)
472
  {
473
  $this->_page_count = (int)$count;
477
  * Sets the line style
478
  *
479
  * @param float $width
480
+ * @param string $cap
481
  * @param string $join
482
  * @param array $dash
 
 
483
  */
484
  protected function _set_line_style($width, $cap, $join, $dash)
485
  {
486
  if (!is_array($dash)) {
487
+ $dash = [];
488
+ }
489
+
490
+ // Work around PDFLib limitation with 0 dash length:
491
+ // Value 0 for option 'dasharray' is too small (minimum 1.5e-05)
492
+ foreach ($dash as &$d) {
493
+ if ($d == 0) {
494
+ $d = 1.5e-5;
495
+ }
496
  }
497
 
498
+ if (count($dash) === 1) {
499
  $dash[] = $dash[0];
500
  }
501
 
651
  /**
652
  * Sets the fill opacity
653
  *
654
+ * @param float $opacity
655
+ * @param string $mode
656
  */
657
  public function _set_fill_opacity($opacity, $mode = "Normal")
658
  {
659
+ if ($mode === "Normal" && isset($opacity)) {
660
  $this->_set_gstate("opacityfill=$opacity");
661
  }
662
  }
664
  /**
665
  * Sets the stroke opacity
666
  *
667
+ * @param float $opacity
668
+ * @param string $mode
669
  */
670
  public function _set_stroke_opacity($opacity, $mode = "Normal")
671
  {
672
+ if ($mode === "Normal" && isset($opacity)) {
673
  $this->_set_gstate("opacitystroke=$opacity");
674
  }
675
  }
676
 
677
+ public function set_opacity(float $opacity, string $mode = "Normal"): void
 
 
 
 
 
 
678
  {
679
+ if ($mode === "Normal") {
680
  $this->_set_gstate("opacityfill=$opacity opacitystroke=$opacity");
681
  $this->_current_opacity = $opacity;
682
  }
835
  return $this->_height - $y;
836
  }
837
 
838
+ public function line($x1, $y1, $x2, $y2, $color, $width, $style = [], $cap = "butt")
 
 
 
 
 
 
 
 
 
839
  {
840
+ $this->_set_line_style($width, $cap, "", $style);
841
  $this->_set_stroke_color($color);
842
 
843
  $y1 = $this->y($y1);
850
  $this->_set_stroke_opacity($this->_current_opacity, "Normal");
851
  }
852
 
853
+ public function arc($x, $y, $r1, $r2, $astart, $aend, $color, $width, $style = [], $cap = "butt")
 
 
 
 
 
 
 
 
 
 
 
 
 
854
  {
855
+ $this->_set_line_style($width, $cap, "", $style);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
856
  $this->_set_stroke_color($color);
857
 
858
+ $y = $this->y($y);
859
 
860
+ $this->_pdf->arc($x, $y, $r1, $astart, $aend);
861
  $this->_pdf->stroke();
862
 
863
  $this->_set_stroke_opacity($this->_current_opacity, "Normal");
864
  }
865
 
866
+ public function rectangle($x1, $y1, $w, $h, $color, $width, $style = [], $cap = "butt")
 
 
 
 
 
 
 
 
 
867
  {
868
  $this->_set_stroke_color($color);
869
+ $this->_set_line_style($width, $cap, "", $style);
870
 
871
  $y1 = $this->y($y1) - $h;
872
 
876
  $this->_set_stroke_opacity($this->_current_opacity, "Normal");
877
  }
878
 
 
 
 
 
 
 
 
879
  public function filled_rectangle($x1, $y1, $w, $h, $color)
880
  {
881
  $this->_set_fill_color($color);
888
  $this->_set_fill_opacity($this->_current_opacity, "Normal");
889
  }
890
 
 
 
 
 
 
 
891
  public function clipping_rectangle($x1, $y1, $w, $h)
892
  {
893
  $this->_pdf->save();
898
  $this->_pdf->clip();
899
  }
900
 
 
 
 
 
 
 
 
 
 
 
901
  public function clipping_roundrectangle($x1, $y1, $w, $h, $rTL, $rTR, $rBR, $rBL)
902
  {
903
  if ($this->getPDFLibMajorVersion() < 9) {
917
  // line: left edge, bottom end
918
  $path = $this->_pdf->add_path_point($path, 0, 0 + $rBL, "line", "");
919
  // curve: bottom-left corner
920
+ if ($rBL > 0) {
921
+ $path = $this->_pdf->add_path_point($path, 0 + $rBL, 0, "elliptical", "radius=$rBL clockwise=false");
922
+ }
923
  // line: bottom edge, left end
924
  $path = $this->_pdf->add_path_point($path, 0 - $rBR + $w, 0, "line", "");
925
  // curve: bottom-right corner
926
+ if ($rBR > 0) {
927
+ $path = $this->_pdf->add_path_point($path, 0 + $w, 0 + $rBR, "elliptical", "radius=$rBR clockwise=false");
928
+ }
929
  // line: right edge, top end
930
  $path = $this->_pdf->add_path_point($path, 0 + $w, 0 - $rTR + $h, "line", "");
931
  // curve: top-right corner
932
+ if ($rTR > 0) {
933
+ $path = $this->_pdf->add_path_point($path, 0 - $rTR + $w, 0 + $h, "elliptical", "radius=$rTR clockwise=false");
934
+ }
935
  // line: top edge, left end
936
  $path = $this->_pdf->add_path_point($path, 0 + $rTL, 0 + $h, "line", "");
937
  // curve: top-left corner
938
+ if ($rTL > 0) {
939
+ $path = $this->_pdf->add_path_point($path, 0, 0 - $rTL + $h, "elliptical", "radius=$rTL clockwise=false");
940
+ }
941
  $this->_pdf->draw_path($path, $x1, $this->_height-$y1-$h, "clip=true");
942
  }
943
 
944
+ public function clipping_polygon(array $points): void
945
+ {
946
+ $this->_pdf->save();
947
+
948
+ $y = $this->y(array_pop($points));
949
+ $x = array_pop($points);
950
+ $this->_pdf->moveto($x, $y);
951
+
952
+ while (count($points) > 1) {
953
+ $y = $this->y(array_pop($points));
954
+ $x = array_pop($points);
955
+ $this->_pdf->lineto($x, $y);
956
+ }
957
+
958
+ $this->_pdf->closepath();
959
+ $this->_pdf->clip();
960
+ }
961
+
962
  public function clipping_end()
963
  {
964
  $this->_pdf->restore();
965
  }
966
 
 
 
 
967
  public function save()
968
  {
969
  $this->_pdf->save();
974
  $this->_pdf->restore();
975
  }
976
 
 
 
 
 
 
977
  public function rotate($angle, $x, $y)
978
  {
979
  $pdf = $this->_pdf;
982
  $pdf->translate(-$x, -$this->_height + $y);
983
  }
984
 
 
 
 
 
 
 
985
  public function skew($angle_x, $angle_y, $x, $y)
986
  {
987
  $pdf = $this->_pdf;
990
  $pdf->translate(-$x, -$this->_height + $y);
991
  }
992
 
 
 
 
 
 
 
993
  public function scale($s_x, $s_y, $x, $y)
994
  {
995
  $pdf = $this->_pdf;
998
  $pdf->translate(-$x, -$this->_height + $y);
999
  }
1000
 
 
 
 
 
1001
  public function translate($t_x, $t_y)
1002
  {
1003
  $this->_pdf->translate($t_x, -$t_y);
1004
  }
1005
 
 
 
 
 
 
 
 
 
1006
  public function transform($a, $b, $c, $d, $e, $f)
1007
  {
1008
  $this->_pdf->concat($a, $b, $c, $d, $e, $f);
1009
  }
1010
 
1011
+ public function polygon($points, $color, $width = null, $style = [], $fill = false)
 
 
 
 
 
 
 
1012
  {
1013
  $this->_set_fill_color($color);
1014
  $this->_set_stroke_color($color);
1037
  $this->_set_stroke_opacity($this->_current_opacity, "Normal");
1038
  }
1039
 
1040
+ public function circle($x, $y, $r, $color, $width = null, $style = [], $fill = false)
 
 
 
 
 
 
 
 
 
1041
  {
1042
  $this->_set_fill_color($color);
1043
  $this->_set_stroke_color($color);
1060
  $this->_set_stroke_opacity($this->_current_opacity, "Normal");
1061
  }
1062
 
1063
+ public function image($img, $x, $y, $w, $h, $resolution = "normal")
 
 
 
 
 
 
 
 
1064
  {
1065
  $w = (int)$w;
1066
  $h = (int)$h;
1067
 
1068
+ $img_type = Cache::detect_type($img, $this->get_dompdf()->getHttpContext());
1069
+
1070
+ // Strip file:// prefix
1071
+ if (substr($img, 0, 7) === "file://") {
1072
+ $img = substr($img, 7);
1073
+ }
1074
 
1075
+ if (!isset($this->_imgs[$img])) {
1076
  if (strtolower($img_type) === "svg") {
1077
  //FIXME: PDFLib loads SVG but returns error message "Function must not be called in 'page' scope"
1078
+ $image_load_response = $this->_pdf->load_graphics($img_type, $img, "");
1079
  } else {
1080
+ $image_load_response = $this->_pdf->load_image($img_type, $img, "");
1081
  }
1082
  if ($image_load_response === 0) {
1083
  //TODO: should do something with the error message
1084
  $error = $this->_pdf->get_errmsg();
1085
  return;
1086
  }
1087
+ $this->_imgs[$img] = $image_load_response;
1088
  }
1089
 
1090
+ $img = $this->_imgs[$img];
1091
 
1092
  $y = $this->y($y) - $h;
1093
  if (strtolower($img_type) === "svg") {
1097
  }
1098
  }
1099
 
 
 
 
 
 
 
 
 
 
 
 
1100
  public function text($x, $y, $text, $font, $size, $color = [0, 0, 0], $word_spacing = 0, $char_spacing = 0, $angle = 0)
1101
  {
1102
+ if ($size == 0) {
1103
+ return;
1104
+ }
1105
+
1106
  $fh = $this->_load_font($font);
1107
 
1108
  $this->_pdf->setfont($fh, $size);
1119
  $this->_set_fill_opacity($this->_current_opacity, "Normal");
1120
  }
1121
 
 
 
 
1122
  public function javascript($code)
1123
  {
1124
  if (strlen($this->_dompdf->getOptions()->getPdflibLicense()) > 0) {
1126
  }
1127
  }
1128
 
 
 
 
 
 
1129
  public function add_named_dest($anchorname)
1130
  {
1131
  $this->_pdf->add_nameddest($anchorname, "");
1132
  }
1133
 
 
 
 
 
 
 
 
 
 
1134
  public function add_link($url, $x, $y, $width, $height)
1135
  {
1136
  $y = $this->y($y) - $height;
1142
  "contents={$url} destname=" . substr($url, 1) . " linewidth=0");
1143
  }
1144
  } else {
1145
+ //TODO: PDFLib::create_action does not permit non-HTTP links for URI actions
1146
+ $action = $this->_pdf->create_action("URI", "url={{$url}}");
1147
+ // add the annotation only if the action was created
1148
+ if ($action !== 0) {
1149
+ $this->_pdf->create_annotation($x, $y, $x + $width, $y + $height, 'Link', "contents={{$url}} action={activate=$action} linewidth=0");
1150
  }
 
 
 
 
 
1151
  }
1152
  }
1153
 
1154
+ public function get_text_width($text, $font, $size, $word_spacing = 0.0, $letter_spacing = 0.0)
 
 
 
 
 
 
 
 
1155
  {
1156
+ if ($size == 0) {
1157
+ return 0.0;
1158
+ }
1159
+
1160
  $fh = $this->_load_font($font);
1161
 
1162
  // Determine the additional width due to extra spacing
1171
  return $this->_pdf->stringwidth($text, $fh, $size) + $delta;
1172
  }
1173
 
 
 
 
 
 
1174
  public function get_font_height($font, $size)
1175
  {
1176
+ if ($size == 0) {
1177
+ return 0.0;
1178
+ }
1179
+
1180
  $fh = $this->_load_font($font);
1181
 
1182
  $this->_pdf->setfont($fh, $size);
1190
  return (abs($asc) + abs($desc)) * $ratio;
1191
  }
1192
 
 
 
 
 
 
1193
  public function get_font_baseline($font, $size)
1194
  {
1195
  $ratio = $this->_dompdf->getOptions()->getFontHeightRatio();
1198
  }
1199
 
1200
  /**
1201
+ * Processes a callback or script on every page.
1202
  *
1203
+ * The callback function receives the four parameters `int $pageNumber`,
1204
+ * `int $pageCount`, `Canvas $canvas`, and `FontMetrics $fontMetrics`, in
1205
+ * that order. If a script is passed as string, the variables `$PAGE_NUM`,
1206
+ * `$PAGE_COUNT`, `$pdf`, and `$fontMetrics` are available instead. Passing
1207
+ * a script as string is deprecated and will be removed in a future version.
1208
  *
1209
+ * This function can be used to add page numbers to all pages after the
1210
+ * first one, for example.
1211
  *
1212
+ * @param callable|string $callback The callback function or PHP script to process on every page
 
 
 
 
 
 
 
 
1213
  */
1214
+ public function page_script($callback): void
1215
  {
1216
+ if (is_string($callback)) {
1217
+ $this->processPageScript(function (
1218
+ int $PAGE_NUM,
1219
+ int $PAGE_COUNT,
1220
+ self $pdf,
1221
+ FontMetrics $fontMetrics
1222
+ ) use ($callback) {
1223
+ eval($callback);
1224
+ });
1225
+ return;
1226
+ }
1227
+
1228
+ $this->processPageScript($callback);
1229
  }
1230
 
1231
+ public function page_text($x, $y, $text, $font, $size, $color = [0, 0, 0], $word_space = 0.0, $char_space = 0.0, $angle = 0.0)
1232
+ {
1233
+ $this->processPageScript(function (int $pageNumber, int $pageCount) use ($x, $y, $text, $font, $size, $color, $word_space, $char_space, $angle) {
1234
+ $text = str_replace(
1235
+ ["{PAGE_NUM}", "{PAGE_COUNT}"],
1236
+ [$pageNumber, $pageCount],
1237
+ $text
1238
+ );
1239
+ $this->text($x, $y, $text, $font, $size, $color, $word_space, $char_space, $angle);
1240
+ });
1241
+ }
1242
 
1243
+ public function page_line($x1, $y1, $x2, $y2, $color, $width, $style = [])
 
 
 
 
 
 
 
 
 
 
 
 
 
1244
  {
1245
+ $this->processPageScript(function () use ($x1, $y1, $x2, $y2, $color, $width, $style) {
1246
+ $this->line($x1, $y1, $x2, $y2, $color, $width, $style);
1247
+ });
 
 
 
 
 
 
1248
  }
1249
 
 
 
 
1250
  public function new_page()
1251
  {
1252
  // Add objects to the current page
1257
  $this->_page_number = ++$this->_page_count;
1258
  }
1259
 
1260
+ protected function processPageScript(callable $callback): void
 
 
 
1261
  {
 
 
 
 
 
1262
  $this->_pdf->suspend_page("");
1263
 
1264
  for ($p = 1; $p <= $this->_page_count; $p++) {
1265
  $this->_pdf->resume_page("pagenumber=$p");
1266
 
1267
+ $fontMetrics = $this->_dompdf->getFontMetrics();
1268
+ $callback($p, $this->_page_count, $this, $fontMetrics);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1269
 
1270
  $this->_pdf->suspend_page("");
1271
  }
1274
  }
1275
 
1276
  /**
 
 
 
 
1277
  * @throws Exception
1278
  */
1279
  public function stream($filename = "document.pdf", $options = [])
1289
  $options["Attachment"] = true;
1290
  }
1291
 
 
 
1292
  if ($options["compress"]) {
1293
  $this->setPDFLibValue("compress", 6);
1294
  } else {
1343
  flush();
1344
  }
1345
 
 
 
 
 
 
 
1346
  public function output($options = [])
1347
  {
1348
  if (!isset($options["compress"])) {
1349
  $options["compress"] = true;
1350
  }
1351
 
 
 
1352
  if ($options["compress"]) {
1353
  $this->setPDFLibValue("compress", 6);
1354
  } else {
vendor/dompdf/dompdf/src/Canvas.php CHANGED
@@ -24,7 +24,13 @@ namespace Dompdf;
24
  */
25
  interface Canvas
26
  {
27
- function __construct($paper = "letter", $orientation = "portrait", Dompdf $dompdf = null);
 
 
 
 
 
 
28
 
29
  /**
30
  * @return Dompdf
@@ -39,7 +45,7 @@ interface Canvas
39
  function get_page_number();
40
 
41
  /**
42
- * Returns the total number of pages
43
  *
44
  * @return int
45
  */
@@ -55,47 +61,68 @@ interface Canvas
55
  /**
56
  * Draws a line from x1,y1 to x2,y2
57
  *
58
- * See {@link Style::munge_color()} for the format of the color array.
59
  * See {@link Cpdf::setLineStyle()} for a description of the format of the
60
- * $style parameter (aka dash).
61
  *
62
- * @param float $x1
63
- * @param float $y1
64
- * @param float $x2
65
- * @param float $y2
66
- * @param array $color
67
- * @param float $width
68
- * @param array $style
 
 
69
  */
70
- function line($x1, $y1, $x2, $y2, $color, $width, $style = null);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
71
 
72
  /**
73
  * Draws a rectangle at x1,y1 with width w and height h
74
  *
75
- * See {@link Style::munge_color()} for the format of the color array.
76
- * See {@link Cpdf::setLineStyle()} for a description of the $style
77
- * parameter (aka dash)
78
  *
79
- * @param float $x1
80
- * @param float $y1
81
- * @param float $w
82
- * @param float $h
83
- * @param array $color
84
- * @param float $width
85
- * @param array $style
 
 
86
  */
87
- function rectangle($x1, $y1, $w, $h, $color, $width, $style = null);
88
 
89
  /**
90
  * Draws a filled rectangle at x1,y1 with width w and height h
91
  *
92
- * See {@link Style::munge_color()} for the format of the color array.
93
- *
94
  * @param float $x1
95
  * @param float $y1
96
  * @param float $w
97
  * @param float $h
98
- * @param array $color
 
99
  */
100
  function filled_rectangle($x1, $y1, $w, $h, $color);
101
 
@@ -120,65 +147,67 @@ interface Canvas
120
  * @param float $tr
121
  * @param float $br
122
  * @param float $bl
123
- *
124
- * @return
125
  */
126
  function clipping_roundrectangle($x1, $y1, $w, $h, $tl, $tr, $br, $bl);
127
 
 
 
 
 
 
 
 
128
  /**
129
  * Ends the last clipping shape
130
  */
131
  function clipping_end();
132
 
133
  /**
134
- * Processes a callback on every page
135
  *
136
- * The callback function receives the four parameters `$pageNumber`,
137
- * `$pageCount`, `$pdf`, and `$fontMetrics`, in that order.
 
138
  *
139
  * This function can be used to add page numbers to all pages after the
140
  * first one, for example.
141
  *
142
  * @param callable $callback The callback function to process on every page
143
- * @todo Enable with next major release
144
  */
145
- //public function page_script(callable $callback): void;
146
 
147
  /**
148
- * Writes text at the specified x and y coordinates on every page
149
  *
150
  * The strings '{PAGE_NUM}' and '{PAGE_COUNT}' are automatically replaced
151
  * with their current values.
152
  *
153
- * See {@link Style::munge_color()} for the format of the color array.
154
- *
155
  * @param float $x
156
  * @param float $y
157
- * @param string $text the text to write
158
- * @param string $font the font file to use
159
- * @param float $size the font size, in points
160
- * @param array $color
161
- * @param float $word_space word spacing adjustment
162
- * @param float $char_space char spacing adjustment
163
- * @param float $angle angle to write the text at, measured CW starting from the x-axis
 
164
  */
165
  public function page_text($x, $y, $text, $font, $size, $color = [0, 0, 0], $word_space = 0.0, $char_space = 0.0, $angle = 0.0);
166
 
167
  /**
168
- * Draw line at the specified coordinates on every page.
169
- *
170
- * See {@link Style::munge_color()} for the format of the color array.
171
  *
172
  * @param float $x1
173
  * @param float $y1
174
  * @param float $x2
175
  * @param float $y2
176
- * @param array $color
 
177
  * @param float $width
178
- * @param array $style optional
179
- * @todo Enable with next major release
180
  */
181
- //public function page_line($x1, $y1, $x2, $y2, $color, $width, $style = []);
182
 
183
  /**
184
  * Save current state
@@ -204,8 +233,8 @@ interface Canvas
204
  *
205
  * @param float $angle_x
206
  * @param float $angle_y
207
- * @param float $x Origin abscissa
208
- * @param float $y Origin ordinate
209
  */
210
  function skew($angle_x, $angle_y, $x, $y);
211
 
@@ -230,13 +259,12 @@ interface Canvas
230
  /**
231
  * Transform
232
  *
233
- * @param $a
234
- * @param $b
235
- * @param $c
236
- * @param $d
237
- * @param $e
238
- * @param $f
239
- * @return
240
  */
241
  function transform($a, $b, $c, $d, $e, $f);
242
 
@@ -245,43 +273,43 @@ interface Canvas
245
  *
246
  * The polygon is formed by joining all the points stored in the $points
247
  * array. $points has the following structure:
248
- * <code>
249
  * array(0 => x1,
250
  * 1 => y1,
251
  * 2 => x2,
252
  * 3 => y2,
253
  * ...
254
  * );
255
- * </code>
256
  *
257
- * See {@link Style::munge_color()} for the format of the color array.
258
- * See {@link Cpdf::setLineStyle()} for a description of the $style
259
- * parameter (aka dash)
260
  *
261
  * @param array $points
262
- * @param array $color
 
263
  * @param float $width
264
  * @param array $style
265
- * @param bool $fill Fills the polygon if true
266
  */
267
- function polygon($points, $color, $width = null, $style = null, $fill = false);
268
 
269
  /**
270
  * Draws a circle at $x,$y with radius $r
271
  *
272
- * See {@link Style::munge_color()} for the format of the color array.
273
- * See {@link Cpdf::setLineStyle()} for a description of the $style
274
- * parameter (aka dash)
275
  *
276
  * @param float $x
277
  * @param float $y
278
  * @param float $r
279
- * @param array $color
 
280
  * @param float $width
281
  * @param array $style
282
- * @param bool $fill Fills the circle if true
283
  */
284
- function circle($x, $y, $r, $color, $width = null, $style = null, $fill = false);
285
 
286
  /**
287
  * Add an image to the pdf.
@@ -289,44 +317,28 @@ interface Canvas
289
  * The image is placed at the specified x and y coordinates with the
290
  * given width and height.
291
  *
292
- * @param string $img_url the path to the image
293
- * @param float $x x position
294
- * @param float $y y position
295
- * @param int $w width (in pixels)
296
- * @param int $h height (in pixels)
297
  * @param string $resolution The resolution of the image
298
  */
299
- function image($img_url, $x, $y, $w, $h, $resolution = "normal");
300
-
301
- /**
302
- * Add an arc to the PDF
303
- * See {@link Style::munge_color()} for the format of the color array.
304
- *
305
- * @param float $x X coordinate of the arc
306
- * @param float $y Y coordinate of the arc
307
- * @param float $r1 Radius 1
308
- * @param float $r2 Radius 2
309
- * @param float $astart Start angle in degrees
310
- * @param float $aend End angle in degrees
311
- * @param array $color Color
312
- * @param float $width
313
- * @param array $style
314
- */
315
- function arc($x, $y, $r1, $r2, $astart, $aend, $color, $width, $style = []);
316
 
317
  /**
318
  * Writes text at the specified x and y coordinates
319
- * See {@link Style::munge_color()} for the format of the color array.
320
  *
321
- * @param float $x
322
- * @param float $y
323
- * @param string $text the text to write
324
- * @param string $font the font file to use
325
- * @param float $size the font size, in points
326
- * @param array $color
327
- * @param float $word_space word spacing adjustment
328
- * @param float $char_space char spacing adjustment
329
- * @param float $angle angle
 
330
  */
331
  function text($x, $y, $text, $font, $size, $color = [0, 0, 0], $word_space = 0.0, $char_space = 0.0, $angle = 0.0);
332
 
@@ -340,30 +352,30 @@ interface Canvas
340
  /**
341
  * Add a link to the pdf
342
  *
343
- * @param string $url The url to link to
344
- * @param float $x The x position of the link
345
- * @param float $y The y position of the link
346
- * @param float $width The width of the link
347
- * @param float $height The height of the link
348
  */
349
  function add_link($url, $x, $y, $width, $height);
350
 
351
  /**
352
- * Add meta information to the pdf
353
  *
354
- * @param string $name Label of the value (Creator, Producer, etc.)
355
  * @param string $value The text to set
356
  */
357
- function add_info($name, $value);
358
 
359
  /**
360
  * Calculates text size, in points
361
  *
362
- * @param string $text the text to be sized
363
- * @param string $font the desired font
364
- * @param float $size the desired font size
365
- * @param float $word_spacing word spacing, if any
366
- * @param float $char_spacing char spacing, if any
367
  *
368
  * @return float
369
  */
@@ -372,55 +384,54 @@ interface Canvas
372
  /**
373
  * Calculates font height, in points
374
  *
375
- * @param string $font
376
- * @param float $size
377
  *
378
  * @return float
379
  */
380
  function get_font_height($font, $size);
381
 
382
  /**
383
- * Calculates font baseline, in points
384
  *
385
- * @param string $font
386
- * @param float $size
387
  *
388
  * @return float
389
  */
390
- function get_font_baseline($font, $size);
391
 
392
  /**
393
- * Returns the PDF's width in points
 
 
 
394
  *
395
  * @return float
396
  */
397
- function get_width();
398
-
399
 
400
  /**
401
- * Return the image's height in pixels
402
  *
403
  * @return float
404
  */
405
- function get_height();
406
 
407
  /**
408
- * Returns the font x-height, in points
409
- *
410
- * @param string $font
411
- * @param float $size
412
  *
413
  * @return float
414
  */
415
- //function get_font_x_height($font, $size);
416
 
417
  /**
418
  * Sets the opacity
419
  *
420
- * @param float $opacity
421
  * @param string $mode
422
  */
423
- function set_opacity($opacity, $mode = "Normal");
424
 
425
  /**
426
  * Sets the default view
@@ -435,17 +446,13 @@ interface Canvas
435
  * 'FitBH' top
436
  * 'FitBV' left
437
  * @param array $options
438
- *
439
- * @return void
440
  */
441
  function set_default_view($view, $options = []);
442
 
443
  /**
444
- * @param string $script
445
- *
446
- * @return void
447
  */
448
- function javascript($script);
449
 
450
  /**
451
  * Starts a new page
@@ -455,10 +462,10 @@ interface Canvas
455
  function new_page();
456
 
457
  /**
458
- * Streams the PDF directly to the browser.
459
  *
460
- * @param string $filename The filename to present to the browser.
461
- * @param array $options Associative array: 'compress' => 1 or 0 (default 1); 'Attachment' => 1 or 0 (default 1).
462
  */
463
  function stream($filename, $options = []);
464
 
@@ -466,6 +473,7 @@ interface Canvas
466
  * Returns the PDF as a string.
467
  *
468
  * @param array $options Associative array: 'compress' => 1 or 0 (default 1).
 
469
  * @return string
470
  */
471
  function output($options = []);
24
  */
25
  interface Canvas
26
  {
27
+ /**
28
+ * @param string|float[] $paper The paper size to use as either a standard paper size (see {@link Dompdf\Adapter\CPDF::$PAPER_SIZES})
29
+ * or an array of the form `[x1, y1, x2, y2]` (typically `[0, 0, width, height]`).
30
+ * @param string $orientation The paper orientation, either `portrait` or `landscape`.
31
+ * @param Dompdf $dompdf The Dompdf instance.
32
+ */
33
+ public function __construct($paper = "letter", $orientation = "portrait", ?Dompdf $dompdf = null);
34
 
35
  /**
36
  * @return Dompdf
45
  function get_page_number();
46
 
47
  /**
48
+ * Returns the total number of pages in the document
49
  *
50
  * @return int
51
  */
61
  /**
62
  * Draws a line from x1,y1 to x2,y2
63
  *
 
64
  * See {@link Cpdf::setLineStyle()} for a description of the format of the
65
+ * $style and $cap parameters (aka dash and cap).
66
  *
67
+ * @param float $x1
68
+ * @param float $y1
69
+ * @param float $x2
70
+ * @param float $y2
71
+ * @param array $color Color array in the format `[r, g, b, "alpha" => alpha]`
72
+ * where r, g, b, and alpha are float values between 0 and 1
73
+ * @param float $width
74
+ * @param array $style
75
+ * @param string $cap `butt`, `round`, or `square`
76
  */
77
+ function line($x1, $y1, $x2, $y2, $color, $width, $style = [], $cap = "butt");
78
+
79
+ /**
80
+ * Draws an arc
81
+ *
82
+ * See {@link Cpdf::setLineStyle()} for a description of the format of the
83
+ * $style and $cap parameters (aka dash and cap).
84
+ *
85
+ * @param float $x X coordinate of the arc
86
+ * @param float $y Y coordinate of the arc
87
+ * @param float $r1 Radius 1
88
+ * @param float $r2 Radius 2
89
+ * @param float $astart Start angle in degrees
90
+ * @param float $aend End angle in degrees
91
+ * @param array $color Color array in the format `[r, g, b, "alpha" => alpha]`
92
+ * where r, g, b, and alpha are float values between 0 and 1
93
+ * @param float $width
94
+ * @param array $style
95
+ * @param string $cap `butt`, `round`, or `square`
96
+ */
97
+ function arc($x, $y, $r1, $r2, $astart, $aend, $color, $width, $style = [], $cap = "butt");
98
 
99
  /**
100
  * Draws a rectangle at x1,y1 with width w and height h
101
  *
102
+ * See {@link Cpdf::setLineStyle()} for a description of the format of the
103
+ * $style and $cap parameters (aka dash and cap).
 
104
  *
105
+ * @param float $x1
106
+ * @param float $y1
107
+ * @param float $w
108
+ * @param float $h
109
+ * @param array $color Color array in the format `[r, g, b, "alpha" => alpha]`
110
+ * where r, g, b, and alpha are float values between 0 and 1
111
+ * @param float $width
112
+ * @param array $style
113
+ * @param string $cap `butt`, `round`, or `square`
114
  */
115
+ function rectangle($x1, $y1, $w, $h, $color, $width, $style = [], $cap = "butt");
116
 
117
  /**
118
  * Draws a filled rectangle at x1,y1 with width w and height h
119
  *
 
 
120
  * @param float $x1
121
  * @param float $y1
122
  * @param float $w
123
  * @param float $h
124
+ * @param array $color Color array in the format `[r, g, b, "alpha" => alpha]`
125
+ * where r, g, b, and alpha are float values between 0 and 1
126
  */
127
  function filled_rectangle($x1, $y1, $w, $h, $color);
128
 
147
  * @param float $tr
148
  * @param float $br
149
  * @param float $bl
 
 
150
  */
151
  function clipping_roundrectangle($x1, $y1, $w, $h, $tl, $tr, $br, $bl);
152
 
153
+ /**
154
+ * Starts a clipping polygon
155
+ *
156
+ * @param float[] $points
157
+ */
158
+ public function clipping_polygon(array $points): void;
159
+
160
  /**
161
  * Ends the last clipping shape
162
  */
163
  function clipping_end();
164
 
165
  /**
166
+ * Processes a callback on every page.
167
  *
168
+ * The callback function receives the four parameters `int $pageNumber`,
169
+ * `int $pageCount`, `Canvas $canvas`, and `FontMetrics $fontMetrics`, in
170
+ * that order.
171
  *
172
  * This function can be used to add page numbers to all pages after the
173
  * first one, for example.
174
  *
175
  * @param callable $callback The callback function to process on every page
 
176
  */
177
+ public function page_script($callback): void;
178
 
179
  /**
180
+ * Writes text at the specified x and y coordinates on every page.
181
  *
182
  * The strings '{PAGE_NUM}' and '{PAGE_COUNT}' are automatically replaced
183
  * with their current values.
184
  *
 
 
185
  * @param float $x
186
  * @param float $y
187
+ * @param string $text The text to write
188
+ * @param string $font The font file to use
189
+ * @param float $size The font size, in points
190
+ * @param array $color Color array in the format `[r, g, b, "alpha" => alpha]`
191
+ * where r, g, b, and alpha are float values between 0 and 1
192
+ * @param float $word_space Word spacing adjustment
193
+ * @param float $char_space Char spacing adjustment
194
+ * @param float $angle Angle to write the text at, measured clockwise starting from the x-axis
195
  */
196
  public function page_text($x, $y, $text, $font, $size, $color = [0, 0, 0], $word_space = 0.0, $char_space = 0.0, $angle = 0.0);
197
 
198
  /**
199
+ * Draws a line at the specified coordinates on every page.
 
 
200
  *
201
  * @param float $x1
202
  * @param float $y1
203
  * @param float $x2
204
  * @param float $y2
205
+ * @param array $color Color array in the format `[r, g, b, "alpha" => alpha]`
206
+ * where r, g, b, and alpha are float values between 0 and 1
207
  * @param float $width
208
+ * @param array $style
 
209
  */
210
+ public function page_line($x1, $y1, $x2, $y2, $color, $width, $style = []);
211
 
212
  /**
213
  * Save current state
233
  *
234
  * @param float $angle_x
235
  * @param float $angle_y
236
+ * @param float $x Origin abscissa
237
+ * @param float $y Origin ordinate
238
  */
239
  function skew($angle_x, $angle_y, $x, $y);
240
 
259
  /**
260
  * Transform
261
  *
262
+ * @param float $a
263
+ * @param float $b
264
+ * @param float $c
265
+ * @param float $d
266
+ * @param float $e
267
+ * @param float $f
 
268
  */
269
  function transform($a, $b, $c, $d, $e, $f);
270
 
273
  *
274
  * The polygon is formed by joining all the points stored in the $points
275
  * array. $points has the following structure:
276
+ * ```
277
  * array(0 => x1,
278
  * 1 => y1,
279
  * 2 => x2,
280
  * 3 => y2,
281
  * ...
282
  * );
283
+ * ```
284
  *
285
+ * See {@link Cpdf::setLineStyle()} for a description of the format of the
286
+ * $style parameter (aka dash).
 
287
  *
288
  * @param array $points
289
+ * @param array $color Color array in the format `[r, g, b, "alpha" => alpha]`
290
+ * where r, g, b, and alpha are float values between 0 and 1
291
  * @param float $width
292
  * @param array $style
293
+ * @param bool $fill Fills the polygon if true
294
  */
295
+ function polygon($points, $color, $width = null, $style = [], $fill = false);
296
 
297
  /**
298
  * Draws a circle at $x,$y with radius $r
299
  *
300
+ * See {@link Cpdf::setLineStyle()} for a description of the format of the
301
+ * $style parameter (aka dash).
 
302
  *
303
  * @param float $x
304
  * @param float $y
305
  * @param float $r
306
+ * @param array $color Color array in the format `[r, g, b, "alpha" => alpha]`
307
+ * where r, g, b, and alpha are float values between 0 and 1
308
  * @param float $width
309
  * @param array $style
310
+ * @param bool $fill Fills the circle if true
311
  */
312
+ function circle($x, $y, $r, $color, $width = null, $style = [], $fill = false);
313
 
314
  /**
315
  * Add an image to the pdf.
317
  * The image is placed at the specified x and y coordinates with the
318
  * given width and height.
319
  *
320
+ * @param string $img The path to the image
321
+ * @param float $x X position
322
+ * @param float $y Y position
323
+ * @param float $w Width
324
+ * @param float $h Height
325
  * @param string $resolution The resolution of the image
326
  */
327
+ function image($img, $x, $y, $w, $h, $resolution = "normal");
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
328
 
329
  /**
330
  * Writes text at the specified x and y coordinates
 
331
  *
332
+ * @param float $x
333
+ * @param float $y
334
+ * @param string $text The text to write
335
+ * @param string $font The font file to use
336
+ * @param float $size The font size, in points
337
+ * @param array $color Color array in the format `[r, g, b, "alpha" => alpha]`
338
+ * where r, g, b, and alpha are float values between 0 and 1
339
+ * @param float $word_space Word spacing adjustment
340
+ * @param float $char_space Char spacing adjustment
341
+ * @param float $angle Angle to write the text at, measured clockwise starting from the x-axis
342
  */
343
  function text($x, $y, $text, $font, $size, $color = [0, 0, 0], $word_space = 0.0, $char_space = 0.0, $angle = 0.0);
344
 
352
  /**
353
  * Add a link to the pdf
354
  *
355
+ * @param string $url The url to link to
356
+ * @param float $x The x position of the link
357
+ * @param float $y The y position of the link
358
+ * @param float $width The width of the link
359
+ * @param float $height The height of the link
360
  */
361
  function add_link($url, $x, $y, $width, $height);
362
 
363
  /**
364
+ * Add meta information to the PDF.
365
  *
366
+ * @param string $label Label of the value (Creator, Producer, etc.)
367
  * @param string $value The text to set
368
  */
369
+ public function add_info(string $label, string $value): void;
370
 
371
  /**
372
  * Calculates text size, in points
373
  *
374
+ * @param string $text The text to be sized
375
+ * @param string $font The font file to use
376
+ * @param float $size The font size, in points
377
+ * @param float $word_spacing Word spacing, if any
378
+ * @param float $char_spacing Char spacing, if any
379
  *
380
  * @return float
381
  */
384
  /**
385
  * Calculates font height, in points
386
  *
387
+ * @param string $font The font file to use
388
+ * @param float $size The font size, in points
389
  *
390
  * @return float
391
  */
392
  function get_font_height($font, $size);
393
 
394
  /**
395
+ * Returns the font x-height, in points
396
  *
397
+ * @param string $font The font file to use
398
+ * @param float $size The font size, in points
399
  *
400
  * @return float
401
  */
402
+ //function get_font_x_height($font, $size);
403
 
404
  /**
405
+ * Calculates font baseline, in points
406
+ *
407
+ * @param string $font The font file to use
408
+ * @param float $size The font size, in points
409
  *
410
  * @return float
411
  */
412
+ function get_font_baseline($font, $size);
 
413
 
414
  /**
415
+ * Returns the PDF's width in points
416
  *
417
  * @return float
418
  */
419
+ function get_width();
420
 
421
  /**
422
+ * Returns the PDF's height in points
 
 
 
423
  *
424
  * @return float
425
  */
426
+ function get_height();
427
 
428
  /**
429
  * Sets the opacity
430
  *
431
+ * @param float $opacity
432
  * @param string $mode
433
  */
434
+ public function set_opacity(float $opacity, string $mode = "Normal"): void;
435
 
436
  /**
437
  * Sets the default view
446
  * 'FitBH' top
447
  * 'FitBV' left
448
  * @param array $options
 
 
449
  */
450
  function set_default_view($view, $options = []);
451
 
452
  /**
453
+ * @param string $code
 
 
454
  */
455
+ function javascript($code);
456
 
457
  /**
458
  * Starts a new page
462
  function new_page();
463
 
464
  /**
465
+ * Streams the PDF to the client.
466
  *
467
+ * @param string $filename The filename to present to the client.
468
+ * @param array $options Associative array: 'compress' => 1 or 0 (default 1); 'Attachment' => 1 or 0 (default 1).
469
  */
470
  function stream($filename, $options = []);
471
 
473
  * Returns the PDF as a string.
474
  *
475
  * @param array $options Associative array: 'compress' => 1 or 0 (default 1).
476
+ *
477
  * @return string
478
  */
479
  function output($options = []);
vendor/dompdf/dompdf/src/Cellmap.php CHANGED
@@ -23,10 +23,8 @@ class Cellmap
23
  {
24
  /**
25
  * Border style weight lookup for collapsed border resolution.
26
- *
27
- * @var array
28
  */
29
- protected static $_BORDER_STYLE_SCORE = [
30
  "double" => 8,
31
  "solid" => 7,
32
  "dashed" => 6,
@@ -131,10 +129,7 @@ class Cellmap
131
  $this->reset();
132
  }
133
 
134
- /**
135
- *
136
- */
137
- public function reset()
138
  {
139
  $this->_num_rows = 0;
140
  $this->_num_cols = 0;
@@ -153,10 +148,7 @@ class Cellmap
153
  $this->__col = $this->__row = 0;
154
  }
155
 
156
- /**
157
- *
158
- */
159
- public function lock_columns()
160
  {
161
  $this->_columns_locked = true;
162
  }
@@ -170,9 +162,9 @@ class Cellmap
170
  }
171
 
172
  /**
173
- * @param $fixed
174
  */
175
- public function set_layout_fixed($fixed)
176
  {
177
  $this->_fixed_layout = $fixed;
178
  }
@@ -456,12 +448,12 @@ class Cellmap
456
  /**
457
  * https://www.w3.org/TR/CSS21/tables.html#border-conflict-resolution
458
  *
459
- * @param int $i
460
- * @param int $j
461
- * @param string $h_v
462
- * @param array $border_spec
463
  */
464
- protected function _resolve_border($i, $j, $h_v, $border_spec)
465
  {
466
  if (!isset($this->_borders[$i][$j][$h_v])) {
467
  $this->_borders[$i][$j][$h_v] = $border_spec;
@@ -483,9 +475,9 @@ class Cellmap
483
  // width here, as its resolved width is always 0
484
  if ($n_style === "hidden" || $n_width > $o_width
485
  || ($o_width == $n_width
486
- && isset(self::$_BORDER_STYLE_SCORE[$n_style])
487
- && isset(self::$_BORDER_STYLE_SCORE[$o_style])
488
- && self::$_BORDER_STYLE_SCORE[$n_style] > self::$_BORDER_STYLE_SCORE[$o_style])
489
  ) {
490
  $this->_borders[$i][$j][$h_v] = $border_spec;
491
  }
@@ -554,7 +546,7 @@ class Cellmap
554
  // Recursively add the frames within the table, its row groups and rows
555
  if ($frame === $this->_table
556
  || $display === "table-row"
557
- || in_array($display, TableFrameDecorator::$ROW_GROUPS, true)
558
  ) {
559
  $start_row = $this->__row;
560
 
@@ -579,14 +571,14 @@ class Cellmap
579
 
580
  // Resolve vertical borders
581
  for ($i = 0; $i < $num_rows + 1; $i++) {
582
- $this->_resolve_border($start_row + $i, 0, "vertical", $bp["left"]);
583
- $this->_resolve_border($start_row + $i, $this->_num_cols, "vertical", $bp["right"]);
584
  }
585
 
586
  // Resolve horizontal borders
587
  for ($j = 0; $j < $this->_num_cols; $j++) {
588
- $this->_resolve_border($start_row, $j, "horizontal", $bp["top"]);
589
- $this->_resolve_border($this->__row, $j, "horizontal", $bp["bottom"]);
590
  }
591
 
592
  if ($frame === $this->_table) {
@@ -644,8 +636,8 @@ class Cellmap
644
 
645
  if ($collapse) {
646
  // Resolve vertical borders
647
- $this->_resolve_border($row, $this->__col, "vertical", $bp["left"]);
648
- $this->_resolve_border($row, $this->__col + $colspan, "vertical", $bp["right"]);
649
  }
650
  }
651
 
@@ -656,8 +648,8 @@ class Cellmap
656
 
657
  if ($collapse) {
658
  // Resolve horizontal borders
659
- $this->_resolve_border($this->__row, $col, "horizontal", $bp["top"]);
660
- $this->_resolve_border($this->__row + $rowspan, $col, "horizontal", $bp["bottom"]);
661
  }
662
  }
663
 
@@ -684,11 +676,8 @@ class Cellmap
684
  } else {
685
  // The additional 1/2 width gets added to the table proper
686
  [$h, $v] = $table_style->border_spacing;
687
-
688
- $v = $table_style->length_in_pt($v);
689
- $h = $table_style->length_in_pt($h);
690
- $v_spacing = is_numeric($v) ? $v / 2 : $v;
691
- $h_spacing = is_numeric($v) ? $h / 2 : $h;
692
  }
693
 
694
  foreach ($this->_frames as $frame_info) {
@@ -790,12 +779,19 @@ class Cellmap
790
  }
791
  }
792
  }
 
 
 
 
 
 
 
 
 
 
793
  }
794
 
795
- /**
796
- *
797
- */
798
- public function add_row()
799
  {
800
  $this->__row++;
801
  $this->_num_rows++;
@@ -899,10 +895,7 @@ class Cellmap
899
  $this->_frames[$g_key]["rows"] = range($first_index, $last_index);
900
  }
901
 
902
- /**
903
- *
904
- */
905
- public function assign_x_positions()
906
  {
907
  // Pre-condition: widths must be resolved and assigned to columns and
908
  // column[0]["x"] must be set.
@@ -918,17 +911,14 @@ class Cellmap
918
  }
919
  }
920
 
921
- /**
922
- *
923
- */
924
- public function assign_frame_heights()
925
  {
926
  // Pre-condition: widths and heights of each column & row must be
927
  // calcluated
928
  foreach ($this->_frames as $arr) {
929
  $frame = $arr["frame"];
930
 
931
- $h = 0;
932
  foreach ($arr["rows"] as $row) {
933
  if (!isset($this->_rows[$row])) {
934
  // The row has been removed because of a page split, so skip it.
@@ -941,7 +931,7 @@ class Cellmap
941
  if ($frame instanceof TableCellFrameDecorator) {
942
  $frame->set_cell_height($h);
943
  } else {
944
- $frame->get_style()->height = $h;
945
  }
946
  }
947
  }
@@ -949,13 +939,13 @@ class Cellmap
949
  /**
950
  * Re-adjust frame height if the table height is larger than its content
951
  */
952
- public function set_frame_heights($table_height, $content_height)
953
  {
954
  // Distribute the increased height proportionally amongst each row
955
  foreach ($this->_frames as $arr) {
956
  $frame = $arr["frame"];
957
 
958
- $h = 0;
959
  foreach ($arr["rows"] as $row) {
960
  if (!isset($this->_rows[$row])) {
961
  continue;
@@ -967,13 +957,13 @@ class Cellmap
967
  if ($content_height > 0) {
968
  $new_height = ($h / $content_height) * $table_height;
969
  } else {
970
- $new_height = 0;
971
  }
972
 
973
  if ($frame instanceof TableCellFrameDecorator) {
974
  $frame->set_cell_height($new_height);
975
  } else {
976
- $frame->get_style()->height = $new_height;
977
  }
978
  }
979
  }
@@ -983,7 +973,7 @@ class Cellmap
983
  *
984
  * @return string
985
  */
986
- public function __toString()
987
  {
988
  $str = "";
989
  $str .= "Columns:<br/>";
23
  {
24
  /**
25
  * Border style weight lookup for collapsed border resolution.
 
 
26
  */
27
+ protected const BORDER_STYLE_SCORE = [
28
  "double" => 8,
29
  "solid" => 7,
30
  "dashed" => 6,
129
  $this->reset();
130
  }
131
 
132
+ public function reset(): void
 
 
 
133
  {
134
  $this->_num_rows = 0;
135
  $this->_num_cols = 0;
148
  $this->__col = $this->__row = 0;
149
  }
150
 
151
+ public function lock_columns(): void
 
 
 
152
  {
153
  $this->_columns_locked = true;
154
  }
162
  }
163
 
164
  /**
165
+ * @param bool $fixed
166
  */
167
+ public function set_layout_fixed(bool $fixed)
168
  {
169
  $this->_fixed_layout = $fixed;
170
  }
448
  /**
449
  * https://www.w3.org/TR/CSS21/tables.html#border-conflict-resolution
450
  *
451
+ * @param int $i
452
+ * @param int $j
453
+ * @param string $h_v `horizontal` or `vertical`
454
+ * @param array $border_spec
455
  */
456
+ protected function resolve_border(int $i, int $j, string $h_v, array $border_spec): void
457
  {
458
  if (!isset($this->_borders[$i][$j][$h_v])) {
459
  $this->_borders[$i][$j][$h_v] = $border_spec;
475
  // width here, as its resolved width is always 0
476
  if ($n_style === "hidden" || $n_width > $o_width
477
  || ($o_width == $n_width
478
+ && isset(self::BORDER_STYLE_SCORE[$n_style])
479
+ && isset(self::BORDER_STYLE_SCORE[$o_style])
480
+ && self::BORDER_STYLE_SCORE[$n_style] > self::BORDER_STYLE_SCORE[$o_style])
481
  ) {
482
  $this->_borders[$i][$j][$h_v] = $border_spec;
483
  }
546
  // Recursively add the frames within the table, its row groups and rows
547
  if ($frame === $this->_table
548
  || $display === "table-row"
549
+ || in_array($display, TableFrameDecorator::ROW_GROUPS, true)
550
  ) {
551
  $start_row = $this->__row;
552
 
571
 
572
  // Resolve vertical borders
573
  for ($i = 0; $i < $num_rows + 1; $i++) {
574
+ $this->resolve_border($start_row + $i, 0, "vertical", $bp["left"]);
575
+ $this->resolve_border($start_row + $i, $this->_num_cols, "vertical", $bp["right"]);
576
  }
577
 
578
  // Resolve horizontal borders
579
  for ($j = 0; $j < $this->_num_cols; $j++) {
580
+ $this->resolve_border($start_row, $j, "horizontal", $bp["top"]);
581
+ $this->resolve_border($this->__row, $j, "horizontal", $bp["bottom"]);
582
  }
583
 
584
  if ($frame === $this->_table) {
636
 
637
  if ($collapse) {
638
  // Resolve vertical borders
639
+ $this->resolve_border($row, $this->__col, "vertical", $bp["left"]);
640
+ $this->resolve_border($row, $this->__col + $colspan, "vertical", $bp["right"]);
641
  }
642
  }
643
 
648
 
649
  if ($collapse) {
650
  // Resolve horizontal borders
651
+ $this->resolve_border($this->__row, $col, "horizontal", $bp["top"]);
652
+ $this->resolve_border($this->__row + $rowspan, $col, "horizontal", $bp["bottom"]);
653
  }
654
  }
655
 
676
  } else {
677
  // The additional 1/2 width gets added to the table proper
678
  [$h, $v] = $table_style->border_spacing;
679
+ $v_spacing = $v / 2;
680
+ $h_spacing = $h / 2;
 
 
 
681
  }
682
 
683
  foreach ($this->_frames as $frame_info) {
779
  }
780
  }
781
  }
782
+
783
+ // Adjust absolute columns so that the absolute (and max) width is the
784
+ // largest minimum width of all cells. This accounts for cells without
785
+ // absolute width within an absolute column
786
+ foreach ($this->_columns as &$col) {
787
+ if ($col["absolute"] > 0) {
788
+ $col["absolute"] = $col["min-width"];
789
+ $col["max-width"] = $col["min-width"];
790
+ }
791
+ }
792
  }
793
 
794
+ protected function add_row(): void
 
 
 
795
  {
796
  $this->__row++;
797
  $this->_num_rows++;
895
  $this->_frames[$g_key]["rows"] = range($first_index, $last_index);
896
  }
897
 
898
+ public function assign_x_positions(): void
 
 
 
899
  {
900
  // Pre-condition: widths must be resolved and assigned to columns and
901
  // column[0]["x"] must be set.
911
  }
912
  }
913
 
914
+ public function assign_frame_heights(): void
 
 
 
915
  {
916
  // Pre-condition: widths and heights of each column & row must be
917
  // calcluated
918
  foreach ($this->_frames as $arr) {
919
  $frame = $arr["frame"];
920
 
921
+ $h = 0.0;
922
  foreach ($arr["rows"] as $row) {
923
  if (!isset($this->_rows[$row])) {
924
  // The row has been removed because of a page split, so skip it.
931
  if ($frame instanceof TableCellFrameDecorator) {
932
  $frame->set_cell_height($h);
933
  } else {
934
+ $frame->get_style()->set_used("height", $h);
935
  }
936
  }
937
  }
939
  /**
940
  * Re-adjust frame height if the table height is larger than its content
941
  */
942
+ public function set_frame_heights(float $table_height, float $content_height): void
943
  {
944
  // Distribute the increased height proportionally amongst each row
945
  foreach ($this->_frames as $arr) {
946
  $frame = $arr["frame"];
947
 
948
+ $h = 0.0;
949
  foreach ($arr["rows"] as $row) {
950
  if (!isset($this->_rows[$row])) {
951
  continue;
957
  if ($content_height > 0) {
958
  $new_height = ($h / $content_height) * $table_height;
959
  } else {
960
+ $new_height = 0.0;
961
  }
962
 
963
  if ($frame instanceof TableCellFrameDecorator) {
964
  $frame->set_cell_height($new_height);
965
  } else {
966
+ $frame->get_style()->set_used("height", $new_height);
967
  }
968
  }
969
  }
973
  *
974
  * @return string
975
  */
976
+ public function __toString(): string
977
  {
978
  $str = "";
979
  $str .= "Columns:<br/>";
vendor/dompdf/dompdf/src/Css/Color.php CHANGED
@@ -165,7 +165,7 @@ class Color
165
  ];
166
 
167
  /**
168
- * @param $color
169
  * @return array|string|null
170
  */
171
  static function parse($color)
@@ -261,7 +261,7 @@ class Color
261
 
262
  // Parse alpha value
263
  if (Helpers::is_percent($alpha)) {
264
- $alpha = round((float) $alpha / 100, 2);
265
  } else {
266
  $alpha = (float) $alpha;
267
  }
@@ -294,7 +294,7 @@ class Color
294
  return null;
295
  }
296
 
297
- $values = array_map(function($c) {
298
  return min(1.0, max(0.0, floatval(trim($c))));
299
  }, $values);
300
 
@@ -306,7 +306,7 @@ class Color
306
  }
307
 
308
  /**
309
- * @param $color
310
  * @param float $alpha
311
  * @return array
312
  */
165
  ];
166
 
167
  /**
168
+ * @param array|string|null $color
169
  * @return array|string|null
170
  */
171
  static function parse($color)
261
 
262
  // Parse alpha value
263
  if (Helpers::is_percent($alpha)) {
264
+ $alpha = (float) $alpha / 100;
265
  } else {
266
  $alpha = (float) $alpha;
267
  }
294
  return null;
295
  }
296
 
297
+ $values = array_map(function ($c) {
298
  return min(1.0, max(0.0, floatval(trim($c))));
299
  }, $values);
300
 
306
  }
307
 
308
  /**
309
+ * @param array|string $color
310
  * @param float $alpha
311
  * @return array
312
  */
vendor/dompdf/dompdf/src/Css/Style.php CHANGED
@@ -22,36 +22,182 @@ use Dompdf\Frame;
22
  * It includes methods to resolve colors and lengths, as well as getters &
23
  * setters for many CSS properties.
24
  *
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
25
  * Actual CSS parsing is performed in the {@link Stylesheet} class.
26
  *
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
27
  * @package dompdf
28
  */
29
  class Style
30
  {
31
- const CSS_IDENTIFIER = "-?[_a-zA-Z]+[_a-zA-Z0-9-]*";
32
- const CSS_INTEGER = "[+-]?\d+";
33
- const CSS_NUMBER = "[+-]?\d*\.?\d+";
34
 
35
  /**
36
  * Default font size, in points.
37
  *
38
  * @var float
39
  */
40
- static $default_font_size = 12;
41
 
42
  /**
43
  * Default line height, as a fraction of the font size.
44
  *
45
  * @var float
46
  */
47
- static $default_line_height = 1.2;
48
 
49
  /**
50
  * Default "absolute" font sizes relative to the default font-size
51
- * http://www.w3.org/TR/css3-fonts/#font-size-the-font-size-property
 
52
  * @var array<float>
53
  */
54
- static $font_size_keywords = [
55
  "xx-small" => 0.6, // 3/5
56
  "x-small" => 0.75, // 3/4
57
  "small" => 0.889, // 8/9
@@ -62,19 +208,15 @@ class Style
62
  ];
63
 
64
  /**
65
- * List of valid text-align keywords. Should also really be a constant.
66
- *
67
- * @var array
68
  */
69
- static $text_align_keywords = ["left", "right", "center", "justify"];
70
 
71
  /**
72
- * List of valid vertical-align keywords. Should also really be a constant.
73
- *
74
- * @var array
75
  */
76
- static $vertical_align_keywords = ["baseline", "bottom", "middle", "sub",
77
- "super", "text-bottom", "text-top", "top"];
78
 
79
  /**
80
  * List of all block-level (outer) display types.
@@ -119,25 +261,19 @@ class Style
119
  ];
120
 
121
  /**
122
- * List of all inline (inner) display types. Should really be a constant.
123
- *
124
- * @var array
125
  */
126
- static $INLINE_TYPES = ["inline"];
127
 
128
  /**
129
- * List of all block (inner) display types. Should really be a constant.
130
- *
131
- * @var array
132
  */
133
- static $BLOCK_TYPES = ["block", "inline-block", "table-cell", "list-item"];
134
 
135
  /**
136
- * List of all table (inner) display types. Should really be a constant.
137
- *
138
- * @var array
139
  */
140
- static $TABLE_TYPES = ["table", "inline-table"];
141
 
142
  /**
143
  * Lookup table for valid display types. Initially computed from the
@@ -148,19 +284,30 @@ class Style
148
  protected static $valid_display_types = [];
149
 
150
  /**
151
- * List of all positioned types. Should really be a constant.
152
- *
153
- * @var array
 
 
 
154
  */
155
- static $POSITIONNED_TYPES = ["relative", "absolute", "fixed"];
 
 
 
 
156
 
157
  /**
158
- * List of valid border styles. Should also really be a constant.
 
159
  *
160
- * @var array
161
  */
162
- static $BORDER_STYLES = ["none", "hidden", "dotted", "dashed", "solid",
163
- "double", "groove", "ridge", "inset", "outset"];
 
 
 
164
 
165
  /**
166
  * Map of CSS shorthand properties and their corresponding sub-properties.
@@ -181,9 +328,18 @@ class Style
181
  "background_color"
182
  ],
183
  "border" => [
184
- "border_width",
185
- "border_style",
186
- "border_color"
 
 
 
 
 
 
 
 
 
187
  ],
188
  "border_top" => [
189
  "border_top_width",
@@ -238,6 +394,12 @@ class Style
238
  "font_weight",
239
  "line_height"
240
  ],
 
 
 
 
 
 
241
  "list_style" => [
242
  "list_style_image",
243
  "list_style_position",
@@ -278,7 +440,7 @@ class Style
278
  /**
279
  * Default style values.
280
  *
281
- * @link http://www.w3.org/TR/CSS21/propidx.html
282
  *
283
  * @var array
284
  */
@@ -287,7 +449,7 @@ class Style
287
  /**
288
  * List of inherited properties
289
  *
290
- * @link http://www.w3.org/TR/CSS21/propidx.html
291
  *
292
  * @var array
293
  */
@@ -315,19 +477,20 @@ class Style
315
  protected $_media_queries;
316
 
317
  /**
318
- * Specified (or declared) values of the CSS properties.
319
- * https://www.w3.org/TR/css-cascade-3/#value-stages
320
  *
321
  * @var array
322
  */
323
- protected $_props = [];
324
 
325
  /**
326
- * Properties set by an `!important` declaration.
 
 
327
  *
328
  * @var array
329
  */
330
- protected $_important_props = [];
331
 
332
  /**
333
  * Computed values of the CSS properties.
@@ -341,7 +504,15 @@ class Style
341
  *
342
  * @var array
343
  */
344
- protected $_prop_cache = [];
 
 
 
 
 
 
 
 
345
 
346
  protected static $_dependency_map = [
347
  "border_top_style" => [
@@ -366,6 +537,11 @@ class Style
366
  "border_right_width",
367
  "border_bottom_width",
368
  "border_left_width",
 
 
 
 
 
369
  "line_height",
370
  "margin_top",
371
  "margin_right",
@@ -376,7 +552,14 @@ class Style
376
  "padding_top",
377
  "padding_right",
378
  "padding_bottom",
379
- "padding_left"
 
 
 
 
 
 
 
380
  ],
381
  "float" => [
382
  "display"
@@ -405,7 +588,7 @@ class Style
405
  protected $parent_style;
406
 
407
  /**
408
- * @var Frame
409
  */
410
  protected $_frame;
411
 
@@ -416,19 +599,20 @@ class Style
416
  */
417
  protected $_origin = Stylesheet::ORIG_AUTHOR;
418
 
419
- // private members
420
  /**
421
  * The computed bottom spacing
 
 
422
  */
423
  private $_computed_bottom_spacing = null;
424
 
425
  /**
426
- * @var bool
427
  */
428
  private $has_border_radius_cache = null;
429
 
430
  /**
431
- * @var array
432
  */
433
  private $resolved_border_radius = null;
434
 
@@ -438,14 +622,12 @@ class Style
438
  private $fontMetrics;
439
 
440
  /**
441
- * Class constructor
442
- *
443
- * @param Stylesheet $stylesheet the stylesheet this Style is associated with.
444
- * @param int $origin
445
  */
446
- public function __construct(Stylesheet $stylesheet, $origin = Stylesheet::ORIG_AUTHOR)
447
  {
448
- $this->setFontMetrics($stylesheet->getFontMetrics());
449
 
450
  $this->_stylesheet = $stylesheet;
451
  $this->_media_queries = [];
@@ -458,17 +640,20 @@ class Style
458
  $d =& self::$_defaults;
459
 
460
  // All CSS 2.1 properties, and their default values
 
 
 
461
  $d["azimuth"] = "center";
462
  $d["background_attachment"] = "scroll";
463
  $d["background_color"] = "transparent";
464
  $d["background_image"] = "none";
465
  $d["background_image_resolution"] = "normal";
466
- $d["background_position"] = "0% 0%";
467
  $d["background_repeat"] = "repeat";
468
  $d["background"] = "";
469
  $d["border_collapse"] = "separate";
470
  $d["border_color"] = "";
471
- $d["border_spacing"] = "0";
472
  $d["border_style"] = "";
473
  $d["border_top"] = "";
474
  $d["border_right"] = "";
@@ -487,10 +672,10 @@ class Style
487
  $d["border_bottom_width"] = "medium";
488
  $d["border_left_width"] = "medium";
489
  $d["border_width"] = "";
490
- $d["border_bottom_left_radius"] = "0";
491
- $d["border_bottom_right_radius"] = "0";
492
- $d["border_top_left_radius"] = "0";
493
- $d["border_top_right_radius"] = "0";
494
  $d["border_radius"] = "";
495
  $d["border"] = "";
496
  $d["bottom"] = "auto";
@@ -518,6 +703,7 @@ class Style
518
  $d["font"] = "";
519
  $d["height"] = "auto";
520
  $d["image_resolution"] = "normal";
 
521
  $d["left"] = "auto";
522
  $d["letter_spacing"] = "normal";
523
  $d["line_height"] = "normal";
@@ -525,27 +711,27 @@ class Style
525
  $d["list_style_position"] = "outside";
526
  $d["list_style_type"] = "disc";
527
  $d["list_style"] = "";
528
- $d["margin_right"] = "0";
529
- $d["margin_left"] = "0";
530
- $d["margin_top"] = "0";
531
- $d["margin_bottom"] = "0";
532
  $d["margin"] = "";
533
  $d["max_height"] = "none";
534
  $d["max_width"] = "none";
535
  $d["min_height"] = "auto";
536
  $d["min_width"] = "auto";
537
- $d["orphans"] = "2";
538
  $d["outline_color"] = "currentcolor"; // "invert" special color is not supported
539
  $d["outline_style"] = "none";
540
  $d["outline_width"] = "medium";
541
- $d["outline_offset"] = "0";
542
  $d["outline"] = "";
543
  $d["overflow"] = "visible";
544
  $d["overflow_wrap"] = "normal";
545
- $d["padding_top"] = "0";
546
- $d["padding_right"] = "0";
547
- $d["padding_bottom"] = "0";
548
- $d["padding_left"] = "0";
549
  $d["padding"] = "";
550
  $d["page_break_after"] = "auto";
551
  $d["page_break_before"] = "auto";
@@ -570,7 +756,7 @@ class Style
570
  $d["table_layout"] = "auto";
571
  $d["text_align"] = "";
572
  $d["text_decoration"] = "none";
573
- $d["text_indent"] = "0";
574
  $d["text_transform"] = "none";
575
  $d["top"] = "auto";
576
  $d["unicode_bidi"] = "normal";
@@ -579,14 +765,15 @@ class Style
579
  $d["voice_family"] = "";
580
  $d["volume"] = "medium";
581
  $d["white_space"] = "normal";
582
- $d["widows"] = "2";
583
  $d["width"] = "auto";
 
584
  $d["word_spacing"] = "normal";
585
  $d["z_index"] = "auto";
586
 
587
  // CSS3
588
- $d["opacity"] = "1.0";
589
- $d["background_size"] = "auto auto";
590
  $d["transform"] = "none";
591
  $d["transform_origin"] = "50% 50%";
592
 
@@ -642,6 +829,7 @@ class Style
642
  "volume",
643
  "white_space",
644
  "widows",
 
645
  "word_spacing",
646
  ];
647
 
@@ -673,24 +861,31 @@ class Style
673
  }
674
 
675
  /**
676
- * "Destructor": forcibly free all references held by this object
 
 
677
  */
678
- function dispose()
679
  {
 
 
 
 
 
680
  }
681
 
682
  /**
683
- * @param $media_queries
684
  */
685
- function set_media_queries($media_queries)
686
  {
687
  $this->_media_queries = $media_queries;
688
  }
689
 
690
  /**
691
- * @return array|int
692
  */
693
- function get_media_queries()
694
  {
695
  return $this->_media_queries;
696
  }
@@ -698,23 +893,23 @@ class Style
698
  /**
699
  * @param Frame $frame
700
  */
701
- function set_frame(Frame $frame)
702
  {
703
  $this->_frame = $frame;
704
  }
705
 
706
  /**
707
- * @return Frame
708
  */
709
- function get_frame()
710
  {
711
  return $this->_frame;
712
  }
713
 
714
  /**
715
- * @param $origin
716
  */
717
- function set_origin($origin)
718
  {
719
  $this->_origin = $origin;
720
  }
@@ -722,17 +917,17 @@ class Style
722
  /**
723
  * @return int
724
  */
725
- function get_origin()
726
  {
727
  return $this->_origin;
728
  }
729
 
730
  /**
731
- * returns the {@link Stylesheet} this Style is associated with.
732
  *
733
  * @return Stylesheet
734
  */
735
- function get_stylesheet()
736
  {
737
  return $this->_stylesheet;
738
  }
@@ -764,12 +959,12 @@ class Style
764
  *
765
  * @return float|string
766
  */
767
- function length_in_pt($length, ?float $ref_size = null)
768
  {
769
  $font_size = $this->__get("font_size");
770
  $ref_size = $ref_size ?? $font_size;
771
 
772
- if (!is_array($length)) {
773
  $length = [$length];
774
  }
775
 
@@ -787,13 +982,7 @@ class Style
787
  }
788
 
789
  $val = $this->single_length_in_pt((string) $l, $ref_size, $font_size);
790
-
791
- // FIXME: Using the ref size as fallback here currently ensures that
792
- // invalid widths or heights are treated as the corresponding
793
- // containing-block dimension, which can look like the declaration
794
- // is being ignored. Implement proper compute methods instead, and
795
- // fall back to 0 here
796
- $ret += $val ?? $ref_size;
797
  }
798
 
799
  return $ret;
@@ -816,60 +1005,78 @@ class Style
816
 
817
  $key = "$l/$ref_size/$font_size";
818
 
819
- if (isset($cache[$key])) {
820
  return $cache[$key];
821
  }
822
 
823
- if (is_numeric($l)) {
 
 
 
 
 
 
 
 
 
 
824
  // Legacy support for unitless values, not covered by spec. Might
825
  // want to restrict this to unitless `0` in the future
826
- $value = (float) $l;
827
  }
828
 
829
- elseif (($i = mb_stripos($l, "%")) !== false) {
830
- $value = (float)mb_substr($l, 0, $i) / 100 * $ref_size;
831
  }
832
 
833
- elseif (($i = mb_stripos($l, "px")) !== false) {
834
  $dpi = $this->_stylesheet->get_dompdf()->getOptions()->getDpi();
835
- $value = ((float)mb_substr($l, 0, $i) * 72) / $dpi;
836
  }
837
 
838
- elseif (($i = mb_stripos($l, "pt")) !== false) {
839
- $value = (float)mb_substr($l, 0, $i);
840
  }
841
 
842
- elseif (($i = mb_stripos($l, "rem")) !== false) {
843
- $root_style = $this->_stylesheet->get_dompdf()->getTree()->get_root()->get_style();
 
844
  $root_font_size = $root_style === null || $root_style === $this
845
  ? $font_size
846
- : $root_style->font_size;
847
- $value = (float)mb_substr($l, 0, $i) * $root_font_size;
 
 
 
 
 
 
 
848
  }
849
 
850
- elseif (($i = mb_stripos($l, "em")) !== false) {
851
- $value = (float)mb_substr($l, 0, $i) * $font_size;
852
  }
853
 
854
- elseif (($i = mb_stripos($l, "cm")) !== false) {
855
- $value = (float)mb_substr($l, 0, $i) * 72 / 2.54;
856
  }
857
 
858
- elseif (($i = mb_stripos($l, "mm")) !== false) {
859
- $value = (float)mb_substr($l, 0, $i) * 72 / 25.4;
860
  }
861
 
862
- elseif (($i = mb_stripos($l, "ex")) !== false) {
863
  // FIXME: em:ex ratio?
864
- $value = (float)mb_substr($l, 0, $i) * $font_size / 2;
865
  }
866
 
867
- elseif (($i = mb_stripos($l, "in")) !== false) {
868
- $value = (float)mb_substr($l, 0, $i) * 72;
869
  }
870
 
871
- elseif (($i = mb_stripos($l, "pc")) !== false) {
872
- $value = (float)mb_substr($l, 0, $i) * 12;
873
  }
874
 
875
  else {
@@ -880,7 +1087,6 @@ class Style
880
  return $cache[$key] = $value;
881
  }
882
 
883
-
884
  /**
885
  * Resolve inherited property values using the provided parent style or the
886
  * default values, in case no parent style exists.
@@ -888,17 +1094,15 @@ class Style
888
  * https://www.w3.org/TR/css-cascade-3/#inheriting
889
  *
890
  * @param Style|null $parent
891
- *
892
- * @return Style
893
  */
894
- function inherit(?Style $parent = null)
895
  {
896
  $this->parent_style = $parent;
897
 
898
  // Clear the computed font size, as it might depend on the parent
899
  // font size
900
  unset($this->_props_computed["font_size"]);
901
- unset($this->_prop_cache["font_size"]);
902
 
903
  if ($parent) {
904
  foreach (self::$_inherited as $prop) {
@@ -908,15 +1112,13 @@ class Style
908
  if (isset($this->_props[$prop]) || isset(self::$_props_shorthand[$prop])) {
909
  continue;
910
  }
911
-
912
  if (isset($parent->_props[$prop])) {
913
- $parent_val = \array_key_exists($prop, $parent->_props_computed)
914
- ? $parent->_props_computed[$prop]
915
- : $parent->compute_prop($prop, $parent->_props[$prop]);
916
-
917
  $this->_props[$prop] = $parent_val;
918
  $this->_props_computed[$prop] = $parent_val;
919
- $this->_prop_cache[$prop] = null;
920
  }
921
  }
922
  }
@@ -924,23 +1126,19 @@ class Style
924
  foreach ($this->_props as $prop => $val) {
925
  if ($val === "inherit") {
926
  if ($parent && isset($parent->_props[$prop])) {
927
- $parent_val = \array_key_exists($prop, $parent->_props_computed)
928
- ? $parent->_props_computed[$prop]
929
- : $parent->compute_prop($prop, $parent->_props[$prop]);
930
 
931
  $this->_props[$prop] = $parent_val;
932
  $this->_props_computed[$prop] = $parent_val;
933
- $this->_prop_cache[$prop] = null;
934
  } else {
935
  // Parent prop not set, use default
936
  $this->_props[$prop] = self::$_defaults[$prop];
937
  unset($this->_props_computed[$prop]);
938
- unset($this->_prop_cache[$prop]);
939
  }
940
  }
941
  }
942
-
943
- return $this;
944
  }
945
 
946
  /**
@@ -948,7 +1146,7 @@ class Style
948
  *
949
  * @param Style $style
950
  */
951
- function merge(Style $style)
952
  {
953
  foreach ($style->_props as $prop => $val) {
954
  $important = isset($style->_important_props[$prop]);
@@ -958,89 +1156,77 @@ class Style
958
  continue;
959
  }
960
 
961
- $computed = \array_key_exists($prop, $style->_props_computed)
962
- ? $style->_props_computed[$prop]
963
- : $style->compute_prop($prop, $val);
964
-
965
- // Skip invalid declarations. Because styles are merged into an
966
- // initially empty style object during stylesheet loading, this
967
- // handles all invalid declarations
968
- if ($computed === null) {
969
- continue;
970
- }
971
-
972
  if ($important) {
973
  $this->_important_props[$prop] = true;
974
  }
975
 
976
  $this->_props[$prop] = $val;
977
 
978
- // Don't use the computed value for dependent properties; they will
979
- // be computed on-demand during inheritance or property access
980
- // instead
981
- if (isset(self::$_dependent_props[$prop])) {
982
- unset($this->_props_computed[$prop]);
983
- unset($this->_prop_cache[$prop]);
 
984
  } else {
985
- $this->_props_computed[$prop] = $computed;
986
- $this->_prop_cache[$prop] = null;
987
  }
988
  }
989
  }
990
 
991
  /**
992
- * Returns an array(r, g, b, "r"=> r, "g"=>g, "b"=>b, "alpha"=>alpha, "hex"=>"#rrggbb")
993
- * based on the provided CSS color value.
994
- *
995
- * @param string $color
996
- * @return array|string|null
997
- */
998
- function munge_color($color)
999
- {
1000
- return Color::parse($color);
1001
- }
1002
-
1003
- /**
1004
- * @deprecated
1005
- * @param string $prop
1006
  */
1007
- function important_set($prop)
1008
  {
1009
- $prop = str_replace("-", "_", $prop);
1010
- $this->_important_props[$prop] = true;
1011
  }
1012
 
1013
  /**
1014
- * @deprecated
1015
- * @param string $prop
1016
- * @return bool
 
1017
  */
1018
- function important_get($prop)
1019
  {
1020
- return isset($this->_important_props[$prop]);
1021
- }
 
 
 
 
 
 
 
 
1022
 
1023
- /**
1024
- * Clear information about important declarations after the style has been
1025
- * finalized during stylesheet loading.
1026
- */
1027
- public function clear_important(): void
1028
- {
1029
- $this->_important_props = [];
 
 
1030
  }
1031
 
1032
  /**
1033
- * Set the specified value of a property.
1034
  *
1035
  * Setting `$clear_dependencies` to `false` is useful for saving a bit of
1036
  * unnecessary work while loading stylesheets.
1037
  *
1038
  * @param string $prop The property to set.
1039
- * @param mixed $val The value declaration.
1040
  * @param bool $important Whether the declaration is important.
1041
  * @param bool $clear_dependencies Whether to clear computed values of dependent properties.
1042
  */
1043
- function set_prop(string $prop, $val, bool $important = false, bool $clear_dependencies = true): void
1044
  {
1045
  $prop = str_replace("-", "_", $prop);
1046
 
@@ -1055,9 +1241,8 @@ class Style
1055
  return;
1056
  }
1057
 
1058
- if ($prop !== "content" && is_string($val) && mb_strpos($val, "url") === false && strlen($val) > 1) {
1059
  $val = mb_strtolower(trim(str_replace(["\n", "\t"], [" "], $val)));
1060
- $val = preg_replace("/([0-9]+) (pt|px|pc|rem|em|ex|in|cm|mm|%)/S", "\\1\\2", $val);
1061
  }
1062
 
1063
  if (isset(self::$_props_shorthand[$prop])) {
@@ -1068,17 +1253,35 @@ class Style
1068
  $this->set_prop($sub_prop, $val, $important, $clear_dependencies);
1069
  }
1070
  } else {
1071
- $method = "set_$prop";
1072
-
1073
  if (!isset(self::$_methods_cache[$method])) {
1074
  self::$_methods_cache[$method] = method_exists($this, $method);
1075
  }
1076
-
1077
  if (self::$_methods_cache[$method]) {
1078
- $this->$method($val, $important);
 
 
 
 
 
 
 
 
 
 
 
1079
  }
1080
  }
1081
  } else {
 
 
 
 
 
 
 
1082
  // `!important` declarations take precedence over normal ones
1083
  if (!$important && isset($this->_important_props[$prop])) {
1084
  return;
@@ -1090,7 +1293,7 @@ class Style
1090
 
1091
  // https://www.w3.org/TR/css-cascade-3/#inherit-initial
1092
  if ($val === "unset") {
1093
- $val = in_array($prop, self::$_inherited, true)
1094
  ? "inherit"
1095
  : "initial";
1096
  }
@@ -1100,9 +1303,16 @@ class Style
1100
  $val = self::$_defaults[$prop];
1101
  }
1102
 
 
 
 
 
 
 
 
1103
  $this->_props[$prop] = $val;
1104
- unset($this->_props_computed[$prop]);
1105
- unset($this->_prop_cache[$prop]);
1106
 
1107
  if ($clear_dependencies) {
1108
  // Clear the computed values of any dependent properties, so
@@ -1110,40 +1320,24 @@ class Style
1110
  if (isset(self::$_dependency_map[$prop])) {
1111
  foreach (self::$_dependency_map[$prop] as $dependent) {
1112
  unset($this->_props_computed[$dependent]);
1113
- unset($this->_prop_cache[$dependent]);
1114
  }
1115
  }
1116
 
1117
- // Clear border-radius cache on setting any border-radius
1118
- // property
1119
- if ($prop === "border_top_left_radius"
1120
- || $prop === "border_top_right_radius"
1121
- || $prop === "border_bottom_left_radius"
1122
- || $prop === "border_bottom_right_radius"
1123
- ) {
1124
- $this->has_border_radius_cache = null;
1125
- }
1126
- }
1127
-
1128
- // FIXME: temporary hack around lack of persistence of base href for
1129
- // URLs. Compute value immediately, before the original base href is
1130
- // no longer available
1131
- if ($prop === "background_image" || $prop === "list_style_image") {
1132
- $this->compute_prop($prop, $val);
1133
  }
1134
  }
1135
  }
1136
 
1137
  /**
1138
- * Similar to __get() without storing the result. Useful for accessing
1139
- * properties while loading stylesheets.
1140
  *
1141
  * @param string $prop
1142
  *
1143
  * @return mixed
1144
  * @throws Exception
1145
  */
1146
- function get_prop(string $prop)
1147
  {
1148
  // Legacy property aliases
1149
  if (isset(self::$_props_alias[$prop])) {
@@ -1154,66 +1348,92 @@ class Style
1154
  throw new Exception("'$prop' is not a recognized CSS property.");
1155
  }
1156
 
1157
- $method = "get_$prop";
 
1158
 
1159
- if (isset($this->_props_computed[$prop])) {
1160
- if (method_exists($this, $method)) {
1161
- return $this->$method();
1162
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1163
 
1164
- return $this->_props_computed[$prop];
 
1165
  }
1166
 
1167
- // Fall back on defaults if property is not set
1168
- return $this->_props[$prop] ?? self::$_defaults[$prop];
 
 
 
 
 
 
 
 
 
1169
  }
1170
 
1171
  /**
1172
- * PHP5 overloaded setter
1173
  *
1174
- * This function along with {@link Style::__get()} permit a user of the
1175
- * Style class to access any (CSS) property using the following syntax:
1176
- * <code>
1177
- * Style->margin_top = "1em";
1178
- * echo (Style->margin_top);
1179
- * </code>
1180
  *
1181
- * __set() automatically calls the provided set function, if one exists,
1182
- * otherwise it sets the property directly. Typically, __set() is not
1183
- * called directly from outside of this class.
1184
- *
1185
- * On each modification clear cache to return accurate setting.
1186
- * Also affects direct settings not using __set
1187
- * For easier finding all assignments, attempted to allowing only explicite assignment:
1188
- * Very many uses, e.g. AbstractFrameReflower.php -> for now leave as it is
1189
- * function __set($prop, $val) {
1190
- * throw new Exception("Implicit replacement of assignment by __set. Not good.");
1191
- * }
1192
- * function props_set($prop, $val) { ... }
1193
  *
1194
- * @param string $prop the property to set
1195
- * @param mixed $val the value of the property
1196
  *
 
1197
  */
1198
- function __set($prop, $val)
1199
  {
1200
- $this->set_prop($prop, $val);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1201
  }
1202
 
1203
  /**
1204
- * PHP5 overloaded getter
1205
- * Along with {@link Style::__set()} __get() provides access to all CSS
1206
- * properties directly. Typically __get() is not called directly outside
1207
- * of this class.
1208
- * On each modification clear cache to return accurate setting.
1209
- * Also affects direct settings not using __set
1210
  *
1211
  * @param string $prop
1212
  *
1213
  * @return mixed
1214
  * @throws Exception
1215
  */
1216
- function __get($prop)
1217
  {
1218
  // Legacy property aliases
1219
  if (isset(self::$_props_alias[$prop])) {
@@ -1224,12 +1444,12 @@ class Style
1224
  throw new Exception("'$prop' is not a recognized CSS property.");
1225
  }
1226
 
1227
- if (isset($this->_prop_cache[$prop])) {
1228
- return $this->_prop_cache[$prop];
1229
  }
1230
 
1231
- $method = "get_$prop";
1232
-
1233
  if (!isset(self::$_methods_cache[$method])) {
1234
  self::$_methods_cache[$method] = method_exists($this, $method);
1235
  }
@@ -1243,87 +1463,69 @@ class Style
1243
  } else {
1244
  return implode(" ", array_map(function ($sub_prop) {
1245
  $val = $this->__get($sub_prop);
1246
- return is_array($val) ? implode(" ", $val) : $val;
1247
  }, self::$_props_shorthand[$prop]));
1248
  }
1249
  } else {
1250
- // Compute the value if needed
1251
- if (!\array_key_exists($prop, $this->_props_computed)) {
1252
- $val = $this->_props[$prop] ?? self::$_defaults[$prop];
1253
- $this->compute_prop($prop, $val);
1254
- }
1255
-
1256
- // Invalid declarations are skipped on style merge, but during
1257
- // style parsing, styles might contain invalid declarations. Fall
1258
- // back to the default value in that case
1259
- $computed = $this->_props_computed[$prop]
1260
- ?? $this->compute_prop($prop, self::$_defaults[$prop]);
1261
  $used = self::$_methods_cache[$method]
1262
- ? $this->$method()
1263
  : $computed;
1264
 
1265
- $this->_prop_cache[$prop] = $used;
1266
  return $used;
1267
  }
1268
  }
1269
 
1270
  /**
1271
- * Experimental fast setter for used values.
1272
- *
1273
- * If a shorthand property is specified, all of its sub-properties are set
1274
- * to the same value.
1275
  *
1276
- * @param string $prop
1277
- * @param mixed $val
1278
  */
1279
- function set_used(string $prop, $val): void
1280
  {
1281
- // Legacy property aliases
1282
- if (isset(self::$_props_alias[$prop])) {
1283
- $prop = self::$_props_alias[$prop];
 
 
1284
  }
1285
 
1286
- if (!isset(self::$_defaults[$prop])) {
1287
- throw new Exception("'$prop' is not a recognized CSS property.");
 
1288
  }
1289
 
1290
- if (isset(self::$_props_shorthand[$prop])) {
1291
- foreach (self::$_props_shorthand[$prop] as $sub_prop) {
1292
- $this->set_used($sub_prop, $val);
1293
- }
 
 
 
 
 
 
1294
  } else {
1295
- $this->_prop_cache[$prop] = $val;
1296
  }
1297
  }
1298
 
1299
  /**
1300
- * @param string $prop The property to compute.
1301
- * @param mixed $val The value to compute.
 
1302
  *
1303
  * @return mixed The computed value.
1304
  */
1305
- protected function compute_prop(string $prop, $val)
1306
  {
1307
- $this->_props_computed[$prop] = null;
1308
- $this->_prop_cache[$prop] = null;
1309
-
1310
- $method = "set_$prop";
1311
-
1312
- if (!isset(self::$_methods_cache[$method])) {
1313
- self::$_methods_cache[$method] = method_exists($this, $method);
1314
- }
1315
-
1316
- // During style merge, the parent style is not available yet, so
1317
- // temporarily use the initial value for `inherit` properties. The
1318
- // keyword is properly resolved during inheritance
1319
- if ($val === "inherit") {
1320
- $val = self::$_defaults[$prop];
1321
- }
1322
 
1323
- if (self::$_methods_cache[$method]) {
1324
- $this->$method($val);
1325
- } elseif ($val !== "") {
1326
- $this->_props_computed[$prop] = $val;
1327
  }
1328
 
1329
  return $this->_props_computed[$prop];
@@ -1331,9 +1533,9 @@ class Style
1331
 
1332
  /**
1333
  * @param float $cbw The width of the containing block.
1334
- * @return float|null|string
1335
  */
1336
- function computed_bottom_spacing(float $cbw)
1337
  {
1338
  // Caching the bottom spacing independently of the given width is a bit
1339
  // iffy, but should be okay, as the containing block should only
@@ -1352,28 +1554,43 @@ class Style
1352
  );
1353
  }
1354
 
 
 
 
 
 
 
 
 
 
 
 
 
1355
  /**
1356
  * @return string
1357
  */
1358
- function get_font_family_raw()
1359
  {
1360
  return trim($this->_props["font_family"], " \t\n\r\x0B\"'");
1361
  }
1362
 
1363
  /**
1364
- * Getter for the 'font-family' CSS property.
 
1365
  * Uses the {@link FontMetrics} class to resolve the font family into an
1366
  * actual font file.
1367
  *
1368
- * @link http://www.w3.org/TR/CSS21/fonts.html#propdef-font-family
 
1369
  * @throws Exception
1370
  *
1371
- * @return string
1372
  */
1373
- function get_font_family()
1374
  {
1375
  //TODO: we should be using the calculated prop rather than perform the entire family parsing operation again
1376
 
 
1377
  $DEBUGCSS = $this->_stylesheet->get_dompdf()->getOptions()->getDebugCss();
1378
 
1379
  // Select the appropriate font. First determine the subtype, then check
@@ -1391,9 +1608,9 @@ class Style
1391
 
1392
  // Resolve font-style
1393
  $font_style = $this->__get("font_style");
1394
- $subtype = $this->getFontMetrics()->getType($weight . ' ' . $font_style);
1395
 
1396
- $families = preg_split("/\s*,\s*/", $this->_props_computed["font_family"]);
1397
 
1398
  $font = null;
1399
  foreach ($families as $family) {
@@ -1403,12 +1620,12 @@ class Style
1403
  if ($DEBUGCSS) {
1404
  print '(' . $family . ')';
1405
  }
1406
- $font = $this->getFontMetrics()->getFont($family, $subtype);
1407
 
1408
  if ($font) {
1409
  if ($DEBUGCSS) {
1410
  print "<pre>[get_font_family:";
1411
- print '(' . $this->_props_computed["font_family"] . '.' . $font_style . '.' . $weight . '.' . $subtype . ')';
1412
  print '(' . $font . ")get_font_family]\n</pre>";
1413
  }
1414
  return $font;
@@ -1419,7 +1636,7 @@ class Style
1419
  if ($DEBUGCSS) {
1420
  print '(default)';
1421
  }
1422
- $font = $this->getFontMetrics()->getFont($family, $subtype);
1423
 
1424
  if ($font) {
1425
  if ($DEBUGCSS) {
@@ -1428,224 +1645,189 @@ class Style
1428
  return $font;
1429
  }
1430
 
1431
- throw new Exception("Unable to find a suitable font replacement for: '" . $this->_props_computed["font_family"] . "'");
1432
  }
1433
 
1434
  /**
1435
- * @link http://www.w3.org/TR/CSS21/text.html#propdef-word-spacing
1436
  * @return float
 
 
1437
  */
1438
- function get_word_spacing()
1439
  {
1440
- $word_spacing = $this->_props_computed["word_spacing"];
1441
-
1442
- if ($word_spacing === "normal") {
1443
- return 0;
1444
- }
1445
-
1446
- if (strpos($word_spacing, "%") !== false) {
1447
- return $word_spacing;
1448
  }
1449
 
1450
- return (float)$this->length_in_pt($word_spacing, $this->__get("font_size"));
 
 
1451
  }
1452
 
1453
  /**
1454
- * @link http://www.w3.org/TR/CSS21/text.html#propdef-letter-spacing
1455
  * @return float
 
 
1456
  */
1457
- function get_letter_spacing()
1458
  {
1459
- $letter_spacing = $this->_props_computed["letter_spacing"];
1460
-
1461
- if ($letter_spacing === "normal") {
1462
- return 0;
1463
  }
1464
 
1465
- return (float)$this->length_in_pt($letter_spacing, $this->__get("font_size"));
 
 
1466
  }
1467
 
1468
  /**
1469
- * @link http://www.w3.org/TR/CSS21/visudet.html#propdef-line-height
1470
  * @return float
 
 
1471
  */
1472
- function get_line_height()
1473
  {
1474
- $line_height = $this->_props_computed["line_height"];
1475
-
1476
- if ($line_height === "normal") {
1477
- return self::$default_line_height * $this->__get("font_size");
1478
  }
1479
 
1480
- if (is_numeric($line_height)) {
1481
- return $line_height * $this->__get("font_size");
1482
- }
 
1483
 
1484
- return (float)$this->length_in_pt($line_height, $this->__get("font_size"));
1485
  }
1486
 
1487
  /**
1488
- * @param string $prop
1489
- * @param bool $current_is_parent
 
1490
  * @return array|string
1491
  */
1492
- protected function get_prop_color(string $prop, bool $current_is_parent = false)
1493
  {
1494
- $val = $this->_props_computed[$prop];
1495
-
1496
- if ($val === "currentcolor") {
1497
  // https://www.w3.org/TR/css-color-4/#resolving-other-colors
1498
  if ($current_is_parent) {
1499
  // Use the `color` value from the parent for the `color`
1500
  // property itself
1501
  return isset($this->parent_style)
1502
  ? $this->parent_style->__get("color")
1503
- : $this->munge_color(self::$_defaults[$prop]);
1504
  }
1505
 
1506
  return $this->__get("color");
1507
  }
1508
 
1509
- return $this->munge_color($val) ?? "transparent";
1510
  }
1511
 
1512
  /**
1513
  * Returns the color as an array
1514
  *
1515
  * The array has the following format:
1516
- * <code>array(r,g,b, "r" => r, "g" => g, "b" => b, "hex" => "#rrggbb")</code>
1517
  *
1518
- * @link http://www.w3.org/TR/CSS21/colors.html#propdef-color
1519
- * @return array
 
 
1520
  */
1521
- function get_color()
1522
  {
1523
- return $this->get_prop_color("color", true);
1524
  }
1525
 
1526
  /**
1527
  * Returns the background color as an array
1528
  *
1529
- * The returned array has the same format as {@link Style::get_color()}
1530
  *
1531
- * @link http://www.w3.org/TR/CSS21/colors.html#propdef-background-color
1532
- * @return array
 
 
1533
  */
1534
- function get_background_color()
1535
  {
1536
- return $this->get_prop_color("background_color");
1537
  }
1538
 
1539
  /**
1540
  * Returns the background image URI, or "none"
1541
  *
1542
- * @link https://www.w3.org/TR/CSS21/colors.html#propdef-background-image
1543
  * @return string
1544
- */
1545
- function get_background_image()
1546
- {
1547
- return $this->_stylesheet->resolve_url($this->_props_computed["background_image"]);
1548
- }
1549
-
1550
- /**
1551
- * Returns the background position as an array
1552
  *
1553
- * The returned array has the following format:
1554
- * <code>array(x,y, "x" => x, "y" => y)</code>
1555
- *
1556
- * @link http://www.w3.org/TR/CSS21/colors.html#propdef-background-position
1557
- * @return array
1558
  */
1559
- function get_background_position()
1560
  {
1561
- $tmp = explode(" ", $this->_props_computed["background_position"]);
1562
-
1563
- return [
1564
- 0 => $tmp[0], "x" => $tmp[0],
1565
- 1 => $tmp[1], "y" => $tmp[1],
1566
- ];
1567
  }
1568
 
1569
-
1570
  /**
1571
- * Returns the background size as an array
1572
- *
1573
- * The return value has one of the following formats:
1574
- * <code>"cover"</code>
1575
- * <code>"contain"</code>
1576
- * <code>array(width,height)</code>
1577
- *
1578
- * @link https://www.w3.org/TR/css3-background/#background-size
1579
- * @return string|array
1580
- */
1581
- function get_background_size()
1582
- {
1583
- switch ($this->_props_computed["background_size"]) {
1584
- case "cover":
1585
- return "cover";
1586
- case "contain":
1587
- return "contain";
1588
- default:
1589
- break;
1590
- }
1591
-
1592
- $result = explode(" ", $this->_props_computed["background_size"]);
1593
- return [$result[0], $result[1]];
1594
- }
1595
-
1596
- /**#@+
1597
  * Returns the border color as an array
1598
  *
1599
- * See {@link Style::get_color()}
1600
  *
1601
- * @link http://www.w3.org/TR/CSS21/box.html#border-color-properties
1602
- * @return array
 
 
1603
  */
1604
- function get_border_top_color()
1605
  {
1606
- return $this->get_prop_color("border_top_color");
1607
  }
1608
 
1609
  /**
1610
- * @return array
 
1611
  */
1612
- function get_border_right_color()
1613
  {
1614
- return $this->get_prop_color("border_right_color");
1615
  }
1616
 
1617
  /**
1618
- * @return array
 
1619
  */
1620
- function get_border_bottom_color()
1621
  {
1622
- return $this->get_prop_color("border_bottom_color");
1623
  }
1624
 
1625
  /**
1626
- * @return array
 
1627
  */
1628
- function get_border_left_color()
1629
  {
1630
- return $this->get_prop_color("border_left_color");
1631
  }
1632
 
1633
- /**#@-*/
1634
-
1635
  /**
1636
  * Return an array of all border properties.
1637
  *
1638
  * The returned array has the following structure:
1639
- * <code>
 
1640
  * array("top" => array("width" => [border-width],
1641
  * "style" => [border-style],
1642
  * "color" => [border-color (array)]),
1643
  * "bottom" ... )
1644
- * </code>
1645
  *
1646
  * @return array
1647
  */
1648
- function get_border_properties()
1649
  {
1650
  return [
1651
  "top" => [
@@ -1672,69 +1854,58 @@ class Style
1672
  }
1673
 
1674
  /**
1675
- * Return a single border property
1676
  *
1677
  * @param string $side
1678
- *
1679
- * @return mixed
1680
  */
1681
- protected function _get_border($side)
1682
  {
1683
- $color = $this->__get("border_" . $side . "_color");
1684
 
1685
- return $this->__get("border_" . $side . "_width") . " " .
1686
- $this->__get("border_" . $side . "_style") . " " .
1687
- (is_array($color) ? $color["hex"] : $color);
1688
  }
1689
 
1690
- /**#@+
1691
  * Return full border properties as a string
1692
  *
1693
  * Border properties are returned just as specified in CSS:
1694
- * <pre>[width] [style] [color]</pre>
1695
  * e.g. "1px solid blue"
1696
  *
1697
- * @link http://www.w3.org/TR/CSS21/box.html#border-shorthand-properties
1698
  * @return string
 
 
1699
  */
1700
- function get_border_top()
1701
- {
1702
- return $this->_get_border("top");
1703
- }
1704
-
1705
- /**
1706
- * @return mixed
1707
- */
1708
- function get_border_right()
1709
  {
1710
- return $this->_get_border("right");
1711
  }
1712
 
1713
  /**
1714
- * @return mixed
1715
  */
1716
- function get_border_bottom()
1717
  {
1718
- return $this->_get_border("bottom");
1719
  }
1720
 
1721
  /**
1722
- * @return mixed
1723
  */
1724
- function get_border_left()
1725
  {
1726
- return $this->_get_border("left");
1727
  }
1728
 
1729
  /**
1730
- * @deprecated
1731
- * @param float $w
1732
- * @param float $h
1733
- * @return float[]
1734
  */
1735
- function get_computed_border_radius($w, $h)
1736
  {
1737
- return $this->resolve_border_radius([0, 0, $w, $h]);
1738
  }
1739
 
1740
  public function has_border_radius(): bool
@@ -1746,10 +1917,10 @@ class Style
1746
  // Use a fixed ref size here. We don't know the border-box width here
1747
  // and font size might be 0. Since we are only interested in whether
1748
  // there is any border radius at all, this should do
1749
- $tl = (float) $this->length_in_pt($this->border_top_left_radius, 10);
1750
- $tr = (float) $this->length_in_pt($this->border_top_right_radius, 10);
1751
- $br = (float) $this->length_in_pt($this->border_bottom_right_radius, 10);
1752
- $bl = (float) $this->length_in_pt($this->border_bottom_left_radius, 10);
1753
 
1754
  $this->has_border_radius_cache = $tl + $tr + $br + $bl > 0;
1755
  return $this->has_border_radius_cache;
@@ -1842,61 +2013,60 @@ class Style
1842
  /**
1843
  * Returns the outline color as an array
1844
  *
1845
- * See {@link Style::get_color()}
1846
  *
1847
- * @link http://www.w3.org/TR/CSS21/box.html#border-color-properties
1848
- * @return array
 
 
 
 
 
 
 
 
 
 
 
 
 
1849
  */
1850
- function get_outline_color()
1851
  {
1852
- return $this->get_prop_color("outline_color");
1853
  }
1854
 
1855
  /**
1856
  * Return full outline properties as a string
1857
  *
1858
  * Outline properties are returned just as specified in CSS:
1859
- * <pre>[width] [style] [color]</pre>
1860
  * e.g. "1px solid blue"
1861
  *
1862
- * @link http://www.w3.org/TR/CSS21/box.html#border-shorthand-properties
1863
  * @return string
 
 
1864
  */
1865
- function get_outline()
1866
  {
1867
  $color = $this->__get("outline_color");
1868
 
1869
  return $this->__get("outline_width") . " " .
1870
  $this->__get("outline_style") . " " .
1871
- (is_array($color) ? $color["hex"] : $color);
1872
- }
1873
-
1874
- /**
1875
- * Returns border spacing as an array
1876
- *
1877
- * The array has the format (h_space,v_space)
1878
- *
1879
- * @link http://www.w3.org/TR/CSS21/tables.html#propdef-border-spacing
1880
- * @return array
1881
- */
1882
- function get_border_spacing()
1883
- {
1884
- $arr = explode(" ", $this->_props_computed["border_spacing"]);
1885
- if (count($arr) == 1) {
1886
- $arr[1] = $arr[0];
1887
- }
1888
- return $arr;
1889
  }
1890
 
1891
  /**
1892
  * Returns the list style image URI, or "none"
1893
  *
1894
- * @link http://www.w3.org/TR/CSS21/generate.html#propdef-list-style-image
1895
  * @return string
 
 
1896
  */
1897
- function get_list_style_image()
1898
  {
1899
- return $this->_stylesheet->resolve_url($this->_props_computed["list_style_image"]);
1900
  }
1901
 
1902
  /**
@@ -1927,45 +2097,48 @@ class Style
1927
  }
1928
 
1929
  /**
 
1930
  * @return array|string
 
 
1931
  */
1932
- function get_counter_increment()
1933
  {
1934
- $val = $this->_props_computed["counter_increment"];
1935
-
1936
- if ($val === "none" || $val === "inherit") {
1937
- return "none";
1938
  }
1939
 
1940
- return $this->parse_counter_prop($val, 1);
1941
  }
1942
 
1943
  /**
 
1944
  * @return array|string
 
 
1945
  */
1946
- protected function get_counter_reset()
1947
  {
1948
- $val = $this->_props_computed["counter_reset"];
1949
-
1950
- if ($val === "none") {
1951
- return "none";
1952
  }
1953
 
1954
- return $this->parse_counter_prop($val, 0);
1955
  }
1956
 
1957
  /**
 
1958
  * @return string[]|string
 
 
1959
  */
1960
- protected function get_content()
1961
  {
1962
- $val = $this->_props_computed["content"];
1963
-
1964
- if ($val === "normal" || $val === "none") {
1965
- return $val;
1966
  }
1967
 
1968
- return $this->parse_property_value($val);
1969
  }
1970
 
1971
  /*==============================*/
@@ -2007,143 +2180,175 @@ class Style
2007
  || preg_match("/^#|rgb\(|rgba\(|cmyk\(/", $val);
2008
  }
2009
 
2010
- protected function prop_name(string $style, string $side, string $type): string
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2011
  {
2012
- $prop = $style;
2013
- if ($side !== "") {
2014
- $prop .= "_" . $side;
2015
- };
2016
- if ($type !== "") {
2017
- $prop .= "_" . $type;
2018
- };
2019
- return $prop;
2020
  }
2021
 
2022
  /**
2023
- * Generalized set function for individual attribute of combined style.
2024
- *
2025
- * Applicable for margin, border, padding, outline.
2026
- *
2027
- * @param string $style
2028
- * @param string $side
2029
- * @param string $type
2030
- * @param mixed $val
2031
  */
2032
- protected function _set_style_side_type($style, $side, $type, $val)
2033
  {
2034
- $prop = $this->prop_name($style, $side, $type);
 
 
 
2035
 
2036
- $this->_prop_cache[$prop] = null;
 
 
 
 
 
 
 
 
2037
 
2038
- if ($val === "inherit") {
2039
- $this->_props_computed[$prop] = null;
2040
- return;
2041
- }
 
 
 
 
 
2042
 
2043
- if ($side === "bottom") {
2044
- $this->_computed_bottom_spacing = null; //reset computed cache, border style can disable/enable border calculations
2045
  }
2046
 
2047
- if ($type === "color") {
2048
- $this->set_prop_color($prop, $val);
2049
- } elseif (($style === "border" || $style === "outline") && $type === "width") {
2050
- // Border-width keywords
2051
- if ($val === "thin") {
2052
- $val_computed = 0.5;
2053
- } elseif ($val === "medium") {
2054
- $val_computed = 1.5;
2055
- } elseif ($val === "thick") {
2056
- $val_computed = 2.5;
2057
- } elseif (mb_strpos($val, "%") !== false) {
2058
- $val_computed = null;
2059
- } else {
2060
- $val_computed = $this->single_length_in_pt($val);
2061
-
2062
- if ($val_computed < 0) {
2063
- $val_computed = null;
2064
- }
2065
- }
2066
 
2067
- if ($val_computed === null) {
2068
- $this->_props_computed[$prop] = null;
2069
- } else {
2070
- $line_style_prop = $this->prop_name($style, $side, "style");
2071
- $line_style = $this->__get($line_style_prop);
2072
- $has_line_style = $line_style !== "none" && $line_style !== "hidden";
 
 
 
2073
 
2074
- $this->_props_computed[$prop] = $has_line_style ? $val_computed : 0;
2075
- }
2076
- } elseif (($style === "border" || $style === "outline") && $type === "style") {
2077
- if (in_array($val, Style::$BORDER_STYLES, true)) {
2078
- $this->_props_computed[$prop] = $val;
2079
- } else {
2080
- $this->_props_computed[$prop] = null;
2081
- }
2082
- } elseif ($style === "margin" || $style === "padding") {
2083
- if ($val === "none") {
2084
- // Legacy support for `none` keyword, not covered by spec
2085
- $val_computed = 0;
2086
- } elseif ($style === "margin" && $val === "auto") {
2087
- $val_computed = $val;
2088
- } elseif (mb_strpos($val, "%") !== false) {
2089
- $val_computed = $val;
2090
- } else {
2091
- $val_computed = $this->single_length_in_pt($val);
2092
 
2093
- if ($style === "padding" && $val_computed < 0) {
2094
- $val_computed = null;
2095
- }
2096
- }
2097
 
2098
- $this->_props_computed[$prop] = $val_computed;
2099
- } elseif ($val !== "") {
2100
- $this->_props_computed[$prop] = $val;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2101
  } else {
2102
- $this->_props_computed[$prop] = null;
 
 
 
 
2103
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2104
  }
2105
 
2106
  /**
2107
- * @param string $style
2108
- * @param string $type
2109
- * @param mixed $val
2110
- * @param bool $important
 
 
 
2111
  */
2112
- protected function _set_style_type($style, $type, $val, $important)
2113
  {
2114
- $v = $this->parse_property_value($val);
2115
 
2116
- switch (count($v)) {
2117
  case 1:
2118
- [$top, $right, $bottom, $left] = [$v[0], $v[0], $v[0], $v[0]];
2119
  break;
2120
  case 2:
2121
- [$top, $right, $bottom, $left] = [$v[0], $v[1], $v[0], $v[1]];
2122
  break;
2123
  case 3:
2124
- [$top, $right, $bottom, $left] = [$v[0], $v[1], $v[2], $v[1]];
2125
  break;
2126
  case 4:
2127
- [$top, $right, $bottom, $left] = [$v[0], $v[1], $v[2], $v[3]];
2128
  break;
2129
  default:
2130
- return;
2131
  }
2132
 
2133
- $this->set_prop($this->prop_name($style, "top", $type), $top, $important);
2134
- $this->set_prop($this->prop_name($style, "right", $type), $right, $important);
2135
- $this->set_prop($this->prop_name($style, "bottom", $type), $bottom, $important);
2136
- $this->set_prop($this->prop_name($style, "left", $type), $left, $important);
2137
  }
2138
 
2139
  /*======================*/
2140
 
2141
  /**
2142
- * https://www.w3.org/TR/CSS21/visuren.html#display-prop
2143
- *
2144
- * @param string $val
2145
  */
2146
- protected function set_display(string $val): void
2147
  {
2148
  // Make sure that common valid, but unsupported display types have an
2149
  // appropriate fallback display type
@@ -2161,12 +2366,12 @@ class Style
2161
  }
2162
 
2163
  if (!isset(self::$valid_display_types[$val])) {
2164
- return;
2165
  }
2166
 
2167
  // https://www.w3.org/TR/CSS21/visuren.html#dis-pos-flo
2168
  if ($this->is_in_flow()) {
2169
- $computed = $val;
2170
  } else {
2171
  switch ($val) {
2172
  case "inline":
@@ -2179,129 +2384,75 @@ class Style
2179
  // case "table-column-group":
2180
  // case "table-column":
2181
  // case "table-caption":
2182
- $computed = "block";
2183
- break;
2184
  case "inline-table":
2185
- $computed = "table";
2186
- break;
2187
  default:
2188
- $computed = $val;
2189
- break;
2190
  }
2191
  }
2192
-
2193
- $this->_props_computed["display"] = $computed;
2194
- }
2195
-
2196
- protected function set_prop_color($prop, $val)
2197
- {
2198
- $this->_prop_cache[$prop] = null;
2199
-
2200
- // https://www.w3.org/TR/css-color-4/#resolving-other-colors
2201
- $munged_color = $val !== "currentcolor"
2202
- ? $this->munge_color($val)
2203
- : $val;
2204
-
2205
- if (is_null($munged_color)) {
2206
- $this->_props_computed[$prop] = null;
2207
- return;
2208
- }
2209
-
2210
- $this->_props_computed[$prop] = is_array($munged_color) ? $munged_color["hex"] : $munged_color;
2211
  }
2212
 
2213
  /**
2214
- * Sets color
2215
- *
2216
- * The color parameter can be any valid CSS color value
2217
- *
2218
- * @link http://www.w3.org/TR/CSS21/colors.html#propdef-color
2219
- * @param string $color
2220
  */
2221
- function set_color($color)
2222
  {
2223
- $this->set_prop_color("color", $color);
2224
  }
2225
 
2226
  /**
2227
- * Sets the background color
2228
- *
2229
- * @link http://www.w3.org/TR/CSS21/colors.html#propdef-background-color
2230
- * @param string $color
2231
  */
2232
- function set_background_color($color)
2233
  {
2234
- $this->set_prop_color("background_color", $color);
2235
  }
2236
 
2237
  /**
2238
- * Set the background image url
2239
  * @link https://www.w3.org/TR/CSS21/colors.html#propdef-background-image
2240
- *
2241
- * @param string $val
2242
  */
2243
- function set_background_image($val)
2244
  {
2245
- $this->_prop_cache["background_image"] = null;
2246
-
2247
  $parsed_val = $this->_stylesheet->resolve_url($val);
2248
 
2249
  if ($parsed_val === "none") {
2250
- $this->_props_computed["background_image"] = "none";
2251
  } else {
2252
- $this->_props_computed["background_image"] = "url(" . $parsed_val . ")";
2253
  }
2254
  }
2255
 
2256
  /**
2257
- * Sets the background repeat
2258
- *
2259
- * @link http://www.w3.org/TR/CSS21/colors.html#propdef-background-repeat
2260
- * @param string $val
2261
  */
2262
- function set_background_repeat($val)
2263
  {
2264
- $this->_prop_cache["background_repeat"] = null;
2265
-
2266
- if ($val === "inherit") {
2267
- $this->_props_computed["background_repeat"] = null;
2268
- return;
2269
- }
2270
-
2271
- $this->_props_computed["background_repeat"] = $val;
2272
  }
2273
 
2274
  /**
2275
- * Sets the background attachment
2276
- *
2277
- * @link http://www.w3.org/TR/CSS21/colors.html#propdef-background-attachment
2278
- * @param string $val
2279
  */
2280
- function set_background_attachment($val)
2281
  {
2282
- $this->_prop_cache["background_attachment"] = null;
2283
-
2284
- if ($val === "inherit") {
2285
- $this->_props_computed["background_attachment"] = null;
2286
- return;
2287
- }
2288
-
2289
- $this->_props_computed["background_attachment"] = $val;
2290
  }
2291
 
2292
  /**
2293
- * Sets the background position
2294
- *
2295
- * @link http://www.w3.org/TR/CSS21/colors.html#propdef-background-position
2296
- * @param string $val
2297
  */
2298
- function set_background_position($val)
2299
  {
2300
- $this->_prop_cache["background_position"] = null;
2301
 
2302
- $tmp = explode(" ", $val);
 
 
2303
 
2304
- switch ($tmp[0]) {
2305
  case "left":
2306
  $x = "0%";
2307
  break;
@@ -2324,12 +2475,12 @@ class Style
2324
  break;
2325
 
2326
  default:
2327
- $x = $tmp[0];
2328
  break;
2329
  }
2330
 
2331
- if (isset($tmp[1])) {
2332
- switch ($tmp[1]) {
2333
  case "left":
2334
  $x = "0%";
2335
  break;
@@ -2347,7 +2498,7 @@ class Style
2347
  break;
2348
 
2349
  case "center":
2350
- if ($tmp[0] === "left" || $tmp[0] === "right" || $tmp[0] === "center") {
2351
  $y = "50%";
2352
  } else {
2353
  $x = "50%";
@@ -2355,7 +2506,7 @@ class Style
2355
  break;
2356
 
2357
  default:
2358
- $y = $tmp[1];
2359
  break;
2360
  }
2361
  } else {
@@ -2369,118 +2520,104 @@ class Style
2369
  if (!isset($y)) {
2370
  $y = "0%";
2371
  }
2372
-
2373
- $this->_props_computed["background_position"] = "$x $y";
2374
  }
2375
 
2376
  /**
2377
- * Sets the background size
2378
  *
2379
- * @link https://www.w3.org/TR/css3-background/#background-size
2380
- * @param string $val
 
 
 
 
2381
  */
2382
- function set_background_size($val)
2383
  {
2384
- $this->_prop_cache["background_size"] = null;
 
 
2385
 
2386
- $result = explode(" ", $val);
2387
- $width = $result[0];
2388
 
2389
- switch ($width) {
2390
- case "cover":
2391
- case "contain":
2392
- $this->_props_computed["background_size"] = $width;
2393
- return;
2394
- case "inherit":
2395
- $this->_props_computed["background_size"] = null;
2396
- return;
2397
  }
2398
 
2399
- if ($width !== "auto" && strpos($width, "%") === false) {
2400
- $width = (float)$this->length_in_pt($width);
 
2401
  }
2402
 
2403
- $height = $result[1] ?? "auto";
2404
- if ($height !== "auto" && strpos($height, "%") === false) {
2405
- $height = (float)$this->length_in_pt($height);
2406
  }
2407
 
2408
- $this->_props_computed["background_size"] = "$width $height";
2409
  }
2410
 
2411
  /**
2412
- * Sets the background - combined options
2413
- *
2414
- * @link http://www.w3.org/TR/CSS21/colors.html#propdef-background
2415
- * @param string $value
2416
- * @param bool $important
2417
  */
2418
- function set_background($value, bool $important = false)
2419
  {
2420
- if ($value === "none") {
2421
- $this->set_prop("background_image", "none", $important);
2422
- $this->set_prop("background_color", "transparent", $important);
2423
- } else {
2424
- $components = $this->parse_property_value($value);
2425
- $pos_size = [];
2426
-
2427
- foreach ($components as $val) {
2428
- if ($val === "none" || mb_substr($val, 0, 4) === "url(") {
2429
- $this->set_prop("background_image", $val, $important);
2430
- } elseif ($val === "fixed" || $val === "scroll") {
2431
- $this->set_prop("background_attachment", $val, $important);
2432
- } elseif ($val === "repeat" || $val === "repeat-x" || $val === "repeat-y" || $val === "no-repeat") {
2433
- $this->set_prop("background_repeat", $val, $important);
2434
- } elseif ($this->is_color_value($val)) {
2435
- $this->set_prop("background_color", $val, $important);
2436
- } else {
2437
- $pos_size[] = $val;
2438
- }
2439
  }
 
2440
 
2441
- if (count($pos_size)) {
2442
- // Split value list at "/"
2443
- $index = array_search("/", $pos_size, true);
2444
 
2445
- if ($index !== false) {
2446
- $pos = array_slice($pos_size, 0, $index);
2447
- $size = array_slice($pos_size, $index + 1);
2448
- } else {
2449
- $pos = $pos_size;
2450
- $size = [];
2451
- }
2452
 
2453
- $this->set_prop("background_position", implode(" ", $pos), $important);
2454
 
2455
- if (count($size)) {
2456
- $this->set_prop("background_size", implode(" ", $size), $important);
2457
- }
2458
  }
2459
  }
 
 
2460
  }
2461
 
2462
  /**
2463
- * Sets the font size
2464
- *
2465
- * $size can be any acceptable CSS size
2466
- *
2467
- * @link http://www.w3.org/TR/CSS21/fonts.html#propdef-font-size
2468
- * @param string|float $size
2469
  */
2470
- function set_font_size($size)
2471
  {
2472
- $this->_prop_cache["font_size"] = null;
2473
-
2474
- if ($size === "inherit") {
2475
- $this->_props_computed["font_size"] = null;
2476
- return;
2477
- }
2478
-
2479
  $parent_font_size = isset($this->parent_style)
2480
  ? $this->parent_style->__get("font_size")
2481
  : self::$default_font_size;
2482
 
2483
- switch ((string)$size) {
2484
  case "xx-small":
2485
  case "x-small":
2486
  case "small":
@@ -2504,23 +2641,14 @@ class Style
2504
  break;
2505
  }
2506
 
2507
- $this->_props_computed["font_size"] = $fs;
2508
  }
2509
 
2510
  /**
2511
- * Sets the font weight
2512
- *
2513
- * @param string|int $weight
2514
  */
2515
- function set_font_weight($weight)
2516
  {
2517
- $this->_prop_cache["font_weight"] = null;
2518
-
2519
- if ($weight === "inherit") {
2520
- $this->_props_computed["font_weight"] = null;
2521
- return;
2522
- }
2523
-
2524
  $computed_weight = $weight;
2525
 
2526
  if ($weight === "bolder") {
@@ -2531,80 +2659,99 @@ class Style
2531
  $computed_weight = "normal";
2532
  }
2533
 
2534
- $this->_props_computed["font_weight"] = $computed_weight;
2535
  }
2536
 
2537
  /**
2538
- * Sets the font style
2539
  *
2540
- * combined attributes
2541
- * set individual attributes also, respecting !important mark
2542
- * exactly this order, separate by space. Multiple fonts separated by comma:
2543
- * font-style, font-variant, font-weight, font-size, line-height, font-family
2544
- *
2545
- * Other than with border and list, existing partial attributes should
2546
- * reset when starting here, even when not mentioned.
2547
- * If individual attribute is !important and explicit or implicit replacement is not,
2548
- * keep individual attribute
2549
- *
2550
- * require whitespace as delimiters for single value attributes
2551
- * On delimiter "/" treat first as font height, second as line height
2552
- * treat all remaining at the end of line as font
2553
- * font-style, font-variant, font-weight, font-size, line-height, font-family
2554
- *
2555
- * missing font-size and font-family might be not allowed, but accept it here and
2556
- * use default (medium size, empty font name)
2557
  *
2558
  * @link https://www.w3.org/TR/CSS21/fonts.html#font-shorthand
2559
- * @param string $val
2560
- * @param bool $important
2561
  */
2562
- function set_font($val, bool $important = false)
2563
  {
2564
- if (preg_match("/^(italic|oblique|normal)\s*(.*)$/i", $val, $match)) {
2565
- $this->set_prop("font_style", $match[1], $important);
2566
- $val = $match[2];
 
 
 
 
 
 
 
 
 
 
 
 
2567
  }
2568
 
2569
- if (preg_match("/^(small-caps|normal)\s*(.*)$/i", $val, $match)) {
2570
- $this->set_prop("font_variant", $match[1], $important);
2571
- $val = $match[2];
2572
  }
2573
 
2574
- //matching numeric value followed by unit -> this is indeed a subsequent font size. Skip!
2575
- if (preg_match("/^(bold|bolder|lighter|100|200|300|400|500|600|700|800|900|normal)\s*(.*)$/i", $val, $match) &&
2576
- !preg_match("/^(?:pt|px|pc|rem|em|ex|in|cm|mm|%)/", $match[2])
2577
- ) {
2578
- $this->set_prop("font_weight", $match[1], $important);
2579
- $val = $match[2];
 
 
2580
  }
2581
 
2582
- if (preg_match("/^(xx-small|x-small|small|medium|large|x-large|xx-large|smaller|larger|\d+\s*(?:pt|px|pc|rem|em|ex|in|cm|mm|%))(?:\/|\s*)(.*)$/i", $val, $match)) {
2583
- $this->set_prop("font_size", $match[1], $important);
2584
- $val = $match[2];
2585
- if (preg_match("/^(?:\/|\s*)(\d+\s*(?:pt|px|pc|rem|em|ex|in|cm|mm|%)?)\s*(.*)$/i", $val, $match)) {
2586
- $this->set_prop("line_height", $match[1], $important);
2587
- $val = $match[2];
 
 
 
 
 
 
 
2588
  }
2589
  }
2590
 
2591
- if (strlen($val) != 0) {
2592
- $this->set_prop("font_family", $val, $important);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2593
  }
 
 
 
 
2594
  }
2595
 
2596
  /**
2597
- * Sets the text alignment
2598
  *
2599
  * If no alignment is set on the element and the direction is rtl then
2600
  * the property is set to "right", otherwise it is set to "left".
2601
  *
2602
  * @link https://www.w3.org/TR/CSS21/text.html#propdef-text-align
2603
  */
2604
- public function set_text_align($val)
2605
  {
2606
- $this->_prop_cache["text_align"] = null;
2607
-
2608
  $alignment = $val;
2609
  if ($alignment === "") {
2610
  $alignment = "left";
@@ -2613,607 +2760,577 @@ class Style
2613
  }
2614
  }
2615
 
2616
- if (!in_array($alignment, self::$text_align_keywords, true)) {
2617
- $this->_props_computed["text_align"] = null;
2618
- return;
2619
  }
2620
 
2621
- $this->_props_computed["text_align"] = $alignment;
2622
  }
2623
 
2624
  /**
2625
- * Sets word spacing property
2626
- *
2627
- * @link http://www.w3.org/TR/CSS21/text.html#propdef-word-spacing
2628
- * @param $val
2629
  */
2630
- function set_word_spacing($val)
2631
  {
2632
- $this->_prop_cache["word_spacing"] = null;
2633
-
2634
- if ($val === "inherit") {
2635
- $this->_props_computed["word_spacing"] = null;
2636
- return;
2637
  }
2638
 
2639
- if ($val === "normal" || strpos($val, "%") !== false) {
2640
- $this->_props_computed["word_spacing"] = $val;
2641
- } else {
2642
- $this->_props_computed["word_spacing"] = ((float)$this->length_in_pt($val, $this->__get("font_size"))) . "pt";
2643
- }
2644
  }
2645
 
2646
  /**
2647
- * Sets letter spacing property
2648
- *
2649
- * @link http://www.w3.org/TR/CSS21/text.html#propdef-letter-spacing
2650
- * @param $val
2651
  */
2652
- function set_letter_spacing($val)
2653
  {
2654
- $this->_prop_cache["letter_spacing"] = null;
2655
-
2656
- if ($val === "inherit") {
2657
- $this->_props_computed["letter_spacing"] = null;
2658
- return;
2659
- }
2660
-
2661
  if ($val === "normal") {
2662
- $this->_props_computed["letter_spacing"] = $val;
2663
- } else {
2664
- $this->_props_computed["letter_spacing"] = ((float)$this->length_in_pt($val, $this->__get("font_size"))) . "pt";
2665
  }
 
 
2666
  }
2667
 
2668
  /**
2669
- * Sets line height property
2670
- *
2671
- * @link http://www.w3.org/TR/CSS21/visudet.html#propdef-line-height
2672
- * @param $val
2673
  */
2674
- function set_line_height($val)
2675
  {
2676
- $this->_prop_cache["line_height"] = null;
2677
-
2678
- if ($val === "inherit") {
2679
- $this->_props_computed["line_height"] = null;
2680
- return;
2681
  }
2682
 
2683
- if ($val === "normal" || is_numeric($val)) {
2684
- $this->_props_computed["line_height"] = $val;
2685
- } else {
2686
- $this->_props_computed["line_height"] = ((float)$this->length_in_pt($val, $this->__get("font_size"))) . "pt";
2687
  }
 
 
 
 
2688
  }
2689
 
2690
  /**
2691
- * Sets page break properties
2692
- *
2693
- * @link http://www.w3.org/TR/CSS21/page.html#page-breaks
2694
- * @param string $break
2695
  */
2696
- function set_page_break_before($break)
2697
  {
2698
- $this->_prop_cache["page_break_before"] = null;
2699
-
2700
- if ($break === "inherit") {
2701
- $this->_props_computed["page_break_before"] = null;
2702
- return;
2703
- }
2704
 
 
 
 
 
 
2705
  if ($break === "left" || $break === "right") {
2706
  $break = "always";
2707
  }
2708
 
2709
- $this->_props_computed["page_break_before"] = $break;
2710
  }
2711
 
2712
  /**
2713
- * @param $break
2714
  */
2715
- function set_page_break_after($break)
2716
  {
2717
- $this->_prop_cache["page_break_after"] = null;
2718
-
2719
- if ($break === "inherit") {
2720
- $this->_props_computed["page_break_after"] = null;
2721
- return;
2722
- }
2723
-
2724
  if ($break === "left" || $break === "right") {
2725
  $break = "always";
2726
  }
2727
 
2728
- $this->_props_computed["page_break_after"] = $break;
2729
  }
2730
 
2731
  /**
2732
- * Sets the margin size
2733
- *
2734
- * @link http://www.w3.org/TR/CSS21/box.html#margin-properties
2735
- * @param $val
2736
  */
2737
- function set_margin_top($val)
2738
  {
2739
- $this->_set_style_side_type("margin", "top", "", $val);
 
 
 
 
2740
  }
2741
 
2742
  /**
2743
- * @param $val
2744
  */
2745
- function set_margin_right($val)
2746
  {
2747
- $this->_set_style_side_type("margin", "right", "", $val);
 
 
 
 
2748
  }
2749
 
2750
  /**
2751
- * @param $val
2752
  */
2753
- function set_margin_bottom($val)
2754
  {
2755
- $this->_set_style_side_type("margin", "bottom", "", $val);
 
 
 
 
 
2756
  }
2757
 
2758
  /**
2759
- * @param $val
2760
  */
2761
- function set_margin_left($val)
2762
  {
2763
- $this->_set_style_side_type("margin", "left", "", $val);
 
 
 
 
 
2764
  }
2765
 
2766
  /**
2767
- * @param string $val
2768
- * @param bool $important
2769
  */
2770
- function set_margin($val, bool $important = false)
2771
  {
2772
- $this->_set_style_type("margin", "", $val, $important);
 
 
 
 
 
2773
  }
2774
 
2775
  /**
2776
- * Sets the padding size
2777
- *
2778
- * @link http://www.w3.org/TR/CSS21/box.html#padding-properties
2779
- * @param $val
2780
  */
2781
- function set_padding_top($val)
2782
  {
2783
- $this->_set_style_side_type("padding", "top", "", $val);
 
 
 
 
 
2784
  }
2785
 
2786
  /**
2787
- * @param $val
 
2788
  */
2789
- function set_padding_right($val)
2790
  {
2791
- $this->_set_style_side_type("padding", "right", "", $val);
2792
  }
2793
 
2794
  /**
2795
- * @param $val
 
2796
  */
2797
- function set_padding_bottom($val)
2798
  {
2799
- $this->_set_style_side_type("padding", "bottom", "", $val);
 
 
 
 
2800
  }
2801
 
2802
- /**
2803
- * @param $val
2804
- */
2805
- function set_padding_left($val)
2806
  {
2807
- $this->_set_style_side_type("padding", "left", "", $val);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2808
  }
2809
 
2810
  /**
2811
- * @param string $val
2812
- * @param bool $important
2813
  */
2814
- function set_padding($val, bool $important = false)
2815
  {
2816
- $this->_set_style_type("padding", "", $val, $important);
2817
  }
2818
 
2819
  /**
2820
- * Sets a single border
2821
- *
2822
- * @param string $side
2823
- * @param string $border_spec ([width] [style] [color])
2824
- * @param bool $important
2825
  */
2826
- protected function _set_border($side, $border_spec, bool $important)
2827
  {
2828
- $components = $this->parse_property_value($border_spec);
 
 
 
2829
 
2830
- foreach ($components as $val) {
2831
- if (in_array($val, self::$BORDER_STYLES, true)) {
2832
- $this->set_prop("border_${side}_style", $val, $important);
2833
- } elseif ($this->is_color_value($val)) {
2834
- $this->set_prop("border_${side}_color", $val, $important);
2835
- } else {
2836
- // Assume width
2837
- $this->set_prop("border_${side}_width", $val, $important);
2838
- }
2839
  }
 
 
2840
  }
2841
 
2842
- /**
2843
- * @link http://www.w3.org/TR/CSS21/box.html#border-properties
2844
- * @param string $val
2845
- * @param bool $important
2846
- */
2847
- function set_border_top($val, bool $important = false)
2848
  {
2849
- $this->_set_border("top", $val, $important);
2850
  }
2851
 
2852
- function set_border_top_color($val)
2853
  {
2854
- $this->_set_style_side_type("border", "top", "color", $val);
2855
  }
2856
 
2857
- function set_border_top_style($val)
2858
  {
2859
- $this->_set_style_side_type("border", "top", "style", $val);
2860
  }
2861
 
2862
- function set_border_top_width($val)
2863
  {
2864
- $this->_set_style_side_type("border", "top", "width", $val);
2865
  }
2866
 
2867
  /**
2868
- * @param string $val
2869
- * @param bool $important
2870
  */
2871
- function set_border_right($val, bool $important = false)
2872
  {
2873
- $this->_set_border("right", $val, $important);
2874
  }
2875
 
2876
- function set_border_right_color($val)
 
 
 
 
2877
  {
2878
- $this->_set_style_side_type("border", "right", "color", $val);
 
 
 
 
 
2879
  }
2880
 
2881
- function set_border_right_style($val)
2882
  {
2883
- $this->_set_style_side_type("border", "right", "style", $val);
2884
  }
2885
 
2886
- function set_border_right_width($val)
2887
  {
2888
- $this->_set_style_side_type("border", "right", "width", $val);
2889
  }
2890
 
2891
- /**
2892
- * @param string $val
2893
- * @param bool $important
2894
- */
2895
- function set_border_bottom($val, bool $important = false)
2896
  {
2897
- $this->_set_border("bottom", $val, $important);
2898
  }
2899
 
2900
- function set_border_bottom_color($val)
2901
  {
2902
- $this->_set_style_side_type("border", "bottom", "color", $val);
2903
  }
2904
 
2905
- function set_border_bottom_style($val)
 
 
 
 
 
 
2906
  {
2907
- $this->_set_style_side_type("border", "bottom", "style", $val);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2908
  }
2909
 
2910
- function set_border_bottom_width($val)
 
 
 
 
2911
  {
2912
- $this->_set_style_side_type("border", "bottom", "width", $val);
 
 
 
 
 
 
 
 
 
 
 
2913
  }
2914
 
2915
  /**
2916
- * @param string $val
2917
- * @param bool $important
 
2918
  */
2919
- function set_border_left($val, bool $important = false)
2920
  {
2921
- $this->_set_border("left", $val, $important);
 
 
 
 
 
 
2922
  }
2923
 
2924
- function set_border_left_color($val)
2925
  {
2926
- $this->_set_style_side_type("border", "left", "color", $val);
2927
  }
2928
 
2929
- function set_border_left_style($val)
2930
  {
2931
- $this->_set_style_side_type("border", "left", "style", $val);
2932
  }
2933
 
2934
- function set_border_left_width($val)
2935
  {
2936
- $this->_set_style_side_type("border", "left", "width", $val);
2937
  }
2938
 
2939
- /**
2940
- * @param string $val
2941
- * @param bool $important
2942
- */
2943
- function set_border($val, bool $important = false)
2944
  {
2945
- $this->_set_border("top", $val, $important);
2946
- $this->_set_border("right", $val, $important);
2947
- $this->_set_border("bottom", $val, $important);
2948
- $this->_set_border("left", $val, $important);
2949
  }
2950
 
2951
  /**
2952
- * @param string $val
2953
- * @param bool $important
2954
  */
2955
- function set_border_width($val, bool $important = false)
2956
  {
2957
- $this->_set_style_type("border", "width", $val, $important);
2958
  }
2959
 
2960
- /**
2961
- * @param string $val
2962
- * @param bool $important
2963
- */
2964
- function set_border_color($val, bool $important = false)
2965
  {
2966
- $this->_set_style_type("border", "color", $val, $important);
2967
  }
2968
 
2969
- /**
2970
- * @param string $val
2971
- * @param bool $important
2972
- */
2973
- function set_border_style($val, bool $important = false)
2974
  {
2975
- $this->_set_style_type("border", "style", $val, $important);
2976
  }
2977
 
2978
- /**
2979
- * Sets the border radius size
2980
- *
2981
- * http://www.w3.org/TR/css3-background/#corners
2982
- *
2983
- * @param string $val
2984
- */
2985
- function set_border_top_left_radius($val)
2986
  {
2987
- $this->_set_border_radius_corner($val, "top_left");
2988
  }
2989
 
2990
- /**
2991
- * @param string $val
2992
- */
2993
- function set_border_top_right_radius($val)
2994
  {
2995
- $this->_set_border_radius_corner($val, "top_right");
2996
  }
2997
 
2998
  /**
2999
- * @param string $val
3000
  */
3001
- function set_border_bottom_left_radius($val)
3002
  {
3003
- $this->_set_border_radius_corner($val, "bottom_left");
3004
  }
3005
 
3006
- /**
3007
- * @param string $val
3008
- */
3009
- function set_border_bottom_right_radius($val)
3010
  {
3011
- $this->_set_border_radius_corner($val, "bottom_right");
3012
  }
3013
 
3014
- /**
3015
- * @param string $val
3016
- * @param bool $important
3017
- */
3018
- function set_border_radius($val, bool $important = false)
3019
  {
3020
- $r = $this->parse_property_value($val);
 
3021
 
3022
- switch (count($r)) {
3023
- case 1:
3024
- [$tl, $tr, $br, $bl] = [$r[0], $r[0], $r[0], $r[0]];
3025
- break;
3026
- case 2:
3027
- [$tl, $tr, $br, $bl] = [$r[0], $r[1], $r[0], $r[1]];
3028
- break;
3029
- case 3:
3030
- [$tl, $tr, $br, $bl] = [$r[0], $r[1], $r[2], $r[1]];
3031
- break;
3032
- case 4:
3033
- [$tl, $tr, $br, $bl] = [$r[0], $r[1], $r[2], $r[3]];
3034
- break;
3035
- default:
3036
- return;
3037
- }
3038
 
3039
- $this->set_prop("border_top_left_radius", $tl, $important);
3040
- $this->set_prop("border_top_right_radius", $tr, $important);
3041
- $this->set_prop("border_bottom_right_radius", $br, $important);
3042
- $this->set_prop("border_bottom_left_radius", $bl, $important);
3043
  }
3044
 
3045
  /**
3046
- * @param string $val
3047
- * @param string $corner
3048
  */
3049
- protected function _set_border_radius_corner($val, $corner)
3050
  {
3051
- $prop = "border_" . $corner . "_radius";
 
3052
 
3053
- $this->_prop_cache[$prop] = null;
 
 
 
3054
 
3055
- if ($val === "inherit") {
3056
- $this->_props_computed[$prop] = null;
3057
- return;
3058
- }
3059
 
3060
- $computed = mb_strpos($val, "%") === false
3061
- ? $this->single_length_in_pt($val)
3062
- : $val;
 
3063
 
3064
- $this->_props_computed[$prop] = $computed;
 
 
3065
  }
3066
 
3067
  /**
3068
- * @return float|int|string
 
3069
  */
3070
- function get_border_top_left_radius()
3071
  {
3072
- return $this->_get_border_radius_corner("top_left");
3073
  }
3074
 
3075
- /**
3076
- * @return float|int|string
3077
- */
3078
- function get_border_top_right_radius()
3079
  {
3080
- return $this->_get_border_radius_corner("top_right");
3081
  }
3082
 
3083
- /**
3084
- * @return float|int|string
3085
- */
3086
- function get_border_bottom_left_radius()
3087
  {
3088
- return $this->_get_border_radius_corner("bottom_left");
3089
  }
3090
 
3091
- /**
3092
- * @return float|int|string
3093
- */
3094
- function get_border_bottom_right_radius()
3095
  {
3096
- return $this->_get_border_radius_corner("bottom_right");
 
 
 
 
 
3097
  }
3098
 
3099
  /**
3100
- * @param $corner
3101
- * @return float|int|string
3102
  */
3103
- protected function _get_border_radius_corner($corner)
3104
  {
3105
- $prop = "border_" . $corner . "_radius";
3106
 
3107
- if (!isset($this->_props_computed[$prop])) {
3108
- return 0;
3109
  }
3110
 
3111
- return $this->_props_computed[$prop];
3112
  }
3113
 
3114
- /**
3115
- * Sets the outline styles
3116
- *
3117
- * @link http://www.w3.org/TR/CSS21/ui.html#dynamic-outlines
3118
- * @param string $value
3119
- * @param bool $important
3120
- */
3121
- function set_outline($value, bool $important = false)
3122
  {
3123
- $components = $this->parse_property_value($value);
3124
-
3125
- foreach ($components as $val) {
3126
- if (in_array($val, self::$BORDER_STYLES, true)) {
3127
- $this->set_prop("outline_style", $val, $important);
3128
- } elseif ($this->is_color_value($val)) {
3129
- $this->set_prop("outline_color", $val, $important);
3130
- } else {
3131
- // Assume width
3132
- $this->set_prop("outline_width", $val, $important);
3133
- }
3134
- }
3135
  }
3136
 
3137
- /**
3138
- * @param $val
3139
- */
3140
- function set_outline_width($val)
3141
  {
3142
- $this->_set_style_side_type("outline", "", "width", $val);
3143
  }
3144
 
3145
- /**
3146
- * @param $val
3147
- */
3148
- function set_outline_color($val)
3149
  {
3150
- $this->_set_style_side_type("outline", "", "color", $val);
3151
  }
3152
 
3153
  /**
3154
- * @param $val
3155
  */
3156
- function set_outline_style($val)
3157
  {
3158
- $this->_set_style_side_type("outline", "", "style", $val);
3159
  }
3160
 
3161
  /**
3162
- * Sets the border spacing
 
3163
  *
3164
- * @link http://www.w3.org/TR/CSS21/box.html#border-properties
3165
- * @param float $val
3166
  */
3167
- function set_border_spacing($val)
3168
  {
3169
- $this->_prop_cache["border_spacing"] = null;
3170
 
3171
- if ($val === "inherit") {
3172
- $this->_props_computed["border_spacing"] = null;
3173
- return;
3174
  }
3175
 
3176
- $arr = explode(" ", $val);
 
 
 
3177
 
3178
- if (count($arr) === 1) {
3179
- $arr[1] = $arr[0];
3180
  }
3181
 
3182
- $this->_props_computed["border_spacing"] = "$arr[0] $arr[1]";
3183
  }
3184
 
3185
  /**
3186
- * Sets the list style image
3187
- *
3188
- * @link http://www.w3.org/TR/CSS21/generate.html#propdef-list-style-image
3189
- * @param $val
3190
  */
3191
- function set_list_style_image($val)
3192
  {
3193
- $this->_prop_cache["list_style_image"] = null;
3194
-
3195
- if ($val === "inherit") {
3196
- $this->_props_computed["list_style_image"] = null;
3197
- return;
3198
- }
3199
-
3200
  $parsed_val = $this->_stylesheet->resolve_url($val);
3201
 
3202
  if ($parsed_val === "none") {
3203
- $this->_props_computed["list_style_image"] = "none";
3204
  } else {
3205
- $this->_props_computed["list_style_image"] = "url(" . $parsed_val . ")";
3206
  }
3207
  }
3208
 
3209
  /**
3210
- * Sets the list style
3211
- *
3212
- * @link http://www.w3.org/TR/CSS21/generate.html#propdef-list-style
3213
- * @param string $value
3214
- * @param bool $important
3215
  */
3216
- function set_list_style($value, bool $important = false)
3217
  {
3218
  static $positions = ["inside", "outside"];
3219
  static $types = [
@@ -3229,14 +3346,15 @@ class Style
3229
  ];
3230
 
3231
  $components = $this->parse_property_value($value);
 
3232
 
3233
  foreach ($components as $val) {
3234
- /* http://www.w3.org/TR/CSS21/generate.html#list-style
3235
  * A value of 'none' for the 'list-style' property sets both 'list-style-type' and 'list-style-image' to 'none'
3236
  */
3237
  if ($val === "none") {
3238
- $this->set_prop("list_style_type", $val, $important);
3239
- $this->set_prop("list_style_image", $val, $important);
3240
  continue;
3241
  }
3242
 
@@ -3246,70 +3364,87 @@ class Style
3246
  //Internet Explorer 7/8 and dompdf is right.
3247
 
3248
  if (mb_substr($val, 0, 4) === "url(") {
3249
- $this->set_prop("list_style_image", $val, $important);
3250
  continue;
3251
  }
3252
 
3253
- if (in_array($val, $types, true)) {
3254
- $this->set_prop("list_style_type", $val, $important);
3255
- } elseif (in_array($val, $positions, true)) {
3256
- $this->set_prop("list_style_position", $val, $important);
3257
  }
3258
  }
 
 
3259
  }
3260
 
3261
  /**
3262
- * @param $val
3263
  */
3264
- function set_size($val)
3265
  {
3266
- $this->_prop_cache["size"] = null;
3267
-
3268
- $length_re = "/(\d+\s*(?:pt|px|pc|rem|em|ex|in|cm|mm|%))/";
3269
-
3270
- $val = mb_strtolower($val);
3271
-
3272
  if ($val === "auto") {
3273
- $this->_props_computed["size"] = $val;
3274
- return;
3275
  }
3276
 
3277
- $parts = preg_split("/\s+/", $val);
 
 
 
 
 
3278
 
3279
- $computed = [];
3280
- if (preg_match($length_re, $parts[0])) {
3281
- $computed[] = $this->length_in_pt($parts[0]);
3282
 
3283
- if (isset($parts[1]) && preg_match($length_re, $parts[1])) {
3284
- $computed[] = $this->length_in_pt($parts[1]);
 
 
 
3285
  } else {
3286
- $computed[] = $computed[0];
3287
  }
 
3288
 
3289
- if (isset($parts[2]) && $parts[2] === "landscape") {
3290
- $computed = array_reverse($computed);
3291
- }
3292
- } elseif (isset(CPDF::$PAPER_SIZES[$parts[0]])) {
3293
- $computed = array_slice(CPDF::$PAPER_SIZES[$parts[0]], 2, 2);
3294
 
3295
- if (isset($parts[1]) && $parts[1] === "landscape") {
3296
- $computed = array_reverse($computed);
3297
- }
 
 
 
 
3298
  } else {
3299
- $this->_props_computed["size"] = null;
3300
- return;
 
 
 
 
 
3301
  }
3302
 
3303
- $this->_props_computed["size"] = $computed;
 
 
 
 
 
 
3304
  }
3305
 
3306
  /**
3307
- * Gets the CSS3 transform property
 
3308
  *
3309
- * @link http://www.w3.org/TR/css3-2d-transforms/#transform-property
3310
- * @return array|null
3311
  */
3312
- function get_transform()
3313
  {
3314
  //TODO: should be handled in setter (lengths set to absolute)
3315
 
@@ -3317,8 +3452,8 @@ class Style
3317
  $tr_value = "\s*([^,\s]+)\s*";
3318
  $angle = "\s*([^,\s]+(?:deg|rad)?)\s*";
3319
 
3320
- if (!preg_match_all("/[a-z]+\([^\)]+\)/i", $this->_props_computed["transform"], $parts, PREG_SET_ORDER)) {
3321
- return null;
3322
  }
3323
 
3324
  $functions = [
@@ -3346,7 +3481,7 @@ class Style
3346
 
3347
  foreach ($functions as $name => $pattern) {
3348
  if (preg_match("/$name\s*$pattern/i", $t, $matches)) {
3349
- $values = array_slice($matches, 1);
3350
 
3351
  switch ($name) {
3352
  // <angle> units
@@ -3357,9 +3492,9 @@ class Style
3357
 
3358
  foreach ($values as $i => $value) {
3359
  if (strpos($value, "rad")) {
3360
- $values[$i] = rad2deg(floatval($value));
3361
  } else {
3362
- $values[$i] = floatval($value);
3363
  }
3364
  }
3365
 
@@ -3431,54 +3566,21 @@ class Style
3431
  }
3432
 
3433
  /**
3434
- * @param $val
3435
- */
3436
- function set_transform($val)
3437
- {
3438
- $this->_prop_cache["transform"] = null;
3439
-
3440
- if ($val === "inherit") {
3441
- $this->_props_computed["transform"] = null;
3442
- return;
3443
- }
3444
-
3445
- $this->_props_computed["transform"] = $val;
3446
- }
3447
-
3448
- /**
3449
- * Sets the CSS3 transform-origin property
3450
- *
3451
- * @link http://www.w3.org/TR/css3-2d-transforms/#transform-origin
3452
- * @param string $val
3453
- */
3454
- function set_transform_origin($val)
3455
- {
3456
- $this->_prop_cache["transform_origin"] = null;
3457
-
3458
- if ($val === "inherit") {
3459
- $this->_props_computed["transform_origin"] = null;
3460
- return;
3461
- }
3462
-
3463
- $this->_props_computed["transform_origin"] = $val;
3464
- }
3465
-
3466
- /**
3467
- * Gets the CSS3 transform-origin property
3468
  *
3469
- * @link http://www.w3.org/TR/css3-2d-transforms/#transform-origin
3470
- * @return mixed[]
3471
  */
3472
- function get_transform_origin()
3473
  {
3474
  //TODO: should be handled in setter
3475
-
3476
- $values = preg_split("/\s+/", $this->_props_computed["transform_origin"]);
3477
 
3478
  $values = array_map(function ($value) {
3479
- if (in_array($value, ["top", "left"])) {
3480
  return 0;
3481
- } else if (in_array($value, ["bottom", "right"])) {
3482
  return "100%";
3483
  } else {
3484
  return $value;
@@ -3493,10 +3595,10 @@ class Style
3493
  }
3494
 
3495
  /**
3496
- * @param $val
3497
- * @return null
3498
  */
3499
- protected function parse_image_resolution($val)
3500
  {
3501
  // If exif data could be get:
3502
  // $re = '/^\s*(\d+|normal|auto)(?:\s*,\s*(\d+|normal))?\s*$/';
@@ -3512,60 +3614,65 @@ class Style
3512
 
3513
  /**
3514
  * auto | normal | dpi
3515
- *
3516
- * @param $val
3517
  */
3518
- function set_background_image_resolution($val)
3519
  {
3520
- $this->_prop_cache["background_image_resolution"] = null;
 
3521
 
3522
- if ($val === "inherit") {
3523
- $this->_props_computed["background_image_resolution"] = null;
3524
- return;
3525
- }
 
 
 
3526
 
3527
- $parsed = $this->parse_image_resolution($val);
 
 
 
 
 
 
3528
 
3529
- $this->_props_computed["background_image_resolution"] = $parsed;
 
 
 
 
 
3530
  }
3531
 
3532
  /**
3533
- * auto | normal | dpi
3534
- *
3535
- * @param $val
3536
  */
3537
- function set_image_resolution($val)
3538
  {
3539
- $this->_prop_cache["image_resolution"] = null;
 
3540
 
3541
- if ($val === "inherit") {
3542
- $this->_props_computed["image_resolution"] = null;
3543
- return;
3544
  }
3545
 
3546
- $parsed = $this->parse_image_resolution($val);
 
 
3547
 
3548
- $this->_props_computed["image_resolution"] = $parsed;
3549
  }
3550
 
3551
  /**
3552
- * @param $val
3553
  */
3554
- function set_z_index($val)
3555
  {
3556
- $this->_prop_cache["z_index"] = null;
3557
-
3558
- if ($val === "inherit") {
3559
- $this->_props_computed["z_index"] = null;
3560
- return;
3561
- }
3562
-
3563
- if ($val !== "auto" && round((float) $val) != $val) {
3564
- $this->_props_computed["z_index"] = null;
3565
- return;
3566
  }
3567
 
3568
- $this->_props_computed["z_index"] = $val;
3569
  }
3570
 
3571
  /**
@@ -3595,18 +3702,18 @@ class Style
3595
  * @return string
3596
  */
3597
  /*DEBUGCSS print: see below additional debugging util*/
3598
- function __toString()
3599
  {
3600
  $parent_font_size = $this->parent_style
3601
  ? $this->parent_style->font_size
3602
  : self::$default_font_size;
3603
 
3604
- return print_r(array_merge(["parent_font_size" => $parent_font_size ],
3605
  $this->_props), true);
3606
  }
3607
 
3608
  /*DEBUGCSS*/
3609
- function debug_print()
3610
  {
3611
  $parent_font_size = $this->parent_style
3612
  ? $this->parent_style->font_size
@@ -3630,7 +3737,7 @@ class Style
3630
  }
3631
  print " ]\n";
3632
  print " cached [\n";
3633
- foreach ($this->_prop_cache as $prop => $val) {
3634
  print ' ' . $prop . ': ' . preg_replace("/\r\n/", ' ', print_r($val, true));
3635
  print ";\n";
3636
  }
22
  * It includes methods to resolve colors and lengths, as well as getters &
23
  * setters for many CSS properties.
24
  *
25
+ * Access to the different CSS properties is provided by the methods
26
+ * {@link Style::set_prop()} and {@link Style::get_specified()}, and the
27
+ * property overload methods {@link Style::__set()} and {@link Style::__get()},
28
+ * as well as {@link Style::set_used()}. The latter methods operate on used
29
+ * values and permit access to any (CSS) property using the following syntax:
30
+ *
31
+ * ```
32
+ * $style->margin_top = 10.0;
33
+ * echo $style->margin_top; // Returns `10.0`
34
+ * ```
35
+ *
36
+ * To declare a property from a string, use {@link Style::set_prop()}:
37
+ *
38
+ * ```
39
+ * $style->set_prop("margin_top", "1em");
40
+ * echo $style->get_specified("margin_top"); // Returns `1em`
41
+ * echo $style->margin_top; // Returns `12.0`, assuming the default font size
42
+ * ```
43
+ *
44
  * Actual CSS parsing is performed in the {@link Stylesheet} class.
45
  *
46
+ * @property string $azimuth
47
+ * @property string $background_attachment
48
+ * @property array|string $background_color
49
+ * @property string $background_image Image URL or `none`
50
+ * @property string $background_image_resolution
51
+ * @property array $background_position
52
+ * @property string $background_repeat
53
+ * @property array|string $background_size `cover`, `contain`, or `[width, height]`, each being a length, percentage, or `auto`
54
+ * @property string $border_collapse
55
+ * @property string $border_color Only use for setting all sides to the same color
56
+ * @property float[] $border_spacing Pair of `[horizontal, vertical]` spacing
57
+ * @property string $border_style Only use for setting all sides to the same style
58
+ * @property array|string $border_top_color
59
+ * @property array|string $border_right_color
60
+ * @property array|string $border_bottom_color
61
+ * @property array|string $border_left_color
62
+ * @property string $border_top_style Valid border style
63
+ * @property string $border_right_style Valid border style
64
+ * @property string $border_bottom_style Valid border style
65
+ * @property string $border_left_style Valid border style
66
+ * @property float $border_top_width Length in pt
67
+ * @property float $border_right_width Length in pt
68
+ * @property float $border_bottom_width Length in pt
69
+ * @property float $border_left_width Length in pt
70
+ * @property string $border_width Only use for setting all sides to the same width
71
+ * @property float|string $border_bottom_left_radius Radius in pt or a percentage value
72
+ * @property float|string $border_bottom_right_radius Radius in pt or a percentage value
73
+ * @property float|string $border_top_left_radius Radius in pt or a percentage value
74
+ * @property float|string $border_top_right_radius Radius in pt or a percentage value
75
+ * @property string $border_radius Only use for setting all corners to the same radius
76
+ * @property float|string $bottom Length in pt, a percentage value, or `auto`
77
+ * @property string $caption_side
78
+ * @property string $clear
79
+ * @property string $clip
80
+ * @property array|string $color
81
+ * @property string[]|string $content List of content components, `normal`, or `none`
82
+ * @property array|string $counter_increment Array defining the counters to increment or `none`
83
+ * @property array|string $counter_reset Array defining the counters to reset or `none`
84
+ * @property string $cue_after
85
+ * @property string $cue_before
86
+ * @property string $cue
87
+ * @property string $cursor
88
+ * @property string $direction
89
+ * @property string $display
90
+ * @property string $elevation
91
+ * @property string $empty_cells
92
+ * @property string $float
93
+ * @property string $font_family
94
+ * @property float $font_size Length in pt
95
+ * @property string $font_style
96
+ * @property string $font_variant
97
+ * @property string $font_weight
98
+ * @property float|string $height Length in pt, a percentage value, or `auto`
99
+ * @property string $image_resolution
100
+ * @property string $inset Only use for setting all box insets to the same length
101
+ * @property float|string $left Length in pt, a percentage value, or `auto`
102
+ * @property float $letter_spacing Length in pt
103
+ * @property float $line_height Length in pt
104
+ * @property string $list_style_image Image URL or `none`
105
+ * @property string $list_style_position
106
+ * @property string $list_style_type
107
+ * @property float|string $margin_right Length in pt, a percentage value, or `auto`
108
+ * @property float|string $margin_left Length in pt, a percentage value, or `auto`
109
+ * @property float|string $margin_top Length in pt, a percentage value, or `auto`
110
+ * @property float|string $margin_bottom Length in pt, a percentage value, or `auto`
111
+ * @property string $margin Only use for setting all sides to the same length
112
+ * @property float|string $max_height Length in pt, a percentage value, or `none`
113
+ * @property float|string $max_width Length in pt, a percentage value, or `none`
114
+ * @property float|string $min_height Length in pt, a percentage value, or `auto`
115
+ * @property float|string $min_width Length in pt, a percentage value, or `auto`
116
+ * @property float $opacity Number in the range [0, 1]
117
+ * @property int $orphans
118
+ * @property array|string $outline_color
119
+ * @property string $outline_style Valid border style, except for `hidden`
120
+ * @property float $outline_width Length in pt
121
+ * @property float $outline_offset Length in pt
122
+ * @property string $overflow
123
+ * @property string $overflow_wrap
124
+ * @property float|string $padding_top Length in pt or a percentage value
125
+ * @property float|string $padding_right Length in pt or a percentage value
126
+ * @property float|string $padding_bottom Length in pt or a percentage value
127
+ * @property float|string $padding_left Length in pt or a percentage value
128
+ * @property string $padding Only use for setting all sides to the same length
129
+ * @property string $page_break_after
130
+ * @property string $page_break_before
131
+ * @property string $page_break_inside
132
+ * @property string $pause_after
133
+ * @property string $pause_before
134
+ * @property string $pause
135
+ * @property string $pitch_range
136
+ * @property string $pitch
137
+ * @property string $play_during
138
+ * @property string $position
139
+ * @property string $quotes
140
+ * @property string $richness
141
+ * @property float|string $right Length in pt, a percentage value, or `auto`
142
+ * @property float[]|string $size Pair of `[width, height]` or `auto`
143
+ * @property string $speak_header
144
+ * @property string $speak_numeral
145
+ * @property string $speak_punctuation
146
+ * @property string $speak
147
+ * @property string $speech_rate
148
+ * @property string $src
149
+ * @property string $stress
150
+ * @property string $table_layout
151
+ * @property string $text_align
152
+ * @property string $text_decoration
153
+ * @property float|string $text_indent Length in pt or a percentage value
154
+ * @property string $text_transform
155
+ * @property float|string $top Length in pt, a percentage value, or `auto`
156
+ * @property array $transform List of transforms
157
+ * @property array $transform_origin
158
+ * @property string $unicode_bidi
159
+ * @property string $unicode_range
160
+ * @property string $vertical_align
161
+ * @property string $visibility
162
+ * @property string $voice_family
163
+ * @property string $volume
164
+ * @property string $white_space
165
+ * @property int $widows
166
+ * @property float|string $width Length in pt, a percentage value, or `auto`
167
+ * @property string $word_break
168
+ * @property float $word_spacing Length in pt
169
+ * @property int|string $z_index Integer value or `auto`
170
+ * @property string $_dompdf_keep
171
+ *
172
  * @package dompdf
173
  */
174
  class Style
175
  {
176
+ protected const CSS_IDENTIFIER = "-?[_a-zA-Z]+[_a-zA-Z0-9-]*";
177
+ protected const CSS_INTEGER = "[+-]?\d+";
178
+ protected const CSS_NUMBER = "[+-]?\d*\.?\d+(?:[eE][+-]?\d+)?";
179
 
180
  /**
181
  * Default font size, in points.
182
  *
183
  * @var float
184
  */
185
+ public static $default_font_size = 12;
186
 
187
  /**
188
  * Default line height, as a fraction of the font size.
189
  *
190
  * @var float
191
  */
192
+ public static $default_line_height = 1.2;
193
 
194
  /**
195
  * Default "absolute" font sizes relative to the default font-size
196
+ * https://www.w3.org/TR/css-fonts-3/#absolute-size-value
197
+ *
198
  * @var array<float>
199
  */
200
+ public static $font_size_keywords = [
201
  "xx-small" => 0.6, // 3/5
202
  "x-small" => 0.75, // 3/4
203
  "small" => 0.889, // 8/9
208
  ];
209
 
210
  /**
211
+ * List of valid text-align keywords.
 
 
212
  */
213
+ public const TEXT_ALIGN_KEYWORDS = ["left", "right", "center", "justify"];
214
 
215
  /**
216
+ * List of valid vertical-align keywords.
 
 
217
  */
218
+ public const VERTICAL_ALIGN_KEYWORDS = ["baseline", "bottom", "middle",
219
+ "sub", "super", "text-bottom", "text-top", "top"];
220
 
221
  /**
222
  * List of all block-level (outer) display types.
261
  ];
262
 
263
  /**
264
+ * List of all inline (inner) display types.
 
 
265
  */
266
+ public const INLINE_TYPES = ["inline"];
267
 
268
  /**
269
+ * List of all block (inner) display types.
 
 
270
  */
271
+ public const BLOCK_TYPES = ["block", "inline-block", "table-cell", "list-item"];
272
 
273
  /**
274
+ * List of all table (inner) display types.
 
 
275
  */
276
+ public const TABLE_TYPES = ["table", "inline-table"];
277
 
278
  /**
279
  * Lookup table for valid display types. Initially computed from the
284
  protected static $valid_display_types = [];
285
 
286
  /**
287
+ * List of all positioned types.
288
+ */
289
+ public const POSITIONED_TYPES = ["relative", "absolute", "fixed"];
290
+
291
+ /**
292
+ * List of valid border styles.
293
  */
294
+ public const BORDER_STYLES = [
295
+ "none", "hidden",
296
+ "dotted", "dashed", "solid",
297
+ "double", "groove", "ridge", "inset", "outset"
298
+ ];
299
 
300
  /**
301
+ * List of valid outline-style values.
302
+ * Same as the border styles, except `auto` is allowed, `hidden` is not.
303
  *
304
+ * @link https://www.w3.org/TR/css-ui-4/#typedef-outline-line-style
305
  */
306
+ protected const OUTLINE_STYLES = [
307
+ "auto", "none",
308
+ "dotted", "dashed", "solid",
309
+ "double", "groove", "ridge", "inset", "outset"
310
+ ];
311
 
312
  /**
313
  * Map of CSS shorthand properties and their corresponding sub-properties.
328
  "background_color"
329
  ],
330
  "border" => [
331
+ "border_top_width",
332
+ "border_right_width",
333
+ "border_bottom_width",
334
+ "border_left_width",
335
+ "border_top_style",
336
+ "border_right_style",
337
+ "border_bottom_style",
338
+ "border_left_style",
339
+ "border_top_color",
340
+ "border_right_color",
341
+ "border_bottom_color",
342
+ "border_left_color"
343
  ],
344
  "border_top" => [
345
  "border_top_width",
394
  "font_weight",
395
  "line_height"
396
  ],
397
+ "inset" => [
398
+ "top",
399
+ "right",
400
+ "bottom",
401
+ "left"
402
+ ],
403
  "list_style" => [
404
  "list_style_image",
405
  "list_style_position",
440
  /**
441
  * Default style values.
442
  *
443
+ * @link https://www.w3.org/TR/CSS21/propidx.html
444
  *
445
  * @var array
446
  */
449
  /**
450
  * List of inherited properties
451
  *
452
+ * @link https://www.w3.org/TR/CSS21/propidx.html
453
  *
454
  * @var array
455
  */
477
  protected $_media_queries;
478
 
479
  /**
480
+ * Properties set by an `!important` declaration.
 
481
  *
482
  * @var array
483
  */
484
+ protected $_important_props = [];
485
 
486
  /**
487
+ * Specified (or declared) values of the CSS properties.
488
+ *
489
+ * https://www.w3.org/TR/css-cascade-3/#value-stages
490
  *
491
  * @var array
492
  */
493
+ protected $_props = [];
494
 
495
  /**
496
  * Computed values of the CSS properties.
504
  *
505
  * @var array
506
  */
507
+ protected $_props_used = [];
508
+
509
+ /**
510
+ * Marks properties with non-final used values that should be cleared on
511
+ * style reset.
512
+ *
513
+ * @var array
514
+ */
515
+ protected $non_final_used = [];
516
 
517
  protected static $_dependency_map = [
518
  "border_top_style" => [
537
  "border_right_width",
538
  "border_bottom_width",
539
  "border_left_width",
540
+ "border_top_left_radius",
541
+ "border_top_right_radius",
542
+ "border_bottom_right_radius",
543
+ "border_bottom_left_radius",
544
+ "letter_spacing",
545
  "line_height",
546
  "margin_top",
547
  "margin_right",
552
  "padding_top",
553
  "padding_right",
554
  "padding_bottom",
555
+ "padding_left",
556
+ "word_spacing",
557
+ "width",
558
+ "height",
559
+ "min-width",
560
+ "min-height",
561
+ "max-width",
562
+ "max-height"
563
  ],
564
  "float" => [
565
  "display"
588
  protected $parent_style;
589
 
590
  /**
591
+ * @var Frame|null
592
  */
593
  protected $_frame;
594
 
599
  */
600
  protected $_origin = Stylesheet::ORIG_AUTHOR;
601
 
 
602
  /**
603
  * The computed bottom spacing
604
+ *
605
+ * @var float|string|null
606
  */
607
  private $_computed_bottom_spacing = null;
608
 
609
  /**
610
+ * @var bool|null
611
  */
612
  private $has_border_radius_cache = null;
613
 
614
  /**
615
+ * @var array|null
616
  */
617
  private $resolved_border_radius = null;
618
 
622
  private $fontMetrics;
623
 
624
  /**
625
+ * @param Stylesheet $stylesheet The stylesheet the style is associated with.
626
+ * @param int $origin
 
 
627
  */
628
+ public function __construct(Stylesheet $stylesheet, int $origin = Stylesheet::ORIG_AUTHOR)
629
  {
630
+ $this->fontMetrics = $stylesheet->getFontMetrics();
631
 
632
  $this->_stylesheet = $stylesheet;
633
  $this->_media_queries = [];
640
  $d =& self::$_defaults;
641
 
642
  // All CSS 2.1 properties, and their default values
643
+ // Some properties are specified with their computed value for
644
+ // efficiency; this only works if the computed value is not
645
+ // dependent on another property
646
  $d["azimuth"] = "center";
647
  $d["background_attachment"] = "scroll";
648
  $d["background_color"] = "transparent";
649
  $d["background_image"] = "none";
650
  $d["background_image_resolution"] = "normal";
651
+ $d["background_position"] = ["0%", "0%"];
652
  $d["background_repeat"] = "repeat";
653
  $d["background"] = "";
654
  $d["border_collapse"] = "separate";
655
  $d["border_color"] = "";
656
+ $d["border_spacing"] = [0.0, 0.0];
657
  $d["border_style"] = "";
658
  $d["border_top"] = "";
659
  $d["border_right"] = "";
672
  $d["border_bottom_width"] = "medium";
673
  $d["border_left_width"] = "medium";
674
  $d["border_width"] = "";
675
+ $d["border_bottom_left_radius"] = 0.0;
676
+ $d["border_bottom_right_radius"] = 0.0;
677
+ $d["border_top_left_radius"] = 0.0;
678
+ $d["border_top_right_radius"] = 0.0;
679
  $d["border_radius"] = "";
680
  $d["border"] = "";
681
  $d["bottom"] = "auto";
703
  $d["font"] = "";
704
  $d["height"] = "auto";
705
  $d["image_resolution"] = "normal";
706
+ $d["inset"] = "";
707
  $d["left"] = "auto";
708
  $d["letter_spacing"] = "normal";
709
  $d["line_height"] = "normal";
711
  $d["list_style_position"] = "outside";
712
  $d["list_style_type"] = "disc";
713
  $d["list_style"] = "";
714
+ $d["margin_right"] = 0.0;
715
+ $d["margin_left"] = 0.0;
716
+ $d["margin_top"] = 0.0;
717
+ $d["margin_bottom"] = 0.0;
718
  $d["margin"] = "";
719
  $d["max_height"] = "none";
720
  $d["max_width"] = "none";
721
  $d["min_height"] = "auto";
722
  $d["min_width"] = "auto";
723
+ $d["orphans"] = 2;
724
  $d["outline_color"] = "currentcolor"; // "invert" special color is not supported
725
  $d["outline_style"] = "none";
726
  $d["outline_width"] = "medium";
727
+ $d["outline_offset"] = 0.0;
728
  $d["outline"] = "";
729
  $d["overflow"] = "visible";
730
  $d["overflow_wrap"] = "normal";
731
+ $d["padding_top"] = 0.0;
732
+ $d["padding_right"] = 0.0;
733
+ $d["padding_bottom"] = 0.0;
734
+ $d["padding_left"] = 0.0;
735
  $d["padding"] = "";
736
  $d["page_break_after"] = "auto";
737
  $d["page_break_before"] = "auto";
756
  $d["table_layout"] = "auto";
757
  $d["text_align"] = "";
758
  $d["text_decoration"] = "none";
759
+ $d["text_indent"] = 0.0;
760
  $d["text_transform"] = "none";
761
  $d["top"] = "auto";
762
  $d["unicode_bidi"] = "normal";
765
  $d["voice_family"] = "";
766
  $d["volume"] = "medium";
767
  $d["white_space"] = "normal";
768
+ $d["widows"] = 2;
769
  $d["width"] = "auto";
770
+ $d["word_break"] = "normal";
771
  $d["word_spacing"] = "normal";
772
  $d["z_index"] = "auto";
773
 
774
  // CSS3
775
+ $d["opacity"] = 1.0;
776
+ $d["background_size"] = ["auto", "auto"];
777
  $d["transform"] = "none";
778
  $d["transform_origin"] = "50% 50%";
779
 
829
  "volume",
830
  "white_space",
831
  "widows",
832
+ "word_break",
833
  "word_spacing",
834
  ];
835
 
861
  }
862
 
863
  /**
864
+ * Clear all non-final used values.
865
+ *
866
+ * @return void
867
  */
868
+ public function reset(): void
869
  {
870
+ foreach (array_keys($this->non_final_used) as $prop) {
871
+ unset($this->_props_used[$prop]);
872
+ }
873
+
874
+ $this->non_final_used = [];
875
  }
876
 
877
  /**
878
+ * @param array $media_queries
879
  */
880
+ public function set_media_queries(array $media_queries): void
881
  {
882
  $this->_media_queries = $media_queries;
883
  }
884
 
885
  /**
886
+ * @return array
887
  */
888
+ public function get_media_queries(): array
889
  {
890
  return $this->_media_queries;
891
  }
893
  /**
894
  * @param Frame $frame
895
  */
896
+ public function set_frame(Frame $frame): void
897
  {
898
  $this->_frame = $frame;
899
  }
900
 
901
  /**
902
+ * @return Frame|null
903
  */
904
+ public function get_frame(): ?Frame
905
  {
906
  return $this->_frame;
907
  }
908
 
909
  /**
910
+ * @param int $origin
911
  */
912
+ public function set_origin(int $origin): void
913
  {
914
  $this->_origin = $origin;
915
  }
917
  /**
918
  * @return int
919
  */
920
+ public function get_origin(): int
921
  {
922
  return $this->_origin;
923
  }
924
 
925
  /**
926
+ * Returns the {@link Stylesheet} the style is associated with.
927
  *
928
  * @return Stylesheet
929
  */
930
+ public function get_stylesheet(): Stylesheet
931
  {
932
  return $this->_stylesheet;
933
  }
959
  *
960
  * @return float|string
961
  */
962
+ public function length_in_pt($length, ?float $ref_size = null)
963
  {
964
  $font_size = $this->__get("font_size");
965
  $ref_size = $ref_size ?? $font_size;
966
 
967
+ if (!\is_array($length)) {
968
  $length = [$length];
969
  }
970
 
982
  }
983
 
984
  $val = $this->single_length_in_pt((string) $l, $ref_size, $font_size);
985
+ $ret += $val ?? 0;
 
 
 
 
 
 
986
  }
987
 
988
  return $ret;
1005
 
1006
  $key = "$l/$ref_size/$font_size";
1007
 
1008
+ if (\array_key_exists($key, $cache)) {
1009
  return $cache[$key];
1010
  }
1011
 
1012
+ $number = self::CSS_NUMBER;
1013
+ $pattern = "/^($number)(.*)?$/";
1014
+
1015
+ if (!preg_match($pattern, $l, $matches)) {
1016
+ return null;
1017
+ }
1018
+
1019
+ $v = (float) $matches[1];
1020
+ $unit = mb_strtolower($matches[2]);
1021
+
1022
+ if ($unit === "") {
1023
  // Legacy support for unitless values, not covered by spec. Might
1024
  // want to restrict this to unitless `0` in the future
1025
+ $value = $v;
1026
  }
1027
 
1028
+ elseif ($unit === "%") {
1029
+ $value = $v / 100 * $ref_size;
1030
  }
1031
 
1032
+ elseif ($unit === "px") {
1033
  $dpi = $this->_stylesheet->get_dompdf()->getOptions()->getDpi();
1034
+ $value = ($v * 72) / $dpi;
1035
  }
1036
 
1037
+ elseif ($unit === "pt") {
1038
+ $value = $v;
1039
  }
1040
 
1041
+ elseif ($unit === "rem") {
1042
+ $tree = $this->_stylesheet->get_dompdf()->getTree();
1043
+ $root_style = $tree !== null ? $tree->get_root()->get_style() : null;
1044
  $root_font_size = $root_style === null || $root_style === $this
1045
  ? $font_size
1046
+ : $root_style->__get("font_size");
1047
+ $value = $v * $root_font_size;
1048
+
1049
+ // Skip caching if the root style is not available yet, as to avoid
1050
+ // incorrectly cached values if the root font size is different from
1051
+ // the default
1052
+ if ($root_style === null) {
1053
+ return $value;
1054
+ }
1055
  }
1056
 
1057
+ elseif ($unit === "em") {
1058
+ $value = $v * $font_size;
1059
  }
1060
 
1061
+ elseif ($unit === "cm") {
1062
+ $value = $v * 72 / 2.54;
1063
  }
1064
 
1065
+ elseif ($unit === "mm") {
1066
+ $value = $v * 72 / 25.4;
1067
  }
1068
 
1069
+ elseif ($unit === "ex") {
1070
  // FIXME: em:ex ratio?
1071
+ $value = $v * $font_size / 2;
1072
  }
1073
 
1074
+ elseif ($unit === "in") {
1075
+ $value = $v * 72;
1076
  }
1077
 
1078
+ elseif ($unit === "pc") {
1079
+ $value = $v * 12;
1080
  }
1081
 
1082
  else {
1087
  return $cache[$key] = $value;
1088
  }
1089
 
 
1090
  /**
1091
  * Resolve inherited property values using the provided parent style or the
1092
  * default values, in case no parent style exists.
1094
  * https://www.w3.org/TR/css-cascade-3/#inheriting
1095
  *
1096
  * @param Style|null $parent
 
 
1097
  */
1098
+ public function inherit(?Style $parent = null): void
1099
  {
1100
  $this->parent_style = $parent;
1101
 
1102
  // Clear the computed font size, as it might depend on the parent
1103
  // font size
1104
  unset($this->_props_computed["font_size"]);
1105
+ unset($this->_props_used["font_size"]);
1106
 
1107
  if ($parent) {
1108
  foreach (self::$_inherited as $prop) {
1112
  if (isset($this->_props[$prop]) || isset(self::$_props_shorthand[$prop])) {
1113
  continue;
1114
  }
1115
+
1116
  if (isset($parent->_props[$prop])) {
1117
+ $parent_val = $parent->computed($prop);
1118
+
 
 
1119
  $this->_props[$prop] = $parent_val;
1120
  $this->_props_computed[$prop] = $parent_val;
1121
+ $this->_props_used[$prop] = null;
1122
  }
1123
  }
1124
  }
1126
  foreach ($this->_props as $prop => $val) {
1127
  if ($val === "inherit") {
1128
  if ($parent && isset($parent->_props[$prop])) {
1129
+ $parent_val = $parent->computed($prop);
 
 
1130
 
1131
  $this->_props[$prop] = $parent_val;
1132
  $this->_props_computed[$prop] = $parent_val;
1133
+ $this->_props_used[$prop] = null;
1134
  } else {
1135
  // Parent prop not set, use default
1136
  $this->_props[$prop] = self::$_defaults[$prop];
1137
  unset($this->_props_computed[$prop]);
1138
+ unset($this->_props_used[$prop]);
1139
  }
1140
  }
1141
  }
 
 
1142
  }
1143
 
1144
  /**
1146
  *
1147
  * @param Style $style
1148
  */
1149
+ public function merge(Style $style): void
1150
  {
1151
  foreach ($style->_props as $prop => $val) {
1152
  $important = isset($style->_important_props[$prop]);
1156
  continue;
1157
  }
1158
 
 
 
 
 
 
 
 
 
 
 
 
1159
  if ($important) {
1160
  $this->_important_props[$prop] = true;
1161
  }
1162
 
1163
  $this->_props[$prop] = $val;
1164
 
1165
+ // Copy an existing computed value only for non-dependent
1166
+ // properties; otherwise it may be invalid for the current style
1167
+ if (!isset(self::$_dependent_props[$prop])
1168
+ && \array_key_exists($prop, $style->_props_computed)
1169
+ ) {
1170
+ $this->_props_computed[$prop] = $style->_props_computed[$prop];
1171
+ $this->_props_used[$prop] = null;
1172
  } else {
1173
+ unset($this->_props_computed[$prop]);
1174
+ unset($this->_props_used[$prop]);
1175
  }
1176
  }
1177
  }
1178
 
1179
  /**
1180
+ * Clear information about important declarations after the style has been
1181
+ * finalized during stylesheet loading.
 
 
 
 
 
 
 
 
 
 
 
 
1182
  */
1183
+ public function clear_important(): void
1184
  {
1185
+ $this->_important_props = [];
 
1186
  }
1187
 
1188
  /**
1189
+ * Clear border-radius and bottom-spacing cache as necessary when a given
1190
+ * property is set.
1191
+ *
1192
+ * @param string $prop The property that is set.
1193
  */
1194
+ protected function clear_cache(string $prop): void
1195
  {
1196
+ // Clear border-radius cache on setting any border-radius
1197
+ // property
1198
+ if ($prop === "border_top_left_radius"
1199
+ || $prop === "border_top_right_radius"
1200
+ || $prop === "border_bottom_left_radius"
1201
+ || $prop === "border_bottom_right_radius"
1202
+ ) {
1203
+ $this->has_border_radius_cache = null;
1204
+ $this->resolved_border_radius = null;
1205
+ }
1206
 
1207
+ // Clear bottom-spacing cache if necessary. Border style can
1208
+ // disable/enable border calculations
1209
+ if ($prop === "margin_bottom"
1210
+ || $prop === "padding_bottom"
1211
+ || $prop === "border_bottom_width"
1212
+ || $prop === "border_bottom_style"
1213
+ ) {
1214
+ $this->_computed_bottom_spacing = null;
1215
+ }
1216
  }
1217
 
1218
  /**
1219
+ * Set a style property from a value declaration.
1220
  *
1221
  * Setting `$clear_dependencies` to `false` is useful for saving a bit of
1222
  * unnecessary work while loading stylesheets.
1223
  *
1224
  * @param string $prop The property to set.
1225
+ * @param mixed $val The value declaration or computed value.
1226
  * @param bool $important Whether the declaration is important.
1227
  * @param bool $clear_dependencies Whether to clear computed values of dependent properties.
1228
  */
1229
+ public function set_prop(string $prop, $val, bool $important = false, bool $clear_dependencies = true): void
1230
  {
1231
  $prop = str_replace("-", "_", $prop);
1232
 
1241
  return;
1242
  }
1243
 
1244
+ if ($prop !== "content" && \is_string($val) && mb_strpos($val, "url") === false && mb_strlen($val) > 1) {
1245
  $val = mb_strtolower(trim(str_replace(["\n", "\t"], [" "], $val)));
 
1246
  }
1247
 
1248
  if (isset(self::$_props_shorthand[$prop])) {
1253
  $this->set_prop($sub_prop, $val, $important, $clear_dependencies);
1254
  }
1255
  } else {
1256
+ $method = "_set_$prop";
1257
+
1258
  if (!isset(self::$_methods_cache[$method])) {
1259
  self::$_methods_cache[$method] = method_exists($this, $method);
1260
  }
1261
+
1262
  if (self::$_methods_cache[$method]) {
1263
+ $values = $this->$method($val);
1264
+
1265
+ if ($values === []) {
1266
+ return;
1267
+ }
1268
+
1269
+ // Each missing sub-property is assigned its initial value
1270
+ // https://www.w3.org/TR/css-cascade-3/#shorthand
1271
+ foreach (self::$_props_shorthand[$prop] as $sub_prop) {
1272
+ $sub_val = $values[$sub_prop] ?? self::$_defaults[$sub_prop];
1273
+ $this->set_prop($sub_prop, $sub_val, $important, $clear_dependencies);
1274
+ }
1275
  }
1276
  }
1277
  } else {
1278
+ // Legacy support for `word-break: break-word`
1279
+ // https://www.w3.org/TR/css-text-3/#valdef-word-break-break-word
1280
+ if ($prop === "word_break" && $val === "break-word") {
1281
+ $val = "normal";
1282
+ $this->set_prop("overflow_wrap", "anywhere", $important, $clear_dependencies);
1283
+ }
1284
+
1285
  // `!important` declarations take precedence over normal ones
1286
  if (!$important && isset($this->_important_props[$prop])) {
1287
  return;
1293
 
1294
  // https://www.w3.org/TR/css-cascade-3/#inherit-initial
1295
  if ($val === "unset") {
1296
+ $val = \in_array($prop, self::$_inherited, true)
1297
  ? "inherit"
1298
  : "initial";
1299
  }
1303
  $val = self::$_defaults[$prop];
1304
  }
1305
 
1306
+ $computed = $this->compute_prop($prop, $val);
1307
+
1308
+ // Skip invalid declarations
1309
+ if ($computed === null) {
1310
+ return;
1311
+ }
1312
+
1313
  $this->_props[$prop] = $val;
1314
+ $this->_props_computed[$prop] = $computed;
1315
+ $this->_props_used[$prop] = null;
1316
 
1317
  if ($clear_dependencies) {
1318
  // Clear the computed values of any dependent properties, so
1320
  if (isset(self::$_dependency_map[$prop])) {
1321
  foreach (self::$_dependency_map[$prop] as $dependent) {
1322
  unset($this->_props_computed[$dependent]);
1323
+ unset($this->_props_used[$dependent]);
1324
  }
1325
  }
1326
 
1327
+ $this->clear_cache($prop);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1328
  }
1329
  }
1330
  }
1331
 
1332
  /**
1333
+ * Get the specified value of a style property.
 
1334
  *
1335
  * @param string $prop
1336
  *
1337
  * @return mixed
1338
  * @throws Exception
1339
  */
1340
+ public function get_specified(string $prop)
1341
  {
1342
  // Legacy property aliases
1343
  if (isset(self::$_props_alias[$prop])) {
1348
  throw new Exception("'$prop' is not a recognized CSS property.");
1349
  }
1350
 
1351
+ return $this->_props[$prop] ?? self::$_defaults[$prop];
1352
+ }
1353
 
1354
+ /**
1355
+ * Set a style property to its final value.
1356
+ *
1357
+ * This sets the specified and used value of the style property to the given
1358
+ * value, meaning the value is not parsed and thus should have a type
1359
+ * compatible with the property.
1360
+ *
1361
+ * If a shorthand property is specified, all of its sub-properties are set
1362
+ * to the given value.
1363
+ *
1364
+ * @param string $prop The property to set.
1365
+ * @param mixed $val The final value of the property.
1366
+ *
1367
+ * @throws Exception
1368
+ */
1369
+ public function __set(string $prop, $val)
1370
+ {
1371
+ // Legacy property aliases
1372
+ if (isset(self::$_props_alias[$prop])) {
1373
+ $prop = self::$_props_alias[$prop];
1374
+ }
1375
 
1376
+ if (!isset(self::$_defaults[$prop])) {
1377
+ throw new Exception("'$prop' is not a recognized CSS property.");
1378
  }
1379
 
1380
+ if (isset(self::$_props_shorthand[$prop])) {
1381
+ foreach (self::$_props_shorthand[$prop] as $sub_prop) {
1382
+ $this->__set($sub_prop, $val);
1383
+ }
1384
+ } else {
1385
+ $this->_props[$prop] = $val;
1386
+ $this->_props_computed[$prop] = $val;
1387
+ $this->_props_used[$prop] = $val;
1388
+
1389
+ $this->clear_cache($prop);
1390
+ }
1391
  }
1392
 
1393
  /**
1394
+ * Set the used value of a style property.
1395
  *
1396
+ * Used values are cleared on style reset.
 
 
 
 
 
1397
  *
1398
+ * If a shorthand property is specified, all of its sub-properties are set
1399
+ * to the given value.
 
 
 
 
 
 
 
 
 
 
1400
  *
1401
+ * @param string $prop The property to set.
1402
+ * @param mixed $val The used value of the property.
1403
  *
1404
+ * @throws Exception
1405
  */
1406
+ public function set_used(string $prop, $val): void
1407
  {
1408
+ // Legacy property aliases
1409
+ if (isset(self::$_props_alias[$prop])) {
1410
+ $prop = self::$_props_alias[$prop];
1411
+ }
1412
+
1413
+ if (!isset(self::$_defaults[$prop])) {
1414
+ throw new Exception("'$prop' is not a recognized CSS property.");
1415
+ }
1416
+
1417
+ if (isset(self::$_props_shorthand[$prop])) {
1418
+ foreach (self::$_props_shorthand[$prop] as $sub_prop) {
1419
+ $this->set_used($sub_prop, $val);
1420
+ }
1421
+ } else {
1422
+ $this->_props_used[$prop] = $val;
1423
+ $this->non_final_used[$prop] = true;
1424
+ }
1425
  }
1426
 
1427
  /**
1428
+ * Get the used or computed value of a style property, depending on whether
1429
+ * the used value has been determined yet.
 
 
 
 
1430
  *
1431
  * @param string $prop
1432
  *
1433
  * @return mixed
1434
  * @throws Exception
1435
  */
1436
+ public function __get(string $prop)
1437
  {
1438
  // Legacy property aliases
1439
  if (isset(self::$_props_alias[$prop])) {
1444
  throw new Exception("'$prop' is not a recognized CSS property.");
1445
  }
1446
 
1447
+ if (isset($this->_props_used[$prop])) {
1448
+ return $this->_props_used[$prop];
1449
  }
1450
 
1451
+ $method = "_get_$prop";
1452
+
1453
  if (!isset(self::$_methods_cache[$method])) {
1454
  self::$_methods_cache[$method] = method_exists($this, $method);
1455
  }
1463
  } else {
1464
  return implode(" ", array_map(function ($sub_prop) {
1465
  $val = $this->__get($sub_prop);
1466
+ return \is_array($val) ? implode(" ", $val) : $val;
1467
  }, self::$_props_shorthand[$prop]));
1468
  }
1469
  } else {
1470
+ $computed = $this->computed($prop);
 
 
 
 
 
 
 
 
 
 
1471
  $used = self::$_methods_cache[$method]
1472
+ ? $this->$method($computed)
1473
  : $computed;
1474
 
1475
+ $this->_props_used[$prop] = $used;
1476
  return $used;
1477
  }
1478
  }
1479
 
1480
  /**
1481
+ * @param string $prop The property to compute.
1482
+ * @param mixed $val The value to compute. Non-string values are treated as already computed.
 
 
1483
  *
1484
+ * @return mixed The computed value.
 
1485
  */
1486
+ protected function compute_prop(string $prop, $val)
1487
  {
1488
+ // During style merge, the parent style is not available yet, so
1489
+ // temporarily use the initial value for `inherit` properties. The
1490
+ // keyword is properly resolved during inheritance
1491
+ if ($val === "inherit") {
1492
+ $val = self::$_defaults[$prop];
1493
  }
1494
 
1495
+ // Check for values which are already computed
1496
+ if (!\is_string($val)) {
1497
+ return $val;
1498
  }
1499
 
1500
+ $method = "_compute_$prop";
1501
+
1502
+ if (!isset(self::$_methods_cache[$method])) {
1503
+ self::$_methods_cache[$method] = method_exists($this, $method);
1504
+ }
1505
+
1506
+ if (self::$_methods_cache[$method]) {
1507
+ return $this->$method($val);
1508
+ } elseif ($val !== "") {
1509
+ return $val;
1510
  } else {
1511
+ return null;
1512
  }
1513
  }
1514
 
1515
  /**
1516
+ * Get the computed value for the given property.
1517
+ *
1518
+ * @param string $prop The property to get the computed value of.
1519
  *
1520
  * @return mixed The computed value.
1521
  */
1522
+ protected function computed(string $prop)
1523
  {
1524
+ if (!\array_key_exists($prop, $this->_props_computed)) {
1525
+ $val = $this->_props[$prop] ?? self::$_defaults[$prop];
1526
+ $computed = $this->compute_prop($prop, $val);
 
 
 
 
 
 
 
 
 
 
 
 
1527
 
1528
+ $this->_props_computed[$prop] = $computed;
 
 
 
1529
  }
1530
 
1531
  return $this->_props_computed[$prop];
1533
 
1534
  /**
1535
  * @param float $cbw The width of the containing block.
1536
+ * @return float|string|null
1537
  */
1538
+ public function computed_bottom_spacing(float $cbw)
1539
  {
1540
  // Caching the bottom spacing independently of the given width is a bit
1541
  // iffy, but should be okay, as the containing block should only
1554
  );
1555
  }
1556
 
1557
+ /**
1558
+ * Returns an `array(r, g, b, "r" => r, "g" => g, "b" => b, "alpha" => alpha, "hex" => "#rrggbb")`
1559
+ * based on the provided CSS color value.
1560
+ *
1561
+ * @param string|null $color
1562
+ * @return array|string|null
1563
+ */
1564
+ public function munge_color($color)
1565
+ {
1566
+ return Color::parse($color);
1567
+ }
1568
+
1569
  /**
1570
  * @return string
1571
  */
1572
+ public function get_font_family_raw(): string
1573
  {
1574
  return trim($this->_props["font_family"], " \t\n\r\x0B\"'");
1575
  }
1576
 
1577
  /**
1578
+ * Getter for the `font-family` CSS property.
1579
+ *
1580
  * Uses the {@link FontMetrics} class to resolve the font family into an
1581
  * actual font file.
1582
  *
1583
+ * @param string $computed
1584
+ * @return string
1585
  * @throws Exception
1586
  *
1587
+ * @link https://www.w3.org/TR/CSS21/fonts.html#propdef-font-family
1588
  */
1589
+ protected function _get_font_family($computed): string
1590
  {
1591
  //TODO: we should be using the calculated prop rather than perform the entire family parsing operation again
1592
 
1593
+ $fontMetrics = $this->getFontMetrics();
1594
  $DEBUGCSS = $this->_stylesheet->get_dompdf()->getOptions()->getDebugCss();
1595
 
1596
  // Select the appropriate font. First determine the subtype, then check
1608
 
1609
  // Resolve font-style
1610
  $font_style = $this->__get("font_style");
1611
+ $subtype = $fontMetrics->getType($weight . ' ' . $font_style);
1612
 
1613
+ $families = preg_split("/\s*,\s*/", $computed);
1614
 
1615
  $font = null;
1616
  foreach ($families as $family) {
1620
  if ($DEBUGCSS) {
1621
  print '(' . $family . ')';
1622
  }
1623
+ $font = $fontMetrics->getFont($family, $subtype);
1624
 
1625
  if ($font) {
1626
  if ($DEBUGCSS) {
1627
  print "<pre>[get_font_family:";
1628
+ print '(' . $computed . '.' . $font_style . '.' . $weight . '.' . $subtype . ')';
1629
  print '(' . $font . ")get_font_family]\n</pre>";
1630
  }
1631
  return $font;
1636
  if ($DEBUGCSS) {
1637
  print '(default)';
1638
  }
1639
+ $font = $fontMetrics->getFont($family, $subtype);
1640
 
1641
  if ($font) {
1642
  if ($DEBUGCSS) {
1645
  return $font;
1646
  }
1647
 
1648
+ throw new Exception("Unable to find a suitable font replacement for: '" . $computed . "'");
1649
  }
1650
 
1651
  /**
1652
+ * @param float|string $computed
1653
  * @return float
1654
+ *
1655
+ * @link https://www.w3.org/TR/css-text-4/#word-spacing-property
1656
  */
1657
+ protected function _get_word_spacing($computed)
1658
  {
1659
+ if (\is_float($computed)) {
1660
+ return $computed;
 
 
 
 
 
 
1661
  }
1662
 
1663
+ // Resolve percentage values
1664
+ $font_size = $this->__get("font_size");
1665
+ return $this->single_length_in_pt($computed, $font_size);
1666
  }
1667
 
1668
  /**
1669
+ * @param float|string $computed
1670
  * @return float
1671
+ *
1672
+ * @link https://www.w3.org/TR/css-text-4/#letter-spacing-property
1673
  */
1674
+ protected function _get_letter_spacing($computed)
1675
  {
1676
+ if (\is_float($computed)) {
1677
+ return $computed;
 
 
1678
  }
1679
 
1680
+ // Resolve percentage values
1681
+ $font_size = $this->__get("font_size");
1682
+ return $this->single_length_in_pt($computed, $font_size);
1683
  }
1684
 
1685
  /**
1686
+ * @param float|string $computed
1687
  * @return float
1688
+ *
1689
+ * @link https://www.w3.org/TR/CSS21/visudet.html#propdef-line-height
1690
  */
1691
+ protected function _get_line_height($computed)
1692
  {
1693
+ // Lengths have been computed to float, number values to string
1694
+ if (\is_float($computed)) {
1695
+ return $computed;
 
1696
  }
1697
 
1698
+ $font_size = $this->__get("font_size");
1699
+ $factor = $computed === "normal"
1700
+ ? self::$default_line_height
1701
+ : (float) $computed;
1702
 
1703
+ return $factor * $font_size;
1704
  }
1705
 
1706
  /**
1707
+ * @param string $computed
1708
+ * @param bool $current_is_parent
1709
+ *
1710
  * @return array|string
1711
  */
1712
+ protected function get_color_value($computed, bool $current_is_parent = false)
1713
  {
1714
+ if ($computed === "currentcolor") {
 
 
1715
  // https://www.w3.org/TR/css-color-4/#resolving-other-colors
1716
  if ($current_is_parent) {
1717
  // Use the `color` value from the parent for the `color`
1718
  // property itself
1719
  return isset($this->parent_style)
1720
  ? $this->parent_style->__get("color")
1721
+ : $this->munge_color(self::$_defaults["color"]);
1722
  }
1723
 
1724
  return $this->__get("color");
1725
  }
1726
 
1727
+ return $this->munge_color($computed) ?? "transparent";
1728
  }
1729
 
1730
  /**
1731
  * Returns the color as an array
1732
  *
1733
  * The array has the following format:
1734
+ * `array(r, g, b, "r" => r, "g" => g, "b" => b, "alpha" => alpha, "hex" => "#rrggbb")`
1735
  *
1736
+ * @param string $computed
1737
+ * @return array|string
1738
+ *
1739
+ * @link https://www.w3.org/TR/CSS21/colors.html#propdef-color
1740
  */
1741
+ protected function _get_color($computed)
1742
  {
1743
+ return $this->get_color_value($computed, true);
1744
  }
1745
 
1746
  /**
1747
  * Returns the background color as an array
1748
  *
1749
+ * See {@link Style::_get_color()} for format of the color array.
1750
  *
1751
+ * @param string $computed
1752
+ * @return array|string
1753
+ *
1754
+ * @link https://www.w3.org/TR/CSS21/colors.html#propdef-background-color
1755
  */
1756
+ protected function _get_background_color($computed)
1757
  {
1758
+ return $this->get_color_value($computed);
1759
  }
1760
 
1761
  /**
1762
  * Returns the background image URI, or "none"
1763
  *
1764
+ * @param string $computed
1765
  * @return string
 
 
 
 
 
 
 
 
1766
  *
1767
+ * @link https://www.w3.org/TR/CSS21/colors.html#propdef-background-image
 
 
 
 
1768
  */
1769
+ protected function _get_background_image($computed): string
1770
  {
1771
+ return $this->_stylesheet->resolve_url($computed);
 
 
 
 
 
1772
  }
1773
 
 
1774
  /**
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1775
  * Returns the border color as an array
1776
  *
1777
+ * See {@link Style::_get_color()} for format of the color array.
1778
  *
1779
+ * @param string $computed
1780
+ * @return array|string
1781
+ *
1782
+ * @link https://www.w3.org/TR/CSS21/box.html#border-color-properties
1783
  */
1784
+ protected function _get_border_top_color($computed)
1785
  {
1786
+ return $this->get_color_value($computed);
1787
  }
1788
 
1789
  /**
1790
+ * @param string $computed
1791
+ * @return array|string
1792
  */
1793
+ protected function _get_border_right_color($computed)
1794
  {
1795
+ return $this->get_color_value($computed);
1796
  }
1797
 
1798
  /**
1799
+ * @param string $computed
1800
+ * @return array|string
1801
  */
1802
+ protected function _get_border_bottom_color($computed)
1803
  {
1804
+ return $this->get_color_value($computed);
1805
  }
1806
 
1807
  /**
1808
+ * @param string $computed
1809
+ * @return array|string
1810
  */
1811
+ protected function _get_border_left_color($computed)
1812
  {
1813
+ return $this->get_color_value($computed);
1814
  }
1815
 
 
 
1816
  /**
1817
  * Return an array of all border properties.
1818
  *
1819
  * The returned array has the following structure:
1820
+ *
1821
+ * ```
1822
  * array("top" => array("width" => [border-width],
1823
  * "style" => [border-style],
1824
  * "color" => [border-color (array)]),
1825
  * "bottom" ... )
1826
+ * ```
1827
  *
1828
  * @return array
1829
  */
1830
+ public function get_border_properties(): array
1831
  {
1832
  return [
1833
  "top" => [
1854
  }
1855
 
1856
  /**
1857
+ * Return a single border-side property
1858
  *
1859
  * @param string $side
1860
+ * @return string
 
1861
  */
1862
+ protected function get_border_side(string $side): string
1863
  {
1864
+ $color = $this->__get("border_{$side}_color");
1865
 
1866
+ return $this->__get("border_{$side}_width") . " " .
1867
+ $this->__get("border_{$side}_style") . " " .
1868
+ (\is_array($color) ? $color["hex"] : $color);
1869
  }
1870
 
1871
+ /**
1872
  * Return full border properties as a string
1873
  *
1874
  * Border properties are returned just as specified in CSS:
1875
+ * `[width] [style] [color]`
1876
  * e.g. "1px solid blue"
1877
  *
 
1878
  * @return string
1879
+ *
1880
+ * @link https://www.w3.org/TR/CSS21/box.html#border-shorthand-properties
1881
  */
1882
+ protected function _get_border_top(): string
 
 
 
 
 
 
 
 
1883
  {
1884
+ return $this->get_border_side("top");
1885
  }
1886
 
1887
  /**
1888
+ * @return string
1889
  */
1890
+ protected function _get_border_right(): string
1891
  {
1892
+ return $this->get_border_side("right");
1893
  }
1894
 
1895
  /**
1896
+ * @return string
1897
  */
1898
+ protected function _get_border_bottom(): string
1899
  {
1900
+ return $this->get_border_side("bottom");
1901
  }
1902
 
1903
  /**
1904
+ * @return string
 
 
 
1905
  */
1906
+ protected function _get_border_left(): string
1907
  {
1908
+ return $this->get_border_side("left");
1909
  }
1910
 
1911
  public function has_border_radius(): bool
1917
  // Use a fixed ref size here. We don't know the border-box width here
1918
  // and font size might be 0. Since we are only interested in whether
1919
  // there is any border radius at all, this should do
1920
+ $tl = (float) $this->length_in_pt($this->border_top_left_radius, 12);
1921
+ $tr = (float) $this->length_in_pt($this->border_top_right_radius, 12);
1922
+ $br = (float) $this->length_in_pt($this->border_bottom_right_radius, 12);
1923
+ $bl = (float) $this->length_in_pt($this->border_bottom_left_radius, 12);
1924
 
1925
  $this->has_border_radius_cache = $tl + $tr + $br + $bl > 0;
1926
  return $this->has_border_radius_cache;
2013
  /**
2014
  * Returns the outline color as an array
2015
  *
2016
+ * See {@link Style::_get_color()} for format of the color array.
2017
  *
2018
+ * @param string $computed
2019
+ * @return array|string
2020
+ *
2021
+ * @link https://www.w3.org/TR/css-ui-4/#propdef-outline-color
2022
+ */
2023
+ protected function _get_outline_color($computed)
2024
+ {
2025
+ return $this->get_color_value($computed);
2026
+ }
2027
+
2028
+ /**
2029
+ * @param string $computed
2030
+ * @return string
2031
+ *
2032
+ * @link https://www.w3.org/TR/css-ui-4/#propdef-outline-style
2033
  */
2034
+ protected function _get_outline_style($computed): string
2035
  {
2036
+ return $computed === "auto" ? "solid" : $computed;
2037
  }
2038
 
2039
  /**
2040
  * Return full outline properties as a string
2041
  *
2042
  * Outline properties are returned just as specified in CSS:
2043
+ * `[width] [style] [color]`
2044
  * e.g. "1px solid blue"
2045
  *
 
2046
  * @return string
2047
+ *
2048
+ * @link https://www.w3.org/TR/CSS21/box.html#border-shorthand-properties
2049
  */
2050
+ protected function _get_outline(): string
2051
  {
2052
  $color = $this->__get("outline_color");
2053
 
2054
  return $this->__get("outline_width") . " " .
2055
  $this->__get("outline_style") . " " .
2056
+ (\is_array($color) ? $color["hex"] : $color);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2057
  }
2058
 
2059
  /**
2060
  * Returns the list style image URI, or "none"
2061
  *
2062
+ * @param string $computed
2063
  * @return string
2064
+ *
2065
+ * @link https://www.w3.org/TR/CSS21/generate.html#propdef-list-style-image
2066
  */
2067
+ protected function _get_list_style_image($computed): string
2068
  {
2069
+ return $this->_stylesheet->resolve_url($computed);
2070
  }
2071
 
2072
  /**
2097
  }
2098
 
2099
  /**
2100
+ * @param string $computed
2101
  * @return array|string
2102
+ *
2103
+ * @link https://www.w3.org/TR/CSS21/generate.html#propdef-counter-increment
2104
  */
2105
+ protected function _get_counter_increment($computed)
2106
  {
2107
+ if ($computed === "none") {
2108
+ return $computed;
 
 
2109
  }
2110
 
2111
+ return $this->parse_counter_prop($computed, 1);
2112
  }
2113
 
2114
  /**
2115
+ * @param string $computed
2116
  * @return array|string
2117
+ *
2118
+ * @link https://www.w3.org/TR/CSS21/generate.html#propdef-counter-reset
2119
  */
2120
+ protected function _get_counter_reset($computed)
2121
  {
2122
+ if ($computed === "none") {
2123
+ return $computed;
 
 
2124
  }
2125
 
2126
+ return $this->parse_counter_prop($computed, 0);
2127
  }
2128
 
2129
  /**
2130
+ * @param string $computed
2131
  * @return string[]|string
2132
+ *
2133
+ * @link https://www.w3.org/TR/CSS21/generate.html#propdef-content
2134
  */
2135
+ protected function _get_content($computed)
2136
  {
2137
+ if ($computed === "normal" || $computed === "none") {
2138
+ return $computed;
 
 
2139
  }
2140
 
2141
+ return $this->parse_property_value($computed);
2142
  }
2143
 
2144
  /*==============================*/
2180
  || preg_match("/^#|rgb\(|rgba\(|cmyk\(/", $val);
2181
  }
2182
 
2183
+ /**
2184
+ * @param string $val
2185
+ * @return string|null
2186
+ */
2187
+ protected function compute_color_value(string $val): ?string
2188
+ {
2189
+ // https://www.w3.org/TR/css-color-4/#resolving-other-colors
2190
+ $munged_color = $val !== "currentcolor"
2191
+ ? $this->munge_color($val)
2192
+ : $val;
2193
+
2194
+ if ($munged_color === null) {
2195
+ return null;
2196
+ }
2197
+
2198
+ return \is_array($munged_color) ? $munged_color["hex"] : $munged_color;
2199
+ }
2200
+
2201
+ /**
2202
+ * @param string $val
2203
+ * @return int|null
2204
+ */
2205
+ protected function compute_integer(string $val): ?int
2206
  {
2207
+ $integer = self::CSS_INTEGER;
2208
+ return preg_match("/^$integer$/", $val)
2209
+ ? (int) $val
2210
+ : null;
 
 
 
 
2211
  }
2212
 
2213
  /**
2214
+ * @param string $val
2215
+ * @return float|null
 
 
 
 
 
 
2216
  */
2217
+ protected function compute_length(string $val): ?float
2218
  {
2219
+ return mb_strpos($val, "%") === false
2220
+ ? $this->single_length_in_pt($val)
2221
+ : null;
2222
+ }
2223
 
2224
+ /**
2225
+ * @param string $val
2226
+ * @return float|null
2227
+ */
2228
+ protected function compute_length_positive(string $val): ?float
2229
+ {
2230
+ $computed = $this->compute_length($val);
2231
+ return $computed !== null && $computed >= 0 ? $computed : null;
2232
+ }
2233
 
2234
+ /**
2235
+ * @param string $val
2236
+ * @return float|string|null
2237
+ */
2238
+ protected function compute_length_percentage(string $val)
2239
+ {
2240
+ // Compute with a fixed ref size to decide whether percentage values
2241
+ // are valid
2242
+ $computed = $this->single_length_in_pt($val, 12);
2243
 
2244
+ if ($computed === null) {
2245
+ return null;
2246
  }
2247
 
2248
+ // Retain valid percentage declarations
2249
+ return mb_strpos($val, "%") === false ? $computed : $val;
2250
+ }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2251
 
2252
+ /**
2253
+ * @param string $val
2254
+ * @return float|string|null
2255
+ */
2256
+ protected function compute_length_percentage_positive(string $val)
2257
+ {
2258
+ // Compute with a fixed ref size to decide whether percentage values
2259
+ // are valid
2260
+ $computed = $this->single_length_in_pt($val, 12);
2261
 
2262
+ if ($computed === null || $computed < 0) {
2263
+ return null;
2264
+ }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2265
 
2266
+ // Retain valid percentage declarations
2267
+ return mb_strpos($val, "%") === false ? $computed : $val;
2268
+ }
 
2269
 
2270
+ /**
2271
+ * @param string $val
2272
+ * @param string $style_prop The corresponding border-/outline-style property.
2273
+ *
2274
+ * @return float|null
2275
+ *
2276
+ * @link https://www.w3.org/TR/css-backgrounds-3/#typedef-line-width
2277
+ */
2278
+ protected function compute_line_width(string $val, string $style_prop): ?float
2279
+ {
2280
+ // Border-width keywords
2281
+ if ($val === "thin") {
2282
+ $computed = 0.5;
2283
+ } elseif ($val === "medium") {
2284
+ $computed = 1.5;
2285
+ } elseif ($val === "thick") {
2286
+ $computed = 2.5;
2287
  } else {
2288
+ $computed = $this->compute_length_positive($val);
2289
+ }
2290
+
2291
+ if ($computed === null) {
2292
+ return null;
2293
  }
2294
+
2295
+ // Computed width is 0 if the line style is `none` or `hidden`
2296
+ // https://www.w3.org/TR/css-backgrounds-3/#border-width
2297
+ // https://www.w3.org/TR/css-ui-4/#outline-width
2298
+ $lineStyle = $this->__get($style_prop);
2299
+ $hasLineStyle = $lineStyle !== "none" && $lineStyle !== "hidden";
2300
+
2301
+ return $hasLineStyle ? $computed : 0.0;
2302
+ }
2303
+
2304
+ /**
2305
+ * @param string $val
2306
+ * @return string|null
2307
+ */
2308
+ protected function compute_border_style(string $val): ?string
2309
+ {
2310
+ return \in_array($val, self::BORDER_STYLES, true) ? $val : null;
2311
  }
2312
 
2313
  /**
2314
+ * Parse a property value with 1 to 4 components into 4 values, as required
2315
+ * by shorthand properties such as `margin`, `padding`, and `border-radius`.
2316
+ *
2317
+ * @param string $prop The shorthand property with exactly 4 sub-properties to handle.
2318
+ * @param string $value The property value to parse.
2319
+ *
2320
+ * @return string[]
2321
  */
2322
+ protected function set_quad_shorthand(string $prop, string $value): array
2323
  {
2324
+ $v = $this->parse_property_value($value);
2325
 
2326
+ switch (\count($v)) {
2327
  case 1:
2328
+ $values = [$v[0], $v[0], $v[0], $v[0]];
2329
  break;
2330
  case 2:
2331
+ $values = [$v[0], $v[1], $v[0], $v[1]];
2332
  break;
2333
  case 3:
2334
+ $values = [$v[0], $v[1], $v[2], $v[1]];
2335
  break;
2336
  case 4:
2337
+ $values = [$v[0], $v[1], $v[2], $v[3]];
2338
  break;
2339
  default:
2340
+ return [];
2341
  }
2342
 
2343
+ return array_combine(self::$_props_shorthand[$prop], $values);
 
 
 
2344
  }
2345
 
2346
  /*======================*/
2347
 
2348
  /**
2349
+ * @link https://www.w3.org/TR/CSS21/visuren.html#display-prop
 
 
2350
  */
2351
+ protected function _compute_display(string $val)
2352
  {
2353
  // Make sure that common valid, but unsupported display types have an
2354
  // appropriate fallback display type
2366
  }
2367
 
2368
  if (!isset(self::$valid_display_types[$val])) {
2369
+ return null;
2370
  }
2371
 
2372
  // https://www.w3.org/TR/CSS21/visuren.html#dis-pos-flo
2373
  if ($this->is_in_flow()) {
2374
+ return $val;
2375
  } else {
2376
  switch ($val) {
2377
  case "inline":
2384
  // case "table-column-group":
2385
  // case "table-column":
2386
  // case "table-caption":
2387
+ return "block";
 
2388
  case "inline-table":
2389
+ return "table";
 
2390
  default:
2391
+ return $val;
 
2392
  }
2393
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2394
  }
2395
 
2396
  /**
2397
+ * @link https://www.w3.org/TR/CSS21/colors.html#propdef-color
 
 
 
 
 
2398
  */
2399
+ protected function _compute_color(string $color)
2400
  {
2401
+ return $this->compute_color_value($color);
2402
  }
2403
 
2404
  /**
2405
+ * @link https://www.w3.org/TR/CSS21/colors.html#propdef-background-color
 
 
 
2406
  */
2407
+ protected function _compute_background_color(string $color)
2408
  {
2409
+ return $this->compute_color_value($color);
2410
  }
2411
 
2412
  /**
 
2413
  * @link https://www.w3.org/TR/CSS21/colors.html#propdef-background-image
 
 
2414
  */
2415
+ protected function _compute_background_image(string $val)
2416
  {
 
 
2417
  $parsed_val = $this->_stylesheet->resolve_url($val);
2418
 
2419
  if ($parsed_val === "none") {
2420
+ return "none";
2421
  } else {
2422
+ return "url($parsed_val)";
2423
  }
2424
  }
2425
 
2426
  /**
2427
+ * @link https://www.w3.org/TR/CSS21/colors.html#propdef-background-repeat
 
 
 
2428
  */
2429
+ protected function _compute_background_repeat(string $val)
2430
  {
2431
+ $keywords = ["repeat", "repeat-x", "repeat-y", "no-repeat"];
2432
+ return \in_array($val, $keywords, true) ? $val : null;
 
 
 
 
 
 
2433
  }
2434
 
2435
  /**
2436
+ * @link https://www.w3.org/TR/CSS21/colors.html#propdef-background-attachment
 
 
 
2437
  */
2438
+ protected function _compute_background_attachment(string $val)
2439
  {
2440
+ $keywords = ["scroll", "fixed"];
2441
+ return \in_array($val, $keywords, true) ? $val : null;
 
 
 
 
 
 
2442
  }
2443
 
2444
  /**
2445
+ * @link https://www.w3.org/TR/CSS21/colors.html#propdef-background-position
 
 
 
2446
  */
2447
+ protected function _compute_background_position(string $val)
2448
  {
2449
+ $parts = preg_split("/\s+/", $val);
2450
 
2451
+ if (\count($parts) > 2) {
2452
+ return null;
2453
+ }
2454
 
2455
+ switch ($parts[0]) {
2456
  case "left":
2457
  $x = "0%";
2458
  break;
2475
  break;
2476
 
2477
  default:
2478
+ $x = $parts[0];
2479
  break;
2480
  }
2481
 
2482
+ if (isset($parts[1])) {
2483
+ switch ($parts[1]) {
2484
  case "left":
2485
  $x = "0%";
2486
  break;
2498
  break;
2499
 
2500
  case "center":
2501
+ if ($parts[0] === "left" || $parts[0] === "right" || $parts[0] === "center") {
2502
  $y = "50%";
2503
  } else {
2504
  $x = "50%";
2506
  break;
2507
 
2508
  default:
2509
+ $y = $parts[1];
2510
  break;
2511
  }
2512
  } else {
2520
  if (!isset($y)) {
2521
  $y = "0%";
2522
  }
2523
+
2524
+ return [$x, $y];
2525
  }
2526
 
2527
  /**
2528
+ * Compute `background-size`.
2529
  *
2530
+ * Computes to one of the following values:
2531
+ * * `cover`
2532
+ * * `contain`
2533
+ * * `[width, height]`, each being a length, percentage, or `auto`
2534
+ *
2535
+ * @link https://www.w3.org/TR/css-backgrounds-3/#background-size
2536
  */
2537
+ protected function _compute_background_size(string $val)
2538
  {
2539
+ if ($val === "cover" || $val === "contain") {
2540
+ return $val;
2541
+ }
2542
 
2543
+ $parts = preg_split("/\s+/", $val);
 
2544
 
2545
+ if (\count($parts) > 2) {
2546
+ return null;
2547
+ }
2548
+
2549
+ $width = $parts[0];
2550
+ if ($width !== "auto") {
2551
+ $width = $this->compute_length_percentage_positive($width);
 
2552
  }
2553
 
2554
+ $height = $parts[1] ?? "auto";
2555
+ if ($height !== "auto") {
2556
+ $height = $this->compute_length_percentage_positive($height);
2557
  }
2558
 
2559
+ if ($width === null || $height === null) {
2560
+ return null;
 
2561
  }
2562
 
2563
+ return [$width, $height];
2564
  }
2565
 
2566
  /**
2567
+ * @link https://www.w3.org/TR/css-backgrounds-3/#propdef-background
 
 
 
 
2568
  */
2569
+ protected function _set_background(string $value): array
2570
  {
2571
+ $components = $this->parse_property_value($value);
2572
+ $props = [];
2573
+ $pos_size = [];
2574
+
2575
+ foreach ($components as $val) {
2576
+ if ($val === "none" || mb_substr($val, 0, 4) === "url(") {
2577
+ $props["background_image"] = $val;
2578
+ } elseif ($val === "scroll" || $val === "fixed") {
2579
+ $props["background_attachment"] = $val;
2580
+ } elseif ($val === "repeat" || $val === "repeat-x" || $val === "repeat-y" || $val === "no-repeat") {
2581
+ $props["background_repeat"] = $val;
2582
+ } elseif ($this->is_color_value($val)) {
2583
+ $props["background_color"] = $val;
2584
+ } else {
2585
+ $pos_size[] = $val;
 
 
 
 
2586
  }
2587
+ }
2588
 
2589
+ if (\count($pos_size)) {
2590
+ // Split value list at "/"
2591
+ $index = array_search("/", $pos_size, true);
2592
 
2593
+ if ($index !== false) {
2594
+ $pos = \array_slice($pos_size, 0, $index);
2595
+ $size = \array_slice($pos_size, $index + 1);
2596
+ } else {
2597
+ $pos = $pos_size;
2598
+ $size = [];
2599
+ }
2600
 
2601
+ $props["background_position"] = implode(" ", $pos);
2602
 
2603
+ if (\count($size)) {
2604
+ $props["background_size"] = implode(" ", $size);
 
2605
  }
2606
  }
2607
+
2608
+ return $props;
2609
  }
2610
 
2611
  /**
2612
+ * @link https://www.w3.org/TR/CSS21/fonts.html#propdef-font-size
 
 
 
 
 
2613
  */
2614
+ protected function _compute_font_size(string $size)
2615
  {
 
 
 
 
 
 
 
2616
  $parent_font_size = isset($this->parent_style)
2617
  ? $this->parent_style->__get("font_size")
2618
  : self::$default_font_size;
2619
 
2620
+ switch ($size) {
2621
  case "xx-small":
2622
  case "x-small":
2623
  case "small":
2641
  break;
2642
  }
2643
 
2644
+ return $fs;
2645
  }
2646
 
2647
  /**
2648
+ * @link https://www.w3.org/TR/CSS21/fonts.html#font-boldness
 
 
2649
  */
2650
+ protected function _compute_font_weight(string $weight)
2651
  {
 
 
 
 
 
 
 
2652
  $computed_weight = $weight;
2653
 
2654
  if ($weight === "bolder") {
2659
  $computed_weight = "normal";
2660
  }
2661
 
2662
+ return $computed_weight;
2663
  }
2664
 
2665
  /**
2666
+ * Handle the `font` shorthand property.
2667
  *
2668
+ * `[ font-style || font-variant || font-weight ] font-size [ / line-height ] font-family`
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2669
  *
2670
  * @link https://www.w3.org/TR/CSS21/fonts.html#font-shorthand
 
 
2671
  */
2672
+ protected function _set_font(string $value): array
2673
  {
2674
+ $components = $this->parse_property_value($value);
2675
+ $props = [];
2676
+
2677
+ $number = self::CSS_NUMBER;
2678
+ $unit = "pt|px|pc|rem|em|ex|in|cm|mm|%";
2679
+ $sizePattern = "/^(xx-small|x-small|small|medium|large|x-large|xx-large|smaller|larger|$number(?:$unit))$/";
2680
+ $sizeIndex = null;
2681
+
2682
+ // Find index of font-size to split the component list
2683
+ foreach ($components as $i => $val) {
2684
+ if (preg_match($sizePattern, $val)) {
2685
+ $sizeIndex = $i;
2686
+ $props["font_size"] = $val;
2687
+ break;
2688
+ }
2689
  }
2690
 
2691
+ // `font-size` is mandatory
2692
+ if ($sizeIndex === null) {
2693
+ return [];
2694
  }
2695
 
2696
+ // `font-style`, `font-variant`, `font-weight` in any order
2697
+ $styleVariantWeight = \array_slice($components, 0, $sizeIndex);
2698
+ $stylePattern = "/^(italic|oblique)$/";
2699
+ $variantPattern = "/^(small-caps)$/";
2700
+ $weightPattern = "/^(bold|bolder|lighter|100|200|300|400|500|600|700|800|900)$/";
2701
+
2702
+ if (\count($styleVariantWeight) > 3) {
2703
+ return [];
2704
  }
2705
 
2706
+ foreach ($styleVariantWeight as $val) {
2707
+ if ($val === "normal") {
2708
+ // Ignore any `normal` value, as it is valid and the initial
2709
+ // value for all three properties
2710
+ } elseif (!isset($props["font_style"]) && preg_match($stylePattern, $val)) {
2711
+ $props["font_style"] = $val;
2712
+ } elseif (!isset($props["font_variant"]) && preg_match($variantPattern, $val)) {
2713
+ $props["font_variant"] = $val;
2714
+ } elseif (!isset($props["font_weight"]) && preg_match($weightPattern, $val)) {
2715
+ $props["font_weight"] = $val;
2716
+ } else {
2717
+ // Duplicates and other values disallowed here
2718
+ return [];
2719
  }
2720
  }
2721
 
2722
+ // Optional slash + `line-height` followed by mandatory `font-family`
2723
+ $lineFamily = \array_slice($components, $sizeIndex + 1);
2724
+ $hasLineHeight = $lineFamily !== [] && $lineFamily[0] === "/";
2725
+ $lineHeight = $hasLineHeight ? \array_slice($lineFamily, 1, 1) : [];
2726
+ $fontFamily = $hasLineHeight ? \array_slice($lineFamily, 2) : $lineFamily;
2727
+ $lineHeightPattern = "/^(normal|$number(?:$unit)?)$/";
2728
+
2729
+ // Missing `font-family` or `line-height` after slash
2730
+ if ($fontFamily === []
2731
+ || ($hasLineHeight && !preg_match($lineHeightPattern, $lineHeight[0]))
2732
+ ) {
2733
+ return [];
2734
+ }
2735
+
2736
+ if ($hasLineHeight) {
2737
+ $props["line_height"] = $lineHeight[0];
2738
  }
2739
+
2740
+ $props["font_family"] = implode("", $fontFamily);
2741
+
2742
+ return $props;
2743
  }
2744
 
2745
  /**
2746
+ * Compute `text-align`.
2747
  *
2748
  * If no alignment is set on the element and the direction is rtl then
2749
  * the property is set to "right", otherwise it is set to "left".
2750
  *
2751
  * @link https://www.w3.org/TR/CSS21/text.html#propdef-text-align
2752
  */
2753
+ protected function _compute_text_align(string $val)
2754
  {
 
 
2755
  $alignment = $val;
2756
  if ($alignment === "") {
2757
  $alignment = "left";
2760
  }
2761
  }
2762
 
2763
+ if (!\in_array($alignment, self::TEXT_ALIGN_KEYWORDS, true)) {
2764
+ return null;
 
2765
  }
2766
 
2767
+ return $alignment;
2768
  }
2769
 
2770
  /**
2771
+ * @link https://www.w3.org/TR/css-text-4/#word-spacing-property
 
 
 
2772
  */
2773
+ protected function _compute_word_spacing(string $val)
2774
  {
2775
+ if ($val === "normal") {
2776
+ return 0.0;
 
 
 
2777
  }
2778
 
2779
+ return $this->compute_length_percentage($val);
 
 
 
 
2780
  }
2781
 
2782
  /**
2783
+ * @link https://www.w3.org/TR/css-text-4/#letter-spacing-property
 
 
 
2784
  */
2785
+ protected function _compute_letter_spacing(string $val)
2786
  {
 
 
 
 
 
 
 
2787
  if ($val === "normal") {
2788
+ return 0.0;
 
 
2789
  }
2790
+
2791
+ return $this->compute_length_percentage($val);
2792
  }
2793
 
2794
  /**
2795
+ * @link https://www.w3.org/TR/CSS21/visudet.html#propdef-line-height
 
 
 
2796
  */
2797
+ protected function _compute_line_height(string $val)
2798
  {
2799
+ if ($val === "normal") {
2800
+ return $val;
 
 
 
2801
  }
2802
 
2803
+ // Compute number values to string and lengths to float (in pt)
2804
+ if (is_numeric($val)) {
2805
+ return (string) $val;
 
2806
  }
2807
+
2808
+ $font_size = $this->__get("font_size");
2809
+ $computed = $this->single_length_in_pt($val, $font_size);
2810
+ return $computed !== null && $computed >= 0 ? $computed : null;
2811
  }
2812
 
2813
  /**
2814
+ * @link https://www.w3.org/TR/css-text-3/#text-indent-property
 
 
 
2815
  */
2816
+ protected function _compute_text_indent(string $val)
2817
  {
2818
+ return $this->compute_length_percentage($val);
2819
+ }
 
 
 
 
2820
 
2821
+ /**
2822
+ * @link https://www.w3.org/TR/CSS21/page.html#propdef-page-break-before
2823
+ */
2824
+ protected function _compute_page_break_before(string $break)
2825
+ {
2826
  if ($break === "left" || $break === "right") {
2827
  $break = "always";
2828
  }
2829
 
2830
+ return $break;
2831
  }
2832
 
2833
  /**
2834
+ * @link https://www.w3.org/TR/CSS21/page.html#propdef-page-break-after
2835
  */
2836
+ protected function _compute_page_break_after(string $break)
2837
  {
 
 
 
 
 
 
 
2838
  if ($break === "left" || $break === "right") {
2839
  $break = "always";
2840
  }
2841
 
2842
+ return $break;
2843
  }
2844
 
2845
  /**
2846
+ * @link https://www.w3.org/TR/CSS21/visudet.html#propdef-width
 
 
 
2847
  */
2848
+ protected function _compute_width(string $val)
2849
  {
2850
+ if ($val === "auto") {
2851
+ return $val;
2852
+ }
2853
+
2854
+ return $this->compute_length_percentage_positive($val);
2855
  }
2856
 
2857
  /**
2858
+ * @link https://www.w3.org/TR/CSS21/visudet.html#propdef-height
2859
  */
2860
+ protected function _compute_height(string $val)
2861
  {
2862
+ if ($val === "auto") {
2863
+ return $val;
2864
+ }
2865
+
2866
+ return $this->compute_length_percentage_positive($val);
2867
  }
2868
 
2869
  /**
2870
+ * @link https://www.w3.org/TR/CSS21/visudet.html#propdef-min-width
2871
  */
2872
+ protected function _compute_min_width(string $val)
2873
  {
2874
+ // Legacy support for `none`, not covered by spec
2875
+ if ($val === "auto" || $val === "none") {
2876
+ return "auto";
2877
+ }
2878
+
2879
+ return $this->compute_length_percentage_positive($val);
2880
  }
2881
 
2882
  /**
2883
+ * @link https://www.w3.org/TR/CSS21/visudet.html#propdef-min-height
2884
  */
2885
+ protected function _compute_min_height(string $val)
2886
  {
2887
+ // Legacy support for `none`, not covered by spec
2888
+ if ($val === "auto" || $val === "none") {
2889
+ return "auto";
2890
+ }
2891
+
2892
+ return $this->compute_length_percentage_positive($val);
2893
  }
2894
 
2895
  /**
2896
+ * @link https://www.w3.org/TR/CSS21/visudet.html#propdef-max-width
 
2897
  */
2898
+ protected function _compute_max_width(string $val)
2899
  {
2900
+ // Legacy support for `auto`, not covered by spec
2901
+ if ($val === "none" || $val === "auto") {
2902
+ return "none";
2903
+ }
2904
+
2905
+ return $this->compute_length_percentage_positive($val);
2906
  }
2907
 
2908
  /**
2909
+ * @link https://www.w3.org/TR/CSS21/visudet.html#propdef-max-height
 
 
 
2910
  */
2911
+ protected function _compute_max_height(string $val)
2912
  {
2913
+ // Legacy support for `auto`, not covered by spec
2914
+ if ($val === "none" || $val === "auto") {
2915
+ return "none";
2916
+ }
2917
+
2918
+ return $this->compute_length_percentage_positive($val);
2919
  }
2920
 
2921
  /**
2922
+ * @link https://www.w3.org/TR/css-position-3/#inset-properties
2923
+ * @link https://www.w3.org/TR/css-position-3/#propdef-inset
2924
  */
2925
+ protected function _set_inset(string $val): array
2926
  {
2927
+ return $this->set_quad_shorthand("inset", $val);
2928
  }
2929
 
2930
  /**
2931
+ * @param string $val
2932
+ * @return float|string|null
2933
  */
2934
+ protected function compute_box_inset(string $val)
2935
  {
2936
+ if ($val === "auto") {
2937
+ return $val;
2938
+ }
2939
+
2940
+ return $this->compute_length_percentage($val);
2941
  }
2942
 
2943
+ protected function _compute_top(string $val)
 
 
 
2944
  {
2945
+ return $this->compute_box_inset($val);
2946
+ }
2947
+
2948
+ protected function _compute_right(string $val)
2949
+ {
2950
+ return $this->compute_box_inset($val);
2951
+ }
2952
+
2953
+ protected function _compute_bottom(string $val)
2954
+ {
2955
+ return $this->compute_box_inset($val);
2956
+ }
2957
+
2958
+ protected function _compute_left(string $val)
2959
+ {
2960
+ return $this->compute_box_inset($val);
2961
  }
2962
 
2963
  /**
2964
+ * @link https://www.w3.org/TR/CSS21/box.html#margin-properties
2965
+ * @link https://www.w3.org/TR/CSS21/box.html#propdef-margin
2966
  */
2967
+ protected function _set_margin(string $val): array
2968
  {
2969
+ return $this->set_quad_shorthand("margin", $val);
2970
  }
2971
 
2972
  /**
2973
+ * @param string $val
2974
+ * @return float|string|null
 
 
 
2975
  */
2976
+ protected function compute_margin(string $val)
2977
  {
2978
+ // Legacy support for `none` keyword, not covered by spec
2979
+ if ($val === "none") {
2980
+ return 0.0;
2981
+ }
2982
 
2983
+ if ($val === "auto") {
2984
+ return $val;
 
 
 
 
 
 
 
2985
  }
2986
+
2987
+ return $this->compute_length_percentage($val);
2988
  }
2989
 
2990
+ protected function _compute_margin_top(string $val)
 
 
 
 
 
2991
  {
2992
+ return $this->compute_margin($val);
2993
  }
2994
 
2995
+ protected function _compute_margin_right(string $val)
2996
  {
2997
+ return $this->compute_margin($val);
2998
  }
2999
 
3000
+ protected function _compute_margin_bottom(string $val)
3001
  {
3002
+ return $this->compute_margin($val);
3003
  }
3004
 
3005
+ protected function _compute_margin_left(string $val)
3006
  {
3007
+ return $this->compute_margin($val);
3008
  }
3009
 
3010
  /**
3011
+ * @link https://www.w3.org/TR/CSS21/box.html#padding-properties
3012
+ * @link https://www.w3.org/TR/CSS21/box.html#propdef-padding
3013
  */
3014
+ protected function _set_padding(string $val): array
3015
  {
3016
+ return $this->set_quad_shorthand("padding", $val);
3017
  }
3018
 
3019
+ /**
3020
+ * @param string $val
3021
+ * @return float|string|null
3022
+ */
3023
+ protected function compute_padding(string $val)
3024
  {
3025
+ // Legacy support for `none` keyword, not covered by spec
3026
+ if ($val === "none") {
3027
+ return 0.0;
3028
+ }
3029
+
3030
+ return $this->compute_length_percentage_positive($val);
3031
  }
3032
 
3033
+ protected function _compute_padding_top(string $val)
3034
  {
3035
+ return $this->compute_padding($val);
3036
  }
3037
 
3038
+ protected function _compute_padding_right(string $val)
3039
  {
3040
+ return $this->compute_padding($val);
3041
  }
3042
 
3043
+ protected function _compute_padding_bottom(string $val)
 
 
 
 
3044
  {
3045
+ return $this->compute_padding($val);
3046
  }
3047
 
3048
+ protected function _compute_padding_left(string $val)
3049
  {
3050
+ return $this->compute_padding($val);
3051
  }
3052
 
3053
+ /**
3054
+ * @param string $value `width || style || color`
3055
+ * @param string[] $styles The list of border styles to accept.
3056
+ *
3057
+ * @return array Array of `[width, style, color]`, or `null` if the declaration is invalid.
3058
+ */
3059
+ protected function parse_border_side(string $value, array $styles = self::BORDER_STYLES): ?array
3060
  {
3061
+ $components = $this->parse_property_value($value);
3062
+ $width = null;
3063
+ $style = null;
3064
+ $color = null;
3065
+
3066
+ foreach ($components as $val) {
3067
+ if ($style === null && \in_array($val, $styles, true)) {
3068
+ $style = $val;
3069
+ } elseif ($color === null && $this->is_color_value($val)) {
3070
+ $color = $val;
3071
+ } elseif ($width === null) {
3072
+ // Assume width
3073
+ $width = $val;
3074
+ } else {
3075
+ // Duplicates are not allowed
3076
+ return null;
3077
+ }
3078
+ }
3079
+
3080
+ return [$width, $style, $color];
3081
  }
3082
 
3083
+ /**
3084
+ * @link https://www.w3.org/TR/CSS21/box.html#border-properties
3085
+ * @link https://www.w3.org/TR/CSS21/box.html#propdef-border
3086
+ */
3087
+ protected function _set_border(string $value): array
3088
  {
3089
+ $values = $this->parse_border_side($value);
3090
+
3091
+ if ($values === null) {
3092
+ return [];
3093
+ }
3094
+
3095
+ return array_merge(
3096
+ array_combine(self::$_props_shorthand["border_top"], $values),
3097
+ array_combine(self::$_props_shorthand["border_right"], $values),
3098
+ array_combine(self::$_props_shorthand["border_bottom"], $values),
3099
+ array_combine(self::$_props_shorthand["border_left"], $values)
3100
+ );
3101
  }
3102
 
3103
  /**
3104
+ * @param string $prop
3105
+ * @param string $value
3106
+ * @return array
3107
  */
3108
+ protected function set_border_side(string $prop, string $value): array
3109
  {
3110
+ $values = $this->parse_border_side($value);
3111
+
3112
+ if ($values === null) {
3113
+ return [];
3114
+ }
3115
+
3116
+ return array_combine(self::$_props_shorthand[$prop], $values);
3117
  }
3118
 
3119
+ protected function _set_border_top(string $val): array
3120
  {
3121
+ return $this->set_border_side("border_top", $val);
3122
  }
3123
 
3124
+ protected function _set_border_right(string $val): array
3125
  {
3126
+ return $this->set_border_side("border_right", $val);
3127
  }
3128
 
3129
+ protected function _set_border_bottom(string $val): array
3130
  {
3131
+ return $this->set_border_side("border_bottom", $val);
3132
  }
3133
 
3134
+ protected function _set_border_left(string $val): array
 
 
 
 
3135
  {
3136
+ return $this->set_border_side("border_left", $val);
 
 
 
3137
  }
3138
 
3139
  /**
3140
+ * @link https://www.w3.org/TR/CSS21/box.html#propdef-border-color
 
3141
  */
3142
+ protected function _set_border_color(string $val): array
3143
  {
3144
+ return $this->set_quad_shorthand("border_color", $val);
3145
  }
3146
 
3147
+ protected function _compute_border_top_color(string $val)
 
 
 
 
3148
  {
3149
+ return $this->compute_color_value($val);
3150
  }
3151
 
3152
+ protected function _compute_border_right_color(string $val)
 
 
 
 
3153
  {
3154
+ return $this->compute_color_value($val);
3155
  }
3156
 
3157
+ protected function _compute_border_bottom_color(string $val)
 
 
 
 
 
 
 
3158
  {
3159
+ return $this->compute_color_value($val);
3160
  }
3161
 
3162
+ protected function _compute_border_left_color(string $val)
 
 
 
3163
  {
3164
+ return $this->compute_color_value($val);
3165
  }
3166
 
3167
  /**
3168
+ * @link https://www.w3.org/TR/CSS21/box.html#propdef-border-style
3169
  */
3170
+ protected function _set_border_style(string $val): array
3171
  {
3172
+ return $this->set_quad_shorthand("border_style", $val);
3173
  }
3174
 
3175
+ protected function _compute_border_top_style(string $val)
 
 
 
3176
  {
3177
+ return $this->compute_border_style($val);
3178
  }
3179
 
3180
+ protected function _compute_border_right_style(string $val)
 
 
 
 
3181
  {
3182
+ return $this->compute_border_style($val);
3183
+ }
3184
 
3185
+ protected function _compute_border_bottom_style(string $val)
3186
+ {
3187
+ return $this->compute_border_style($val);
3188
+ }
 
 
 
 
 
 
 
 
 
 
 
 
3189
 
3190
+ protected function _compute_border_left_style(string $val)
3191
+ {
3192
+ return $this->compute_border_style($val);
 
3193
  }
3194
 
3195
  /**
3196
+ * @link https://www.w3.org/TR/CSS21/box.html#propdef-border-width
 
3197
  */
3198
+ protected function _set_border_width(string $val): array
3199
  {
3200
+ return $this->set_quad_shorthand("border_width", $val);
3201
+ }
3202
 
3203
+ protected function _compute_border_top_width(string $val)
3204
+ {
3205
+ return $this->compute_line_width($val, "border_top_style");
3206
+ }
3207
 
3208
+ protected function _compute_border_right_width(string $val)
3209
+ {
3210
+ return $this->compute_line_width($val, "border_right_style");
3211
+ }
3212
 
3213
+ protected function _compute_border_bottom_width(string $val)
3214
+ {
3215
+ return $this->compute_line_width($val, "border_bottom_style");
3216
+ }
3217
 
3218
+ protected function _compute_border_left_width(string $val)
3219
+ {
3220
+ return $this->compute_line_width($val, "border_left_style");
3221
  }
3222
 
3223
  /**
3224
+ * @link https://www.w3.org/TR/css-backgrounds-3/#corners
3225
+ * @link https://www.w3.org/TR/css-backgrounds-3/#propdef-border-radius
3226
  */
3227
+ protected function _set_border_radius(string $val): array
3228
  {
3229
+ return $this->set_quad_shorthand("border_radius", $val);
3230
  }
3231
 
3232
+ protected function _compute_border_top_left_radius(string $val)
 
 
 
3233
  {
3234
+ return $this->compute_length_percentage_positive($val);
3235
  }
3236
 
3237
+ protected function _compute_border_top_right_radius(string $val)
 
 
 
3238
  {
3239
+ return $this->compute_length_percentage_positive($val);
3240
  }
3241
 
3242
+ protected function _compute_border_bottom_right_radius(string $val)
 
 
 
3243
  {
3244
+ return $this->compute_length_percentage_positive($val);
3245
+ }
3246
+
3247
+ protected function _compute_border_bottom_left_radius(string $val)
3248
+ {
3249
+ return $this->compute_length_percentage_positive($val);
3250
  }
3251
 
3252
  /**
3253
+ * @link https://www.w3.org/TR/css-ui-4/#outline-props
3254
+ * @link https://www.w3.org/TR/css-ui-4/#propdef-outline
3255
  */
3256
+ protected function _set_outline(string $value): array
3257
  {
3258
+ $values = $this->parse_border_side($value, self::OUTLINE_STYLES);
3259
 
3260
+ if ($values === null) {
3261
+ return [];
3262
  }
3263
 
3264
+ return array_combine(self::$_props_shorthand["outline"], $values);
3265
  }
3266
 
3267
+ protected function _compute_outline_color(string $val)
 
 
 
 
 
 
 
3268
  {
3269
+ return $this->compute_color_value($val);
 
 
 
 
 
 
 
 
 
 
 
3270
  }
3271
 
3272
+ protected function _compute_outline_style(string $val)
 
 
 
3273
  {
3274
+ return \in_array($val, self::OUTLINE_STYLES, true) ? $val : null;
3275
  }
3276
 
3277
+ protected function _compute_outline_width(string $val)
 
 
 
3278
  {
3279
+ return $this->compute_line_width($val, "outline_style");
3280
  }
3281
 
3282
  /**
3283
+ * @link https://www.w3.org/TR/css-ui-4/#propdef-outline-offset
3284
  */
3285
+ protected function _compute_outline_offset(string $val)
3286
  {
3287
+ return $this->compute_length($val);
3288
  }
3289
 
3290
  /**
3291
+ * Compute `border-spacing` to two lengths of the form
3292
+ * `[horizontal, vertical]`.
3293
  *
3294
+ * @link https://www.w3.org/TR/CSS21/tables.html#propdef-border-spacing
 
3295
  */
3296
+ protected function _compute_border_spacing(string $val)
3297
  {
3298
+ $parts = preg_split("/\s+/", $val);
3299
 
3300
+ if (\count($parts) > 2) {
3301
+ return null;
 
3302
  }
3303
 
3304
+ $h = $this->compute_length_positive($parts[0]);
3305
+ $v = isset($parts[1])
3306
+ ? $this->compute_length_positive($parts[1])
3307
+ : $h;
3308
 
3309
+ if ($h === null || $v === null) {
3310
+ return null;
3311
  }
3312
 
3313
+ return [$h, $v];
3314
  }
3315
 
3316
  /**
3317
+ * @link https://www.w3.org/TR/CSS21/generate.html#propdef-list-style-image
 
 
 
3318
  */
3319
+ protected function _compute_list_style_image(string $val)
3320
  {
 
 
 
 
 
 
 
3321
  $parsed_val = $this->_stylesheet->resolve_url($val);
3322
 
3323
  if ($parsed_val === "none") {
3324
+ return "none";
3325
  } else {
3326
+ return "url($parsed_val)";
3327
  }
3328
  }
3329
 
3330
  /**
3331
+ * @link https://www.w3.org/TR/CSS21/generate.html#propdef-list-style
 
 
 
 
3332
  */
3333
+ protected function _set_list_style(string $value): array
3334
  {
3335
  static $positions = ["inside", "outside"];
3336
  static $types = [
3346
  ];
3347
 
3348
  $components = $this->parse_property_value($value);
3349
+ $props = [];
3350
 
3351
  foreach ($components as $val) {
3352
+ /* https://www.w3.org/TR/CSS21/generate.html#list-style
3353
  * A value of 'none' for the 'list-style' property sets both 'list-style-type' and 'list-style-image' to 'none'
3354
  */
3355
  if ($val === "none") {
3356
+ $props["list_style_type"] = $val;
3357
+ $props["list_style_image"] = $val;
3358
  continue;
3359
  }
3360
 
3364
  //Internet Explorer 7/8 and dompdf is right.
3365
 
3366
  if (mb_substr($val, 0, 4) === "url(") {
3367
+ $props["list_style_image"] = $val;
3368
  continue;
3369
  }
3370
 
3371
+ if (\in_array($val, $types, true)) {
3372
+ $props["list_style_type"] = $val;
3373
+ } elseif (\in_array($val, $positions, true)) {
3374
+ $props["list_style_position"] = $val;
3375
  }
3376
  }
3377
+
3378
+ return $props;
3379
  }
3380
 
3381
  /**
3382
+ * @link https://www.w3.org/TR/css-page-3/#page-size-prop
3383
  */
3384
+ protected function _compute_size(string $val)
3385
  {
 
 
 
 
 
 
3386
  if ($val === "auto") {
3387
+ return $val;
 
3388
  }
3389
 
3390
+ $parts = $this->parse_property_value($val);
3391
+ $count = \count($parts);
3392
+
3393
+ if ($count === 0 || $count > 3) {
3394
+ return null;
3395
+ }
3396
 
3397
+ $size = null;
3398
+ $orientation = null;
3399
+ $lengths = [];
3400
 
3401
+ foreach ($parts as $part) {
3402
+ if ($size === null && isset(CPDF::$PAPER_SIZES[$part])) {
3403
+ $size = $part;
3404
+ } elseif ($orientation === null && ($part === "portrait" || $part === "landscape")) {
3405
+ $orientation = $part;
3406
  } else {
3407
+ $lengths[] = $part;
3408
  }
3409
+ }
3410
 
3411
+ if ($size !== null && $lengths !== []) {
3412
+ return null;
3413
+ }
 
 
3414
 
3415
+ if ($size !== null) {
3416
+ // Standard paper size
3417
+ [$l1, $l2] = \array_slice(CPDF::$PAPER_SIZES[$size], 2, 2);
3418
+ } elseif ($lengths === []) {
3419
+ // Orientation only, use default paper size
3420
+ $dims = $this->_stylesheet->get_dompdf()->getPaperSize();
3421
+ [$l1, $l2] = \array_slice($dims, 2, 2);
3422
  } else {
3423
+ // Custom paper size
3424
+ $l1 = $this->compute_length_positive($lengths[0]);
3425
+ $l2 = isset($lengths[1]) ? $this->compute_length_positive($lengths[1]) : $l1;
3426
+
3427
+ if ($l1 === null || $l2 === null) {
3428
+ return null;
3429
+ }
3430
  }
3431
 
3432
+ if (($orientation === "portrait" && $l1 > $l2)
3433
+ || ($orientation === "landscape" && $l2 > $l1)
3434
+ ) {
3435
+ return [$l2, $l1];
3436
+ }
3437
+
3438
+ return [$l1, $l2];
3439
  }
3440
 
3441
  /**
3442
+ * @param string $computed
3443
+ * @return array
3444
  *
3445
+ * @link https://www.w3.org/TR/css-transforms-1/#transform-property
 
3446
  */
3447
+ protected function _get_transform($computed)
3448
  {
3449
  //TODO: should be handled in setter (lengths set to absolute)
3450
 
3452
  $tr_value = "\s*([^,\s]+)\s*";
3453
  $angle = "\s*([^,\s]+(?:deg|rad)?)\s*";
3454
 
3455
+ if (!preg_match_all("/[a-z]+\([^\)]+\)/i", $computed, $parts, PREG_SET_ORDER)) {
3456
+ return [];
3457
  }
3458
 
3459
  $functions = [
3481
 
3482
  foreach ($functions as $name => $pattern) {
3483
  if (preg_match("/$name\s*$pattern/i", $t, $matches)) {
3484
+ $values = \array_slice($matches, 1);
3485
 
3486
  switch ($name) {
3487
  // <angle> units
3492
 
3493
  foreach ($values as $i => $value) {
3494
  if (strpos($value, "rad")) {
3495
+ $values[$i] = rad2deg((float) $value);
3496
  } else {
3497
+ $values[$i] = (float) $value;
3498
  }
3499
  }
3500
 
3566
  }
3567
 
3568
  /**
3569
+ * @param string $computed
3570
+ * @return array
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3571
  *
3572
+ * @link https://www.w3.org/TR/css-transforms-1/#transform-origin-property
 
3573
  */
3574
+ protected function _get_transform_origin($computed)
3575
  {
3576
  //TODO: should be handled in setter
3577
+
3578
+ $values = preg_split("/\s+/", $computed);
3579
 
3580
  $values = array_map(function ($value) {
3581
+ if (\in_array($value, ["top", "left"], true)) {
3582
  return 0;
3583
+ } elseif (\in_array($value, ["bottom", "right"], true)) {
3584
  return "100%";
3585
  } else {
3586
  return $value;
3595
  }
3596
 
3597
  /**
3598
+ * @param string $val
3599
+ * @return string|null
3600
  */
3601
+ protected function parse_image_resolution(string $val): ?string
3602
  {
3603
  // If exif data could be get:
3604
  // $re = '/^\s*(\d+|normal|auto)(?:\s*,\s*(\d+|normal))?\s*$/';
3614
 
3615
  /**
3616
  * auto | normal | dpi
 
 
3617
  */
3618
+ protected function _compute_background_image_resolution(string $val)
3619
  {
3620
+ return $this->parse_image_resolution($val);
3621
+ }
3622
 
3623
+ /**
3624
+ * auto | normal | dpi
3625
+ */
3626
+ protected function _compute_image_resolution(string $val)
3627
+ {
3628
+ return $this->parse_image_resolution($val);
3629
+ }
3630
 
3631
+ /**
3632
+ * @link https://www.w3.org/TR/css-break-3/#propdef-orphans
3633
+ */
3634
+ protected function _compute_orphans(string $val)
3635
+ {
3636
+ return $this->compute_integer($val);
3637
+ }
3638
 
3639
+ /**
3640
+ * @link https://www.w3.org/TR/css-break-3/#propdef-widows
3641
+ */
3642
+ protected function _compute_widows(string $val)
3643
+ {
3644
+ return $this->compute_integer($val);
3645
  }
3646
 
3647
  /**
3648
+ * @link https://www.w3.org/TR/css-color-4/#propdef-opacity
 
 
3649
  */
3650
+ protected function _compute_opacity(string $val)
3651
  {
3652
+ $number = self::CSS_NUMBER;
3653
+ $pattern = "/^($number)(%?)$/";
3654
 
3655
+ if (!preg_match($pattern, $val, $matches)) {
3656
+ return null;
 
3657
  }
3658
 
3659
+ $v = (float) $matches[1];
3660
+ $percent = $matches[2] === "%";
3661
+ $opacity = $percent ? ($v / 100) : $v;
3662
 
3663
+ return max(0.0, min($opacity, 1.0));
3664
  }
3665
 
3666
  /**
3667
+ * @link https://www.w3.org/TR/CSS21//visuren.html#propdef-z-index
3668
  */
3669
+ protected function _compute_z_index(string $val)
3670
  {
3671
+ if ($val === "auto") {
3672
+ return $val;
 
 
 
 
 
 
 
 
3673
  }
3674
 
3675
+ return $this->compute_integer($val);
3676
  }
3677
 
3678
  /**
3702
  * @return string
3703
  */
3704
  /*DEBUGCSS print: see below additional debugging util*/
3705
+ public function __toString(): string
3706
  {
3707
  $parent_font_size = $this->parent_style
3708
  ? $this->parent_style->font_size
3709
  : self::$default_font_size;
3710
 
3711
+ return print_r(array_merge(["parent_font_size" => $parent_font_size],
3712
  $this->_props), true);
3713
  }
3714
 
3715
  /*DEBUGCSS*/
3716
+ public function debug_print(): void
3717
  {
3718
  $parent_font_size = $this->parent_style
3719
  ? $this->parent_style->font_size
3737
  }
3738
  print " ]\n";
3739
  print " cached [\n";
3740
+ foreach ($this->_props_used as $prop => $val) {
3741
  print ' ' . $prop . ': ' . preg_replace("/\r\n/", ' ', print_r($val, true));
3742
  print ";\n";
3743
  }
vendor/dompdf/dompdf/src/Css/Stylesheet.php CHANGED
@@ -288,19 +288,6 @@ class Stylesheet
288
  $this->_styles[$key][] = $style;
289
  }
290
 
291
- /**
292
- * Lookup a specific Style collection
293
- *
294
- * @deprecated
295
- * @param string $key the selector of the requested Style collection
296
- *
297
- * @return Style[]
298
- */
299
- function lookup(string $key): array
300
- {
301
- return $this->_styles[$key] ?? [];
302
- }
303
-
304
  /**
305
  * load and parse a CSS string
306
  *
@@ -338,46 +325,26 @@ class Stylesheet
338
  $parsed = Helpers::parse_data_uri($file);
339
  $css = $parsed["data"];
340
  } else {
341
- $parsed_url = Helpers::explode_url($file);
342
-
343
- [$this->_protocol, $this->_base_host, $this->_base_path, $filename] = $parsed_url;
344
 
345
- $file = Helpers::build_url($this->_protocol, $this->_base_host, $this->_base_path, $filename);
 
346
 
347
- $options = $this->_dompdf->getOptions();
348
- // Download the remote file
349
- if (!$options->isRemoteEnabled() && ($this->_protocol !== "" && $this->_protocol !== "file://")) {
350
- Helpers::record_warnings(E_USER_WARNING, "Remote CSS resource '$file' referenced, but remote file download is disabled.", __FILE__, __LINE__);
351
- return;
352
- }
353
- if ($this->_protocol === "" || $this->_protocol === "file://") {
354
- $realfile = realpath($file);
355
-
356
- $rootDir = realpath($options->getRootDir());
357
- if (strpos($realfile, $rootDir) !== 0) {
358
- $chroot = $options->getChroot();
359
- $chrootValid = false;
360
- foreach ($chroot as $chrootPath) {
361
- $chrootPath = realpath($chrootPath);
362
- if ($chrootPath !== false && strpos($realfile, $chrootPath) === 0) {
363
- $chrootValid = true;
364
- break;
365
- }
366
- }
367
- if ($chrootValid !== true) {
368
- Helpers::record_warnings(E_USER_WARNING, "Permission denied on $file. The file could not be found under the paths specified by Options::chroot.", __FILE__, __LINE__);
369
  return;
370
  }
371
  }
372
-
373
- if (!$realfile) {
374
- Helpers::record_warnings(E_USER_WARNING, "File '$realfile' not found.", __FILE__, __LINE__);
375
- return;
376
- }
377
-
378
- $file = $realfile;
379
  }
380
-
381
  [$css, $http_response_header] = Helpers::getFileContent($file, $this->_dompdf->getHttpContext());
382
 
383
  $good_mime_type = true;
@@ -392,11 +359,12 @@ class Stylesheet
392
  }
393
  }
394
  }
395
-
396
  if (!$good_mime_type || $css === null) {
397
  Helpers::record_warnings(E_USER_WARNING, "Unable to load css file $file", __FILE__, __LINE__);
398
  return;
399
  }
 
 
400
  }
401
 
402
  $this->_parse_css($css);
@@ -577,7 +545,7 @@ class Stylesheet
577
  // class=".* $tok .*" and class=".* $tok"
578
 
579
  // This doesn't work because libxml only supports XPath 1.0...
580
- //$query .= "[matches(@$attr,\"^${tok}\$|^${tok}[ ]+|[ ]+${tok}\$|[ ]+${tok}[ ]+\")]";
581
 
582
  $query .= "[contains(concat(' ', normalize-space(@$attr), ' '), concat(' ', '$tok', ' '))]";
583
  $tok = "";
@@ -620,7 +588,7 @@ class Stylesheet
620
  switch ($tok) {
621
 
622
  case "first-child":
623
- $query .= "[1]";
624
  $tok = "";
625
  break;
626
 
@@ -653,16 +621,17 @@ class Stylesheet
653
  $pseudo_classes[$tok] = true;
654
  $p = $i + 1;
655
  $nth = trim(mb_substr($selector, $p, strpos($selector, ")", $i) - $p));
 
656
 
657
  // 1
658
  if (preg_match("/^\d+$/", $nth)) {
659
- $condition = "position() = $nth";
660
  } // odd
661
  elseif ($nth === "odd") {
662
- $condition = "(position() mod 2) = 1";
663
  } // even
664
  elseif ($nth === "even") {
665
- $condition = "(position() mod 2) = 0";
666
  } // an+b
667
  else {
668
  $condition = $this->_selector_an_plus_b($nth, $last);
@@ -684,16 +653,17 @@ class Stylesheet
684
  $pseudo_classes[$tok] = true;
685
  $p = $i + 1;
686
  $nth = trim(mb_substr($selector, $p, strpos($selector, ")", $i) - $p));
 
687
 
688
  // 1
689
  if (preg_match("/^\d+$/", $nth)) {
690
- $condition = "position() = $nth";
691
  } // odd
692
  elseif ($nth === "odd") {
693
- $condition = "(position() mod 2) = 1";
694
  } // even
695
  elseif ($nth === "even") {
696
- $condition = "(position() mod 2) = 0";
697
  } // an+b
698
  else {
699
  $condition = $this->_selector_an_plus_b($nth, $last);
@@ -920,26 +890,27 @@ class Stylesheet
920
  /**
921
  * https://github.com/tenderlove/nokogiri/blob/master/lib/nokogiri/css/xpath_visitor.rb
922
  *
923
- * @param $expr
924
  * @param bool $last
 
925
  * @return string
926
  */
927
- protected function _selector_an_plus_b($expr, $last = false)
928
  {
929
  $expr = preg_replace("/\s/", "", $expr);
930
  if (!preg_match("/^(?P<a>-?[0-9]*)?n(?P<b>[-+]?[0-9]+)?$/", $expr, $matches)) {
931
  return "false()";
932
  }
933
 
934
- $a = ((isset($matches["a"]) && $matches["a"] !== "") ? intval($matches["a"]) : 1);
935
- $b = ((isset($matches["b"]) && $matches["b"] !== "") ? intval($matches["b"]) : 0);
936
 
937
- $position = ($last ? "(last()-position()+1)" : "position()");
938
 
939
  if ($b == 0) {
940
  return "($position mod $a) = 0";
941
  } else {
942
- $compare = (($a < 0) ? "<=" : ">=");
943
  $b2 = -$b;
944
  if ($b2 >= 0) {
945
  $b2 = "+$b2";
@@ -1006,7 +977,7 @@ class Stylesheet
1006
  continue;
1007
  }
1008
 
1009
- $content = $style->get_prop("content");
1010
 
1011
  // Do not create non-displayed before/after pseudo elements
1012
  // https://www.w3.org/TR/CSS21/generate.html#content
@@ -1075,7 +1046,7 @@ class Stylesheet
1075
  // Now create the styles and assign them to the appropriate frames. (We
1076
  // iterate over the tree using an implicit FrameTree iterator.)
1077
  $root_flg = false;
1078
- foreach ($tree->get_frames() as $frame) {
1079
  // Helpers::pre_r($frame->get_node()->nodeName . ":");
1080
  if (!$root_flg && $this->_page_styles["base"]) {
1081
  $style = $this->_page_styles["base"];
@@ -1330,7 +1301,7 @@ class Stylesheet
1330
  $media_query_feature = strtolower($media_query_match[3]);
1331
  $media_query_value = strtolower($media_query_match[2]);
1332
  $mq[] = [$media_query_feature, $media_query_value];
1333
- } else if (empty($media_query_match[4]) === false) {
1334
  $media_query_feature = strtolower($media_query_match[5]);
1335
  $media_query_value = (array_key_exists(8, $media_query_match) ? strtolower($media_query_match[8]) : null);
1336
  $mq[] = [$media_query_feature, $media_query_value];
@@ -1431,20 +1402,16 @@ class Stylesheet
1431
  $val = preg_replace("/url\(\s*['\"]?([^'\")]+)['\"]?\s*\)/", "\\1", trim($val));
1432
 
1433
  // Resolve the url now in the context of the current stylesheet
1434
- $parsed_url = Helpers::explode_url($val);
1435
  $path = Helpers::build_url($this->_protocol,
1436
  $this->_base_host,
1437
  $this->_base_path,
1438
  $val);
1439
- if (($parsed_url["protocol"] === "" || $parsed_url["protocol"] === "file://") && ($this->_protocol === "" || $this->_protocol === "file://")) {
1440
- $path = realpath($path);
1441
- // If realpath returns FALSE then specifically state that there is no background image
1442
- if ($path === false) {
1443
- $path = "none";
1444
- }
1445
  }
1446
  }
1447
  if ($DEBUGCSS) {
 
1448
  print "<pre>[_image\n";
1449
  print_r($parsed_url);
1450
  print $this->_protocol . "\n" . $this->_base_path . "\n" . $path . "\n";
@@ -1493,9 +1460,9 @@ class Stylesheet
1493
  // Above does not work for subfolders and absolute urls.
1494
  // Todo: As above, do we need to replace php or file to an empty protocol for local files?
1495
 
1496
- $url = $this->resolve_url($url);
1497
-
1498
- $this->load_css_file($url);
1499
 
1500
  // Restore the current base url
1501
  $this->_protocol = $protocol;
@@ -1516,9 +1483,7 @@ class Stylesheet
1516
 
1517
  preg_match_all("/(url|local)\s*\([\"\']?([^\"\'\)]+)[\"\']?\)\s*(format\s*\([\"\']?([^\"\'\)]+)[\"\']?\))?/i", $descriptors->src, $src);
1518
 
1519
- $sources = [];
1520
  $valid_sources = [];
1521
-
1522
  foreach ($src[0] as $i => $value) {
1523
  $source = [
1524
  "local" => strtolower($src[1][$i]) === "local",
@@ -1527,11 +1492,9 @@ class Stylesheet
1527
  "path" => Helpers::build_url($this->_protocol, $this->_base_host, $this->_base_path, $src[2][$i]),
1528
  ];
1529
 
1530
- if (!$source["local"] && in_array($source["format"], ["", "truetype"])) {
1531
  $valid_sources[] = $source;
1532
  }
1533
-
1534
- $sources[] = $source;
1535
  }
1536
 
1537
  // No valid sources
@@ -1685,7 +1648,7 @@ class Stylesheet
1685
  {
1686
  $options = $this->_dompdf->getOptions();
1687
  $rootDir = realpath($options->getRootDir());
1688
- return $rootDir . self::DEFAULT_STYLESHEET;
1689
  }
1690
 
1691
  /**
288
  $this->_styles[$key][] = $style;
289
  }
290
 
 
 
 
 
 
 
 
 
 
 
 
 
 
291
  /**
292
  * load and parse a CSS string
293
  *
325
  $parsed = Helpers::parse_data_uri($file);
326
  $css = $parsed["data"];
327
  } else {
328
+ $options = $this->_dompdf->getOptions();
 
 
329
 
330
+ $parsed_url = Helpers::explode_url($file);
331
+ $protocol = $parsed_url["protocol"];
332
 
333
+ if ($file !== $this->getDefaultStylesheet()) {
334
+ $allowed_protocols = $options->getAllowedProtocols();
335
+ if (!array_key_exists($protocol, $allowed_protocols)) {
336
+ Helpers::record_warnings(E_USER_WARNING, "Permission denied on $file. The communication protocol is not supported.", __FILE__, __LINE__);
337
+ return;
338
+ }
339
+ foreach ($allowed_protocols[$protocol]["rules"] as $rule) {
340
+ [$result, $message] = $rule($file);
341
+ if (!$result) {
342
+ Helpers::record_warnings(E_USER_WARNING, "Error loading $file: $message", __FILE__, __LINE__);
 
 
 
 
 
 
 
 
 
 
 
 
343
  return;
344
  }
345
  }
 
 
 
 
 
 
 
346
  }
347
+
348
  [$css, $http_response_header] = Helpers::getFileContent($file, $this->_dompdf->getHttpContext());
349
 
350
  $good_mime_type = true;
359
  }
360
  }
361
  }
 
362
  if (!$good_mime_type || $css === null) {
363
  Helpers::record_warnings(E_USER_WARNING, "Unable to load css file $file", __FILE__, __LINE__);
364
  return;
365
  }
366
+
367
+ [$this->_protocol, $this->_base_host, $this->_base_path] = $parsed_url;
368
  }
369
 
370
  $this->_parse_css($css);
545
  // class=".* $tok .*" and class=".* $tok"
546
 
547
  // This doesn't work because libxml only supports XPath 1.0...
548
+ //$query .= "[matches(@$attr,\"^{$tok}\$|^{$tok}[ ]+|[ ]+{$tok}\$|[ ]+{$tok}[ ]+\")]";
549
 
550
  $query .= "[contains(concat(' ', normalize-space(@$attr), ' '), concat(' ', '$tok', ' '))]";
551
  $tok = "";
588
  switch ($tok) {
589
 
590
  case "first-child":
591
+ $query .= "[not(preceding-sibling::*)]";
592
  $tok = "";
593
  break;
594
 
621
  $pseudo_classes[$tok] = true;
622
  $p = $i + 1;
623
  $nth = trim(mb_substr($selector, $p, strpos($selector, ")", $i) - $p));
624
+ $position = $last ? "(last()-position()+1)" : "position()";
625
 
626
  // 1
627
  if (preg_match("/^\d+$/", $nth)) {
628
+ $condition = "$position = $nth";
629
  } // odd
630
  elseif ($nth === "odd") {
631
+ $condition = "($position mod 2) = 1";
632
  } // even
633
  elseif ($nth === "even") {
634
+ $condition = "($position mod 2) = 0";
635
  } // an+b
636
  else {
637
  $condition = $this->_selector_an_plus_b($nth, $last);
653
  $pseudo_classes[$tok] = true;
654
  $p = $i + 1;
655
  $nth = trim(mb_substr($selector, $p, strpos($selector, ")", $i) - $p));
656
+ $position = $last ? "(last()-position()+1)" : "position()";
657
 
658
  // 1
659
  if (preg_match("/^\d+$/", $nth)) {
660
+ $condition = "$position = $nth";
661
  } // odd
662
  elseif ($nth === "odd") {
663
+ $condition = "($position mod 2) = 1";
664
  } // even
665
  elseif ($nth === "even") {
666
+ $condition = "($position mod 2) = 0";
667
  } // an+b
668
  else {
669
  $condition = $this->_selector_an_plus_b($nth, $last);
890
  /**
891
  * https://github.com/tenderlove/nokogiri/blob/master/lib/nokogiri/css/xpath_visitor.rb
892
  *
893
+ * @param string $expr
894
  * @param bool $last
895
+ *
896
  * @return string
897
  */
898
+ protected function _selector_an_plus_b(string $expr, bool $last = false): string
899
  {
900
  $expr = preg_replace("/\s/", "", $expr);
901
  if (!preg_match("/^(?P<a>-?[0-9]*)?n(?P<b>[-+]?[0-9]+)?$/", $expr, $matches)) {
902
  return "false()";
903
  }
904
 
905
+ $a = (isset($matches["a"]) && $matches["a"] !== "") ? ($matches["a"] !== "-" ? intval($matches["a"]) : -1) : 1;
906
+ $b = (isset($matches["b"]) && $matches["b"] !== "") ? intval($matches["b"]) : 0;
907
 
908
+ $position = $last ? "(last()-position()+1)" : "position()";
909
 
910
  if ($b == 0) {
911
  return "($position mod $a) = 0";
912
  } else {
913
+ $compare = ($a < 0) ? "<=" : ">=";
914
  $b2 = -$b;
915
  if ($b2 >= 0) {
916
  $b2 = "+$b2";
977
  continue;
978
  }
979
 
980
+ $content = $style->get_specified("content");
981
 
982
  // Do not create non-displayed before/after pseudo elements
983
  // https://www.w3.org/TR/CSS21/generate.html#content
1046
  // Now create the styles and assign them to the appropriate frames. (We
1047
  // iterate over the tree using an implicit FrameTree iterator.)
1048
  $root_flg = false;
1049
+ foreach ($tree as $frame) {
1050
  // Helpers::pre_r($frame->get_node()->nodeName . ":");
1051
  if (!$root_flg && $this->_page_styles["base"]) {
1052
  $style = $this->_page_styles["base"];
1301
  $media_query_feature = strtolower($media_query_match[3]);
1302
  $media_query_value = strtolower($media_query_match[2]);
1303
  $mq[] = [$media_query_feature, $media_query_value];
1304
+ } elseif (empty($media_query_match[4]) === false) {
1305
  $media_query_feature = strtolower($media_query_match[5]);
1306
  $media_query_value = (array_key_exists(8, $media_query_match) ? strtolower($media_query_match[8]) : null);
1307
  $mq[] = [$media_query_feature, $media_query_value];
1402
  $val = preg_replace("/url\(\s*['\"]?([^'\")]+)['\"]?\s*\)/", "\\1", trim($val));
1403
 
1404
  // Resolve the url now in the context of the current stylesheet
 
1405
  $path = Helpers::build_url($this->_protocol,
1406
  $this->_base_host,
1407
  $this->_base_path,
1408
  $val);
1409
+ if ($path === null) {
1410
+ $path = "none";
 
 
 
 
1411
  }
1412
  }
1413
  if ($DEBUGCSS) {
1414
+ $parsed_url = Helpers::explode_url($path);
1415
  print "<pre>[_image\n";
1416
  print_r($parsed_url);
1417
  print $this->_protocol . "\n" . $this->_base_path . "\n" . $path . "\n";
1460
  // Above does not work for subfolders and absolute urls.
1461
  // Todo: As above, do we need to replace php or file to an empty protocol for local files?
1462
 
1463
+ if (($url = $this->resolve_url($url)) !== "none") {
1464
+ $this->load_css_file($url);
1465
+ }
1466
 
1467
  // Restore the current base url
1468
  $this->_protocol = $protocol;
1483
 
1484
  preg_match_all("/(url|local)\s*\([\"\']?([^\"\'\)]+)[\"\']?\)\s*(format\s*\([\"\']?([^\"\'\)]+)[\"\']?\))?/i", $descriptors->src, $src);
1485
 
 
1486
  $valid_sources = [];
 
1487
  foreach ($src[0] as $i => $value) {
1488
  $source = [
1489
  "local" => strtolower($src[1][$i]) === "local",
1492
  "path" => Helpers::build_url($this->_protocol, $this->_base_host, $this->_base_path, $src[2][$i]),
1493
  ];
1494
 
1495
+ if (!$source["local"] && in_array($source["format"], ["", "truetype"]) && $source["path"] !== null) {
1496
  $valid_sources[] = $source;
1497
  }
 
 
1498
  }
1499
 
1500
  // No valid sources
1648
  {
1649
  $options = $this->_dompdf->getOptions();
1650
  $rootDir = realpath($options->getRootDir());
1651
+ return Helpers::build_url("file://", "", $rootDir, $rootDir . self::DEFAULT_STYLESHEET);
1652
  }
1653
 
1654
  /**
vendor/dompdf/dompdf/src/Dompdf.php CHANGED
@@ -14,11 +14,10 @@ use Dompdf\Adapter\CPDF;
14
  use DOMXPath;
15
  use Dompdf\Frame\Factory;
16
  use Dompdf\Frame\FrameTree;
17
- use HTML5_Tokenizer;
18
- use HTML5_TreeBuilder;
19
  use Dompdf\Image\Cache;
20
  use Dompdf\Css\Stylesheet;
21
  use Dompdf\Helpers;
 
22
 
23
  /**
24
  * Dompdf - PHP5 HTML to PDF renderer
@@ -197,16 +196,6 @@ class Dompdf
197
  */
198
  private $quirksmode = false;
199
 
200
- /**
201
- * Protocol whitelist
202
- *
203
- * Protocols and PHP wrappers allowed in URLs. Full support is not
204
- * guaranteed for the protocols/wrappers contained in this array.
205
- *
206
- * @var array
207
- */
208
- private $allowedProtocols = ["", "file://", "http://", "https://"];
209
-
210
  /**
211
  * Local file extension whitelist
212
  *
@@ -259,7 +248,7 @@ class Dompdf
259
  /**
260
  * Class constructor
261
  *
262
- * @param array|Options $options
263
  */
264
  public function __construct($options = null)
265
  {
@@ -272,8 +261,11 @@ class Dompdf
272
  }
273
 
274
  $versionFile = realpath(__DIR__ . '/../VERSION');
275
- if (file_exists($versionFile) && ($version = trim(file_get_contents($versionFile))) !== false && $version !== '$Format:<%h>$') {
276
- $this->version = sprintf('dompdf %s', $version);
 
 
 
277
  }
278
 
279
  $this->setPhpConfig();
@@ -281,8 +273,8 @@ class Dompdf
281
  $this->paperSize = $this->options->getDefaultPaperSize();
282
  $this->paperOrientation = $this->options->getDefaultPaperOrientation();
283
 
284
- $this->setCanvas(CanvasFactory::get_instance($this, $this->paperSize, $this->paperOrientation));
285
- $this->setFontMetrics(new FontMetrics($this->getCanvas(), $this->getOptions()));
286
  $this->css = new Stylesheet($this);
287
 
288
  $this->restorePhpConfig();
@@ -353,43 +345,25 @@ class Dompdf
353
  [$this->protocol, $this->baseHost, $this->basePath] = Helpers::explode_url($file);
354
  }
355
  $protocol = strtolower($this->protocol);
356
-
357
  $uri = Helpers::build_url($this->protocol, $this->baseHost, $this->basePath, $file);
358
 
359
- if (!in_array($protocol, $this->allowedProtocols, true)) {
 
360
  throw new Exception("Permission denied on $file. The communication protocol is not supported.");
361
  }
362
 
363
- if (!$this->options->isRemoteEnabled() && ($protocol !== "" && $protocol !== "file://")) {
364
- throw new Exception("Remote file requested, but remote file download is disabled.");
365
- }
366
-
367
- if ($protocol === "" || $protocol === "file://") {
368
- $realfile = realpath($uri);
369
-
370
- $chroot = $this->options->getChroot();
371
- $chrootValid = false;
372
- foreach ($chroot as $chrootPath) {
373
- $chrootPath = realpath($chrootPath);
374
- if ($chrootPath !== false && strpos($realfile, $chrootPath) === 0) {
375
- $chrootValid = true;
376
- break;
377
- }
378
- }
379
- if ($chrootValid !== true) {
380
- throw new Exception("Permission denied on $file. The file could not be found under the paths specified by Options::chroot.");
381
- }
382
-
383
- $ext = strtolower(pathinfo($realfile, PATHINFO_EXTENSION));
384
  if (!in_array($ext, $this->allowedLocalFileExtensions)) {
385
- throw new Exception("Permission denied on $file. This file extension is forbidden");
386
  }
 
387
 
388
- if (!$realfile) {
389
- throw new Exception("File '$file' not found.");
 
 
390
  }
391
-
392
- $uri = $realfile;
393
  }
394
 
395
  [$contents, $http_response_header] = Helpers::getFileContent($uri, $this->options->getHttpContext());
@@ -498,43 +472,18 @@ class Dompdf
498
 
499
  try {
500
  // @todo Take the quirksmode into account
 
501
  // http://hsivonen.iki.fi/doctype/
502
- // https://developer.mozilla.org/en/mozilla's_quirks_mode
503
  $quirksmode = false;
504
 
505
- if ($this->options->isHtml5ParserEnabled() && class_exists(HTML5_Tokenizer::class)) {
506
- $tokenizer = new HTML5_Tokenizer($str);
507
- $tokenizer->parse();
508
- $doc = $tokenizer->save();
509
-
510
- $quirksmode = ($tokenizer->getTree()->getQuirksMode() > HTML5_TreeBuilder::NO_QUIRKS);
511
- } else {
512
- // loadHTML assumes ISO-8859-1 unless otherwise specified on the HTML document header.
513
- // http://devzone.zend.com/1538/php-dom-xml-extension-encoding-processing/ (see #4)
514
- // http://stackoverflow.com/a/11310258/264628
515
- $doc = new DOMDocument("1.0", $encoding);
516
- $doc->preserveWhiteSpace = true;
517
- $doc->loadHTML($str);
518
- $doc->encoding = $encoding;
519
-
520
- // If some text is before the doctype, we are in quirksmode
521
- if (preg_match("/^(.+)<!doctype/i", ltrim($str), $matches)) {
522
- $quirksmode = true;
523
- } // If no doctype is provided, we are in quirksmode
524
- elseif (!preg_match("/^<!doctype/i", ltrim($str), $matches)) {
525
- $quirksmode = true;
526
- } else {
527
- // HTML5 <!DOCTYPE html>
528
- if (!$doc->doctype->publicId && !$doc->doctype->systemId) {
529
- $quirksmode = false;
530
- }
531
 
532
- // not XHTML
533
- if (!preg_match("/xhtml/i", $doc->doctype->publicId)) {
534
- $quirksmode = true;
535
- }
536
- }
537
- }
538
 
539
  $this->loadDOM($doc, $quirksmode);
540
  } finally {
@@ -584,9 +533,11 @@ class Dompdf
584
  $acceptedmedia[] = $this->options->getDefaultMediaType();
585
 
586
  // <base href="" />
587
- $base_nodes = $this->dom->getElementsByTagName("base");
588
- if ($base_nodes->length && ($href = $base_nodes->item(0)->getAttribute("href"))) {
589
- [$this->protocol, $this->baseHost, $this->basePath] = Helpers::explode_url($href);
 
 
590
  }
591
 
592
  // Set the base path of the Stylesheet to that of the file being processed
@@ -628,7 +579,9 @@ class Dompdf
628
  $url = $tag->getAttribute("href");
629
  $url = Helpers::build_url($this->protocol, $this->baseHost, $this->basePath, $url);
630
 
631
- $this->css->load_css_file($url, Stylesheet::ORIG_AUTHOR);
 
 
632
  }
633
  break;
634
 
@@ -726,9 +679,8 @@ class Dompdf
726
  public function render()
727
  {
728
  $this->setPhpConfig();
729
- $options = $this->options;
730
 
731
- $logOutputFile = $options->getLogOutputFile();
732
  if ($logOutputFile) {
733
  if (!file_exists($logOutputFile) && is_writable(dirname($logOutputFile))) {
734
  touch($logOutputFile);
@@ -753,29 +705,27 @@ class Dompdf
753
  $pageStyle->inherit($basePageStyle);
754
  }
755
 
756
- $defaultOptionPaperSize = $this->getPaperSize($options->getDefaultPaperSize());
757
- // If there is a CSS defined paper size compare to the paper size used to create the canvas to determine a
758
- // recreation need
759
  if (is_array($basePageStyle->size)) {
760
- $basePageStyleSize = $basePageStyle->size;
761
- $this->setPaper([0, 0, $basePageStyleSize[0], $basePageStyleSize[1]]);
762
  }
763
 
764
- $paperSize = $this->getPaperSize();
765
- if (
766
- $defaultOptionPaperSize[2] !== $paperSize[2] ||
767
- $defaultOptionPaperSize[3] !== $paperSize[3] ||
768
- $options->getDefaultPaperOrientation() !== $this->paperOrientation
769
- ) {
770
- $this->setCanvas(CanvasFactory::get_instance($this, $this->paperSize, $this->paperOrientation));
771
- $this->fontMetrics->setCanvas($this->getCanvas());
772
- }
773
 
774
- $canvas = $this->getCanvas();
 
 
 
775
 
 
776
  $root = null;
777
 
778
- foreach ($this->tree->get_frames() as $frame) {
779
  // Set up the root frame
780
  if (is_null($root)) {
781
  $root = Factory::decorate_root($this->tree->get_root(), $this);
@@ -819,6 +769,14 @@ class Dompdf
819
  // This is where the magic happens:
820
  $root->reflow();
821
 
 
 
 
 
 
 
 
 
822
  // Clean up cached images
823
  if (!$this->options->getDebugKeepTemp()) {
824
  Cache::clear($this->options->getDebugPng());
@@ -846,17 +804,6 @@ class Dompdf
846
  $this->restorePhpConfig();
847
  }
848
 
849
- /**
850
- * Add meta information to the PDF after rendering
851
- */
852
- public function add_info($label, $value)
853
- {
854
- $canvas = $this->getCanvas();
855
- if (!is_null($canvas)) {
856
- $canvas->add_info($label, $value);
857
- }
858
- }
859
-
860
  /**
861
  * Writes the output buffer in the log file
862
  *
@@ -883,6 +830,27 @@ class Dompdf
883
  file_put_contents($logOutputFile, $out);
884
  }
885
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
886
  /**
887
  * Streams the PDF to the client.
888
  *
@@ -903,10 +871,7 @@ class Dompdf
903
  {
904
  $this->setPhpConfig();
905
 
906
- $canvas = $this->getCanvas();
907
- if (!is_null($canvas)) {
908
- $canvas->stream($filename, $options);
909
- }
910
 
911
  $this->restorePhpConfig();
912
  }
@@ -927,12 +892,7 @@ class Dompdf
927
  {
928
  $this->setPhpConfig();
929
 
930
- $canvas = $this->getCanvas();
931
- if (is_null($canvas)) {
932
- return null;
933
- }
934
-
935
- $output = $canvas->output($options);
936
 
937
  $this->restorePhpConfig();
938
 
@@ -1006,7 +966,7 @@ class Dompdf
1006
  /**
1007
  * Sets the paper size & orientation
1008
  *
1009
- * @param string|array $size 'letter', 'legal', 'A4', etc. {@link Dompdf\Adapter\CPDF::$PAPER_SIZES}
1010
  * @param string $orientation 'portrait' or 'landscape'
1011
  * @return $this
1012
  */
@@ -1020,19 +980,25 @@ class Dompdf
1020
  /**
1021
  * Gets the paper size
1022
  *
1023
- * @param null|string|array $paperSize
1024
- * @return int[] A four-element integer array
1025
  */
1026
- public function getPaperSize($paperSize = null)
1027
  {
1028
- $size = $paperSize !== null ? $paperSize : $this->paperSize;
1029
- if (is_array($size)) {
1030
- return $size;
1031
- } else if (isset(Adapter\CPDF::$PAPER_SIZES[mb_strtolower($size)])) {
1032
- return Adapter\CPDF::$PAPER_SIZES[mb_strtolower($size)];
1033
  } else {
1034
- return Adapter\CPDF::$PAPER_SIZES["letter"];
 
 
 
 
 
1035
  }
 
 
1036
  }
1037
 
1038
  /**
@@ -1265,6 +1231,11 @@ class Dompdf
1265
  }
1266
 
1267
  /**
 
 
 
 
 
1268
  * @param Canvas $canvas
1269
  * @return $this
1270
  */
@@ -1361,7 +1332,7 @@ class Dompdf
1361
  }
1362
 
1363
  $this->options = $options;
1364
- $fontMetrics = $this->getFontMetrics();
1365
  if (isset($fontMetrics)) {
1366
  $fontMetrics->setOptions($options);
1367
  }
@@ -1397,15 +1368,16 @@ class Dompdf
1397
 
1398
  /**
1399
  * @param array $callbacks the set of callbacks to set
 
1400
  * @deprecated
1401
  */
1402
  public function set_callbacks($callbacks)
1403
  {
1404
- $this->setCallbacks($callbacks);
1405
  }
1406
 
1407
  /**
1408
- * Sets callbacks for events like rendering of pages and elements.
1409
  *
1410
  * The callbacks array should contain arrays with `event` set to a callback
1411
  * event name and `f` set to a function or any other callable.
@@ -1416,27 +1388,31 @@ class Dompdf
1416
  * * `end_frame`: called after frame rendering is complete
1417
  * * `begin_page_render`: called before a page is rendered
1418
  * * `end_page_render`: called after page rendering is complete
 
1419
  *
1420
- * The function `f` must take an array as argument, which contains info
1421
- * about the event (`[0 => Canvas, 1 => Frame, "canvas" => Canvas,
1422
- * "frame" => Frame]`).
 
1423
  *
1424
- * @param array $callbacks The set of callbacks to set
 
1425
  */
1426
- public function setCallbacks($callbacks)
1427
  {
1428
- if (is_array($callbacks)) {
1429
- $this->callbacks = [];
1430
- foreach ($callbacks as $c) {
1431
- if (is_array($c) && isset($c["event"]) && isset($c["f"])) {
1432
- $event = $c["event"];
1433
- $f = $c["f"];
1434
- if (is_string($event) && is_callable($f)) {
1435
- $this->callbacks[$event][] = $f;
1436
- }
1437
  }
1438
  }
1439
  }
 
 
1440
  }
1441
 
1442
  /**
14
  use DOMXPath;
15
  use Dompdf\Frame\Factory;
16
  use Dompdf\Frame\FrameTree;
 
 
17
  use Dompdf\Image\Cache;
18
  use Dompdf\Css\Stylesheet;
19
  use Dompdf\Helpers;
20
+ use Masterminds\HTML5;
21
 
22
  /**
23
  * Dompdf - PHP5 HTML to PDF renderer
196
  */
197
  private $quirksmode = false;
198
 
 
 
 
 
 
 
 
 
 
 
199
  /**
200
  * Local file extension whitelist
201
  *
248
  /**
249
  * Class constructor
250
  *
251
+ * @param Options|array|null $options
252
  */
253
  public function __construct($options = null)
254
  {
261
  }
262
 
263
  $versionFile = realpath(__DIR__ . '/../VERSION');
264
+ if (($version = file_get_contents($versionFile)) !== false) {
265
+ $version = trim($version);
266
+ if ($version !== '$Format:<%h>$') {
267
+ $this->version = sprintf('dompdf %s', $version);
268
+ }
269
  }
270
 
271
  $this->setPhpConfig();
273
  $this->paperSize = $this->options->getDefaultPaperSize();
274
  $this->paperOrientation = $this->options->getDefaultPaperOrientation();
275
 
276
+ $this->canvas = CanvasFactory::get_instance($this, $this->paperSize, $this->paperOrientation);
277
+ $this->fontMetrics = new FontMetrics($this->canvas, $this->options);
278
  $this->css = new Stylesheet($this);
279
 
280
  $this->restorePhpConfig();
345
  [$this->protocol, $this->baseHost, $this->basePath] = Helpers::explode_url($file);
346
  }
347
  $protocol = strtolower($this->protocol);
 
348
  $uri = Helpers::build_url($this->protocol, $this->baseHost, $this->basePath, $file);
349
 
350
+ $allowed_protocols = $this->options->getAllowedProtocols();
351
+ if (!array_key_exists($protocol, $allowed_protocols)) {
352
  throw new Exception("Permission denied on $file. The communication protocol is not supported.");
353
  }
354
 
355
+ if ($protocol === "file://") {
356
+ $ext = strtolower(pathinfo($uri, PATHINFO_EXTENSION));
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
357
  if (!in_array($ext, $this->allowedLocalFileExtensions)) {
358
+ throw new Exception("Permission denied on $file: The file extension is forbidden.");
359
  }
360
+ }
361
 
362
+ foreach ($allowed_protocols[$protocol]["rules"] as $rule) {
363
+ [$result, $message] = $rule($uri);
364
+ if (!$result) {
365
+ throw new Exception("Error loading $file: $message");
366
  }
 
 
367
  }
368
 
369
  [$contents, $http_response_header] = Helpers::getFileContent($uri, $this->options->getHttpContext());
472
 
473
  try {
474
  // @todo Take the quirksmode into account
475
+ // https://quirks.spec.whatwg.org/
476
  // http://hsivonen.iki.fi/doctype/
 
477
  $quirksmode = false;
478
 
479
+ $html5 = new HTML5(['encoding' => $encoding, 'disable_html_ns' => true]);
480
+ $dom = $html5->loadHTML($str);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
481
 
482
+ // extra step to normalize the HTML document structure
483
+ // see Masterminds/html5-php#166
484
+ $doc = new DOMDocument("1.0", $encoding);
485
+ $doc->preserveWhiteSpace = true;
486
+ $doc->loadHTML($html5->saveHTML($dom), LIBXML_NOWARNING | LIBXML_NOERROR);
 
487
 
488
  $this->loadDOM($doc, $quirksmode);
489
  } finally {
533
  $acceptedmedia[] = $this->options->getDefaultMediaType();
534
 
535
  // <base href="" />
536
+ /** @var \DOMElement|null */
537
+ $baseNode = $this->dom->getElementsByTagName("base")->item(0);
538
+ $baseHref = $baseNode ? $baseNode->getAttribute("href") : "";
539
+ if ($baseHref !== "") {
540
+ [$this->protocol, $this->baseHost, $this->basePath] = Helpers::explode_url($baseHref);
541
  }
542
 
543
  // Set the base path of the Stylesheet to that of the file being processed
579
  $url = $tag->getAttribute("href");
580
  $url = Helpers::build_url($this->protocol, $this->baseHost, $this->basePath, $url);
581
 
582
+ if ($url !== null) {
583
+ $this->css->load_css_file($url, Stylesheet::ORIG_AUTHOR);
584
+ }
585
  }
586
  break;
587
 
679
  public function render()
680
  {
681
  $this->setPhpConfig();
 
682
 
683
+ $logOutputFile = $this->options->getLogOutputFile();
684
  if ($logOutputFile) {
685
  if (!file_exists($logOutputFile) && is_writable(dirname($logOutputFile))) {
686
  touch($logOutputFile);
705
  $pageStyle->inherit($basePageStyle);
706
  }
707
 
708
+ // Set paper size if defined via CSS
 
 
709
  if (is_array($basePageStyle->size)) {
710
+ [$width, $height] = $basePageStyle->size;
711
+ $this->setPaper([0, 0, $width, $height]);
712
  }
713
 
714
+ // Create a new canvas instance if the current one does not match the
715
+ // desired paper size
716
+ $canvasWidth = $this->canvas->get_width();
717
+ $canvasHeight = $this->canvas->get_height();
718
+ $size = $this->getPaperSize();
 
 
 
 
719
 
720
+ if ($canvasWidth !== $size[2] || $canvasHeight !== $size[3]) {
721
+ $this->canvas = CanvasFactory::get_instance($this, $this->paperSize, $this->paperOrientation);
722
+ $this->fontMetrics->setCanvas($this->canvas);
723
+ }
724
 
725
+ $canvas = $this->canvas;
726
  $root = null;
727
 
728
+ foreach ($this->tree as $frame) {
729
  // Set up the root frame
730
  if (is_null($root)) {
731
  $root = Factory::decorate_root($this->tree->get_root(), $this);
769
  // This is where the magic happens:
770
  $root->reflow();
771
 
772
+ if (isset($this->callbacks["end_document"])) {
773
+ $fs = $this->callbacks["end_document"];
774
+
775
+ foreach ($fs as $f) {
776
+ $canvas->page_script($f);
777
+ }
778
+ }
779
+
780
  // Clean up cached images
781
  if (!$this->options->getDebugKeepTemp()) {
782
  Cache::clear($this->options->getDebugPng());
804
  $this->restorePhpConfig();
805
  }
806
 
 
 
 
 
 
 
 
 
 
 
 
807
  /**
808
  * Writes the output buffer in the log file
809
  *
830
  file_put_contents($logOutputFile, $out);
831
  }
832
 
833
+ /**
834
+ * Add meta information to the PDF after rendering.
835
+ *
836
+ * @deprecated
837
+ */
838
+ public function add_info($label, $value)
839
+ {
840
+ $this->addInfo($label, $value);
841
+ }
842
+
843
+ /**
844
+ * Add meta information to the PDF after rendering.
845
+ *
846
+ * @param string $label Label of the value (Creator, Producer, etc.)
847
+ * @param string $value The text to set
848
+ */
849
+ public function addInfo(string $label, string $value): void
850
+ {
851
+ $this->canvas->add_info($label, $value);
852
+ }
853
+
854
  /**
855
  * Streams the PDF to the client.
856
  *
871
  {
872
  $this->setPhpConfig();
873
 
874
+ $this->canvas->stream($filename, $options);
 
 
 
875
 
876
  $this->restorePhpConfig();
877
  }
892
  {
893
  $this->setPhpConfig();
894
 
895
+ $output = $this->canvas->output($options);
 
 
 
 
 
896
 
897
  $this->restorePhpConfig();
898
 
966
  /**
967
  * Sets the paper size & orientation
968
  *
969
+ * @param string|float[] $size 'letter', 'legal', 'A4', etc. {@link Dompdf\Adapter\CPDF::$PAPER_SIZES}
970
  * @param string $orientation 'portrait' or 'landscape'
971
  * @return $this
972
  */
980
  /**
981
  * Gets the paper size
982
  *
983
+ * @return float[] A four-element float array
 
984
  */
985
+ public function getPaperSize()
986
  {
987
+ $paper = $this->paperSize;
988
+ $orientation = $this->paperOrientation;
989
+
990
+ if (is_array($paper)) {
991
+ $size = array_map("floatval", $paper);
992
  } else {
993
+ $paper = strtolower($paper);
994
+ $size = CPDF::$PAPER_SIZES[$paper] ?? CPDF::$PAPER_SIZES["letter"];
995
+ }
996
+
997
+ if (strtolower($orientation) === "landscape") {
998
+ [$size[2], $size[3]] = [$size[3], $size[2]];
999
  }
1000
+
1001
+ return $size;
1002
  }
1003
 
1004
  /**
1231
  }
1232
 
1233
  /**
1234
+ * Set a custom `Canvas` instance to render the document to.
1235
+ *
1236
+ * Be aware that the instance will be replaced on render if the document
1237
+ * defines a paper size different from the canvas.
1238
+ *
1239
  * @param Canvas $canvas
1240
  * @return $this
1241
  */
1332
  }
1333
 
1334
  $this->options = $options;
1335
+ $fontMetrics = $this->fontMetrics;
1336
  if (isset($fontMetrics)) {
1337
  $fontMetrics->setOptions($options);
1338
  }
1368
 
1369
  /**
1370
  * @param array $callbacks the set of callbacks to set
1371
+ * @return $this
1372
  * @deprecated
1373
  */
1374
  public function set_callbacks($callbacks)
1375
  {
1376
+ return $this->setCallbacks($callbacks);
1377
  }
1378
 
1379
  /**
1380
+ * Define callbacks that allow modifying the document during render.
1381
  *
1382
  * The callbacks array should contain arrays with `event` set to a callback
1383
  * event name and `f` set to a function or any other callable.
1388
  * * `end_frame`: called after frame rendering is complete
1389
  * * `begin_page_render`: called before a page is rendered
1390
  * * `end_page_render`: called after page rendering is complete
1391
+ * * `end_document`: called for every page after rendering is complete
1392
  *
1393
+ * The function `f` receives three arguments `Frame $frame`, `Canvas $canvas`,
1394
+ * and `FontMetrics $fontMetrics` for all events but `end_document`. For
1395
+ * `end_document`, the function receives four arguments `int $pageNumber`,
1396
+ * `int $pageCount`, `Canvas $canvas`, and `FontMetrics $fontMetrics` instead.
1397
  *
1398
+ * @param array $callbacks The set of callbacks to set.
1399
+ * @return $this
1400
  */
1401
+ public function setCallbacks(array $callbacks): self
1402
  {
1403
+ $this->callbacks = [];
1404
+
1405
+ foreach ($callbacks as $c) {
1406
+ if (is_array($c) && isset($c["event"]) && isset($c["f"])) {
1407
+ $event = $c["event"];
1408
+ $f = $c["f"];
1409
+ if (is_string($event) && is_callable($f)) {
1410
+ $this->callbacks[$event][] = $f;
 
1411
  }
1412
  }
1413
  }
1414
+
1415
+ return $this;
1416
  }
1417
 
1418
  /**
vendor/dompdf/dompdf/src/FontMetrics.php CHANGED
@@ -25,7 +25,7 @@ use FontLib\Font;
25
  class FontMetrics
26
  {
27
  /**
28
- * Name of the font cache file
29
  *
30
  * This file must be writable by the webserver process only to update it
31
  * with save_font_families() after adding the .afm file references of a new font family
@@ -33,13 +33,8 @@ class FontMetrics
33
  * This is typically done only from command line with load_font.php on converting
34
  * ttf fonts to ufm with php-font-lib.
35
  */
36
- const CACHE_FILE = "dompdf_font_family_cache.php";
37
 
38
- /**
39
- * @var Canvas
40
- * @deprecated
41
- */
42
- protected $pdf;
43
 
44
  /**
45
  * Underlying {@link Canvas} object to perform text size calculations
@@ -49,13 +44,25 @@ class FontMetrics
49
  protected $canvas;
50
 
51
  /**
52
- * Array of font family names to font files
 
 
 
 
 
 
 
53
  *
54
- * Usually cached by the {@link load_font.php} script
 
 
 
 
 
55
  *
56
  * @var array
57
  */
58
- protected $fontLookup = [];
59
 
60
  /**
61
  * @var Options
@@ -84,29 +91,14 @@ class FontMetrics
84
  * Saves the stored font family cache
85
  *
86
  * The name and location of the cache file are determined by {@link
87
- * FontMetrics::CACHE_FILE}. This file should be writable by the
88
  * webserver process.
89
  *
90
  * @see FontMetrics::loadFontFamilies()
91
  */
92
  public function saveFontFamilies()
93
  {
94
- // replace the path to the DOMPDF font directories with the corresponding constants (allows for more portability)
95
- $cacheData = sprintf("<?php return function (%s, %s) {%s", '$fontDir', '$rootDir', PHP_EOL);
96
- $cacheData .= sprintf("return array (%s", PHP_EOL);
97
- foreach ($this->fontLookup as $family => $variants) {
98
- $cacheData .= sprintf(" '%s' => array(%s", addslashes($family), PHP_EOL);
99
- foreach ($variants as $variant => $path) {
100
- $path = sprintf("'%s'", $path);
101
- $path = str_replace('\'' . $this->options->getFontDir(), '$fontDir . \'', $path);
102
- $path = str_replace('\'' . $this->options->getRootDir(), '$rootDir . \'', $path);
103
- $cacheData .= sprintf(" '%s' => %s,%s", $variant, $path, PHP_EOL);
104
- }
105
- $cacheData .= sprintf(" ),%s", PHP_EOL);
106
- }
107
- $cacheData .= ");" . PHP_EOL;
108
- $cacheData .= "}; ?>";
109
- file_put_contents($this->getCacheFile(), $cacheData);
110
  }
111
 
112
  /**
@@ -124,33 +116,45 @@ class FontMetrics
124
  */
125
  public function loadFontFamilies()
126
  {
127
- $fontDir = $this->options->getFontDir();
128
- $rootDir = $this->options->getRootDir();
129
-
130
- // FIXME: temporarily define constants for cache files <= v0.6.2
131
- if (!defined("DOMPDF_DIR")) { define("DOMPDF_DIR", $rootDir); }
132
- if (!defined("DOMPDF_FONT_DIR")) { define("DOMPDF_FONT_DIR", $fontDir); }
133
-
134
- $file = $rootDir . "/lib/fonts/dompdf_font_family_cache.dist.php";
135
- $distFontsClosure = require $file;
136
- $distFonts = is_array($distFontsClosure) ? $distFontsClosure : $distFontsClosure($rootDir);
137
- if (!is_readable($this->getCacheFile())) {
138
- $this->fontLookup = $distFonts;
139
- return;
140
- }
141
 
142
- $cacheDataClosure = require $this->getCacheFile();
143
- $cacheData = is_array($cacheDataClosure) ? $cacheDataClosure : $cacheDataClosure($fontDir, $rootDir);
 
 
 
 
144
 
145
- $this->fontLookup = [];
146
- if (is_array($this->fontLookup)) {
147
- foreach ($cacheData as $key => $value) {
148
- $this->fontLookup[stripslashes($key)] = $value;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
149
  }
150
  }
151
-
152
- // Merge provided fonts
153
- $this->fontLookup += $distFonts;
154
  }
155
 
156
  /**
@@ -183,7 +187,6 @@ class FontMetrics
183
 
184
  $styleString = $this->getType("{$style['weight']} {$style['style']}");
185
 
186
- $fontDir = $this->options->getFontDir();
187
  $remoteHash = md5($remoteFile);
188
 
189
  $prefix = $fontname . "_" . $styleString;
@@ -199,49 +202,30 @@ class FontMetrics
199
  $prefix = preg_replace("[\W]", "_", $prefix);
200
  $prefix = preg_replace("/[^-_\w]+/", "", $prefix);
201
 
202
- $localFile = $fontDir . "/" . $prefix . "_" . $remoteHash;
 
203
 
204
- if (isset($entry[$styleString]) && $localFile == $entry[$styleString]) {
205
  return true;
206
  }
207
 
208
- $cacheEntry = $localFile;
209
 
210
- $entry[$styleString] = $cacheEntry;
211
 
212
  // Download the remote file
213
  [$protocol] = Helpers::explode_url($remoteFile);
214
- if (!$this->options->isRemoteEnabled() && ($protocol !== "" && $protocol !== "file://")) {
215
- Helpers::record_warnings(E_USER_WARNING, "Remote font resource $remoteFile referenced, but remote file download is disabled.", __FILE__, __LINE__);
216
- return false;
217
  }
218
- if ($protocol === "" || $protocol === "file://") {
219
- $realfile = realpath($remoteFile);
220
-
221
- $rootDir = realpath($this->options->getRootDir());
222
- if (strpos($realfile, $rootDir) !== 0) {
223
- $chroot = $this->options->getChroot();
224
- $chrootValid = false;
225
- foreach ($chroot as $chrootPath) {
226
- $chrootPath = realpath($chrootPath);
227
- if ($chrootPath !== false && strpos($realfile, $chrootPath) === 0) {
228
- $chrootValid = true;
229
- break;
230
- }
231
- }
232
- if ($chrootValid !== true) {
233
- Helpers::record_warnings(E_USER_WARNING, "Permission denied on $remoteFile. The file could not be found under the paths specified by Options::chroot.", __FILE__, __LINE__);
234
- return false;
235
- }
236
- }
237
 
238
- if (!$realfile) {
239
- Helpers::record_warnings(E_USER_WARNING, "File '$realfile' not found.", __FILE__, __LINE__);
240
- return false;
 
241
  }
242
-
243
- $remoteFile = $realfile;
244
  }
 
245
  list($remoteFileContent, $http_response_header) = @Helpers::getFileContent($remoteFile, $context);
246
  if ($remoteFileContent === null) {
247
  return false;
@@ -257,33 +241,33 @@ class FontMetrics
257
  return false;
258
  }
259
 
260
- switch ($font->getFontType()) {
261
- case "TrueType":
262
- default:
263
- $localFile .= ".ttf";
264
- break;
265
- }
266
-
267
  $font->parse();
268
- $font->saveAdobeFontMetrics("$cacheEntry.ufm");
269
  $font->close();
270
 
271
  unlink($localTempFile);
272
 
273
- if ( !file_exists("$cacheEntry.ufm") ) {
274
  return false;
275
  }
276
 
 
 
 
 
 
 
 
 
277
  // Save the changes
278
- file_put_contents($localFile, $remoteFileContent);
279
 
280
- if ( !file_exists($localFile) ) {
281
- unlink("$cacheEntry.ufm");
282
  return false;
283
  }
284
 
285
  $this->setFontFamily($fontname, $entry);
286
- $this->saveFontFamilies();
287
 
288
  return true;
289
  }
@@ -306,16 +290,15 @@ class FontMetrics
306
  /**
307
  * Calculates text size, in points
308
  *
309
- * @param string $text the text to be sized
310
- * @param string $font the desired font
311
- * @param float $size the desired font size
312
- * @param float $wordSpacing
313
- * @param float $charSpacing
314
  *
315
- * @internal param float $spacing word spacing, if any
316
  * @return float
317
  */
318
- public function getTextWidth($text, $font, $size, $wordSpacing = 0.0, $charSpacing = 0.0)
319
  {
320
  // @todo Make sure this cache is efficient before enabling it
321
  static $cache = [];
@@ -359,12 +342,12 @@ class FontMetrics
359
  /**
360
  * Calculates font height, in points
361
  *
362
- * @param string $font
363
- * @param float $size
364
  *
365
  * @return float
366
  */
367
- public function getFontHeight($font, $size)
368
  {
369
  return $this->canvas->get_font_height($font, $size);
370
  }
@@ -372,12 +355,12 @@ class FontMetrics
372
  /**
373
  * Calculates font baseline, in points
374
  *
375
- * @param string $font
376
- * @param float $size
377
  *
378
  * @return float
379
  */
380
- public function getFontBaseline($font, $size)
381
  {
382
  return $this->canvas->get_font_baseline($font, $size);
383
  }
@@ -400,10 +383,10 @@ class FontMetrics
400
  * ({@link Options::defaultFont}) is used. The font file returned
401
  * is the absolute pathname to the font file on the system.
402
  *
403
- * @param string $familyRaw
404
- * @param string $subtypeRaw
405
  *
406
- * @return string
407
  */
408
  public function getFont($familyRaw, $subtypeRaw = "normal")
409
  {
@@ -423,11 +406,12 @@ class FontMetrics
423
 
424
  $subtype = strtolower($subtypeRaw);
425
 
 
426
  if ($familyRaw) {
427
  $family = str_replace(["'", '"'], "", strtolower($familyRaw));
428
 
429
- if (isset($this->fontLookup[$family][$subtype])) {
430
- return $cache[$familyRaw][$subtypeRaw] = $this->fontLookup[$family][$subtype];
431
  }
432
 
433
  return null;
@@ -435,15 +419,15 @@ class FontMetrics
435
 
436
  $fallback_families = [strtolower($this->options->getDefaultFont()), "serif"];
437
  foreach ($fallback_families as $family) {
438
- if (isset($this->fontLookup[$family][$subtype])) {
439
- return $cache[$familyRaw][$subtypeRaw] = $this->fontLookup[$family][$subtype];
440
  }
441
 
442
- if (!isset($this->fontLookup[$family])) {
443
  continue;
444
  }
445
 
446
- $family = $this->fontLookup[$family];
447
 
448
  foreach ($family as $sub => $font) {
449
  if (strpos($subtype, $sub) !== false) {
@@ -486,9 +470,10 @@ class FontMetrics
486
  public function getFamily($family)
487
  {
488
  $family = str_replace(["'", '"'], "", mb_strtolower($family));
 
489
 
490
- if (isset($this->fontLookup[$family])) {
491
- return $this->fontLookup[$family];
492
  }
493
 
494
  return null;
@@ -547,7 +532,41 @@ class FontMetrics
547
  */
548
  public function getFontFamilies()
549
  {
550
- return $this->fontLookup;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
551
  }
552
 
553
  /**
@@ -566,15 +585,17 @@ class FontMetrics
566
  */
567
  public function setFontFamily($fontname, $entry)
568
  {
569
- $this->fontLookup[mb_strtolower($fontname)] = $entry;
 
 
570
  }
571
 
572
  /**
573
  * @return string
574
  */
575
- public function getCacheFile()
576
  {
577
- return $this->options->getFontDir() . '/' . self::CACHE_FILE;
578
  }
579
 
580
  /**
@@ -584,6 +605,7 @@ class FontMetrics
584
  public function setOptions(Options $options)
585
  {
586
  $this->options = $options;
 
587
  return $this;
588
  }
589
 
@@ -602,8 +624,6 @@ class FontMetrics
602
  public function setCanvas(Canvas $canvas)
603
  {
604
  $this->canvas = $canvas;
605
- // Still write deprecated pdf for now. It might be used by a parent class.
606
- $this->pdf = $canvas;
607
  return $this;
608
  }
609
 
25
  class FontMetrics
26
  {
27
  /**
28
+ * Name of the user font families file
29
  *
30
  * This file must be writable by the webserver process only to update it
31
  * with save_font_families() after adding the .afm file references of a new font family
33
  * This is typically done only from command line with load_font.php on converting
34
  * ttf fonts to ufm with php-font-lib.
35
  */
36
+ const USER_FONTS_FILE = "installed-fonts.json";
37
 
 
 
 
 
 
38
 
39
  /**
40
  * Underlying {@link Canvas} object to perform text size calculations
44
  protected $canvas;
45
 
46
  /**
47
+ * Array of bundled font family names to variants
48
+ *
49
+ * @var array
50
+ */
51
+ protected $bundledFonts = [];
52
+
53
+ /**
54
+ * Array of user defined font family names to variants
55
  *
56
+ * @var array
57
+ */
58
+ protected $userFonts = [];
59
+
60
+ /**
61
+ * combined list of all font families with absolute paths
62
  *
63
  * @var array
64
  */
65
+ protected $fontFamilies;
66
 
67
  /**
68
  * @var Options
91
  * Saves the stored font family cache
92
  *
93
  * The name and location of the cache file are determined by {@link
94
+ * FontMetrics::USER_FONTS_FILE}. This file should be writable by the
95
  * webserver process.
96
  *
97
  * @see FontMetrics::loadFontFamilies()
98
  */
99
  public function saveFontFamilies()
100
  {
101
+ file_put_contents($this->getUserFontsFilePath(), json_encode($this->userFonts, JSON_PRETTY_PRINT));
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
102
  }
103
 
104
  /**
116
  */
117
  public function loadFontFamilies()
118
  {
119
+ $file = $this->options->getRootDir() . "/lib/fonts/installed-fonts.dist.json";
120
+ $this->bundledFonts = json_decode(file_get_contents($file), true);
 
 
 
 
 
 
 
 
 
 
 
 
121
 
122
+ if (is_readable($this->getUserFontsFilePath())) {
123
+ $this->userFonts = json_decode(file_get_contents($this->getUserFontsFilePath()), true);
124
+ } else {
125
+ $this->loadFontFamiliesLegacy();
126
+ }
127
+ }
128
 
129
+ private function loadFontFamiliesLegacy()
130
+ {
131
+ $legacyCacheFile = $this->options->getFontDir() . '/dompdf_font_family_cache.php';
132
+ if (is_readable($legacyCacheFile)) {
133
+ $fontDir = $this->options->getFontDir();
134
+ $rootDir = $this->options->getRootDir();
135
+
136
+ if (!defined("DOMPDF_DIR")) { define("DOMPDF_DIR", $rootDir); }
137
+ if (!defined("DOMPDF_FONT_DIR")) { define("DOMPDF_FONT_DIR", $fontDir); }
138
+
139
+ $cacheDataClosure = require $legacyCacheFile;
140
+ $cacheData = is_array($cacheDataClosure) ? $cacheDataClosure : $cacheDataClosure($fontDir, $rootDir);
141
+ if (is_array($cacheData)) {
142
+ foreach ($cacheData as $family => $variants) {
143
+ if (!isset($this->bundledFonts[$family]) && is_array($variants)) {
144
+ foreach ($variants as $variant => $variantPath) {
145
+ $variantName = basename($variantPath);
146
+ $variantDir = dirname($variantPath);
147
+ if ($variantDir == $fontDir) {
148
+ $this->userFonts[$family][$variant] = $variantName;
149
+ } else {
150
+ $this->userFonts[$family][$variant] = $variantPath;
151
+ }
152
+ }
153
+ }
154
+ }
155
+ $this->saveFontFamilies();
156
  }
157
  }
 
 
 
158
  }
159
 
160
  /**
187
 
188
  $styleString = $this->getType("{$style['weight']} {$style['style']}");
189
 
 
190
  $remoteHash = md5($remoteFile);
191
 
192
  $prefix = $fontname . "_" . $styleString;
202
  $prefix = preg_replace("[\W]", "_", $prefix);
203
  $prefix = preg_replace("/[^-_\w]+/", "", $prefix);
204
 
205
+ $localFile = $prefix . "_" . $remoteHash;
206
+ $localFilePath = $this->getOptions()->getFontDir() . "/" . $localFile;
207
 
208
+ if (isset($entry[$styleString]) && $localFilePath == $entry[$styleString]) {
209
  return true;
210
  }
211
 
 
212
 
213
+ $entry[$styleString] = $localFile;
214
 
215
  // Download the remote file
216
  [$protocol] = Helpers::explode_url($remoteFile);
217
+ $allowed_protocols = $this->options->getAllowedProtocols();
218
+ if (!array_key_exists($protocol, $allowed_protocols)) {
219
+ Helpers::record_warnings(E_USER_WARNING, "Permission denied on $remoteFile. The communication protocol is not supported.", __FILE__, __LINE__);
220
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
221
 
222
+ foreach ($allowed_protocols[$protocol]["rules"] as $rule) {
223
+ [$result, $message] = $rule($remoteFile);
224
+ if ($result !== true) {
225
+ Helpers::record_warnings(E_USER_WARNING, "Error loading $remoteFile: $message", __FILE__, __LINE__);
226
  }
 
 
227
  }
228
+
229
  list($remoteFileContent, $http_response_header) = @Helpers::getFileContent($remoteFile, $context);
230
  if ($remoteFileContent === null) {
231
  return false;
241
  return false;
242
  }
243
 
 
 
 
 
 
 
 
244
  $font->parse();
245
+ $font->saveAdobeFontMetrics("$localFilePath.ufm");
246
  $font->close();
247
 
248
  unlink($localTempFile);
249
 
250
+ if ( !file_exists("$localFilePath.ufm") ) {
251
  return false;
252
  }
253
 
254
+ $fontExtension = ".ttf";
255
+ switch ($font->getFontType()) {
256
+ case "TrueType":
257
+ default:
258
+ $fontExtension = ".ttf";
259
+ break;
260
+ }
261
+
262
  // Save the changes
263
+ file_put_contents($localFilePath.$fontExtension, $remoteFileContent);
264
 
265
+ if ( !file_exists($localFilePath.$fontExtension) ) {
266
+ unlink("$localFilePath.ufm");
267
  return false;
268
  }
269
 
270
  $this->setFontFamily($fontname, $entry);
 
271
 
272
  return true;
273
  }
290
  /**
291
  * Calculates text size, in points
292
  *
293
+ * @param string $text The text to be sized
294
+ * @param string $font The font file to use
295
+ * @param float $size The font size, in points
296
+ * @param float $wordSpacing Word spacing, if any
297
+ * @param float $charSpacing Char spacing, if any
298
  *
 
299
  * @return float
300
  */
301
+ public function getTextWidth(string $text, $font, float $size, float $wordSpacing = 0.0, float $charSpacing = 0.0): float
302
  {
303
  // @todo Make sure this cache is efficient before enabling it
304
  static $cache = [];
342
  /**
343
  * Calculates font height, in points
344
  *
345
+ * @param string $font The font file to use
346
+ * @param float $size The font size, in points
347
  *
348
  * @return float
349
  */
350
+ public function getFontHeight($font, float $size): float
351
  {
352
  return $this->canvas->get_font_height($font, $size);
353
  }
355
  /**
356
  * Calculates font baseline, in points
357
  *
358
+ * @param string $font The font file to use
359
+ * @param float $size The font size, in points
360
  *
361
  * @return float
362
  */
363
+ public function getFontBaseline($font, float $size): float
364
  {
365
  return $this->canvas->get_font_baseline($font, $size);
366
  }
383
  * ({@link Options::defaultFont}) is used. The font file returned
384
  * is the absolute pathname to the font file on the system.
385
  *
386
+ * @param string|null $familyRaw
387
+ * @param string $subtypeRaw
388
  *
389
+ * @return string|null
390
  */
391
  public function getFont($familyRaw, $subtypeRaw = "normal")
392
  {
406
 
407
  $subtype = strtolower($subtypeRaw);
408
 
409
+ $families = $this->getFontFamilies();
410
  if ($familyRaw) {
411
  $family = str_replace(["'", '"'], "", strtolower($familyRaw));
412
 
413
+ if (isset($families[$family][$subtype])) {
414
+ return $cache[$familyRaw][$subtypeRaw] = $families[$family][$subtype];
415
  }
416
 
417
  return null;
419
 
420
  $fallback_families = [strtolower($this->options->getDefaultFont()), "serif"];
421
  foreach ($fallback_families as $family) {
422
+ if (isset($families[$family][$subtype])) {
423
+ return $cache[$familyRaw][$subtypeRaw] = $families[$family][$subtype];
424
  }
425
 
426
+ if (!isset($families[$family])) {
427
  continue;
428
  }
429
 
430
+ $family = $families[$family];
431
 
432
  foreach ($family as $sub => $font) {
433
  if (strpos($subtype, $sub) !== false) {
470
  public function getFamily($family)
471
  {
472
  $family = str_replace(["'", '"'], "", mb_strtolower($family));
473
+ $families = $this->getFontFamilies();
474
 
475
+ if (isset($families[$family])) {
476
+ return $families[$family];
477
  }
478
 
479
  return null;
532
  */
533
  public function getFontFamilies()
534
  {
535
+ if (!isset($this->fontFamilies)) {
536
+ $this->setFontFamilies();
537
+ }
538
+ return $this->fontFamilies;
539
+ }
540
+
541
+ /**
542
+ * Convert loaded fonts to font lookup table
543
+ *
544
+ * @return array
545
+ */
546
+ public function setFontFamilies()
547
+ {
548
+ $fontFamilies = [];
549
+ if (isset($this->bundledFonts) && is_array($this->bundledFonts)) {
550
+ foreach ($this->bundledFonts as $family => $variants) {
551
+ if (!isset($fontFamilies[$family])) {
552
+ $fontFamilies[$family] = array_map(function ($variant) {
553
+ return $this->getOptions()->getRootDir() . '/lib/fonts/' . $variant;
554
+ }, $variants);
555
+ }
556
+ }
557
+ }
558
+ if (isset($this->userFonts) && is_array($this->userFonts)) {
559
+ foreach ($this->userFonts as $family => $variants) {
560
+ $fontFamilies[$family] = array_map(function ($variant) {
561
+ $variantName = basename($variant);
562
+ if ($variantName === $variant) {
563
+ return $this->getOptions()->getFontDir() . '/' . $variant;
564
+ }
565
+ return $variant;
566
+ }, $variants);
567
+ }
568
+ }
569
+ $this->fontFamilies = $fontFamilies;
570
  }
571
 
572
  /**
585
  */
586
  public function setFontFamily($fontname, $entry)
587
  {
588
+ $this->userFonts[mb_strtolower($fontname)] = $entry;
589
+ $this->saveFontFamilies();
590
+ unset($this->fontFamilies);
591
  }
592
 
593
  /**
594
  * @return string
595
  */
596
+ public function getUserFontsFilePath()
597
  {
598
+ return $this->options->getFontDir() . '/' . self::USER_FONTS_FILE;
599
  }
600
 
601
  /**
605
  public function setOptions(Options $options)
606
  {
607
  $this->options = $options;
608
+ unset($this->fontFamilies);
609
  return $this;
610
  }
611
 
624
  public function setCanvas(Canvas $canvas)
625
  {
626
  $this->canvas = $canvas;
 
 
627
  return $this;
628
  }
629
 
vendor/dompdf/dompdf/src/Frame.php CHANGED
@@ -3,7 +3,7 @@
3
  namespace Dompdf;
4
 
5
  use Dompdf\Css\Style;
6
- use Dompdf\Frame\FrameList;
7
 
8
  /**
9
  * @package dompdf
@@ -57,14 +57,6 @@ class Frame
57
  */
58
  protected $_style;
59
 
60
- /**
61
- * This frame's original style. Needed for cases where frames are
62
- * split across pages.
63
- *
64
- * @var Style
65
- */
66
- protected $_original_style;
67
-
68
  /**
69
  * This frame's parent in the document tree.
70
  *
@@ -72,13 +64,6 @@ class Frame
72
  */
73
  protected $_parent;
74
 
75
- /**
76
- * This frame's children
77
- *
78
- * @var Frame[]
79
- */
80
- protected $_frame_list;
81
-
82
  /**
83
  * This frame's first child. All children are handled as a
84
  * doubly-linked list.
@@ -133,7 +118,7 @@ class Frame
133
  /**
134
  * This frame's decorator
135
  *
136
- * @var \Dompdf\FrameDecorator\AbstractFrameDecorator
137
  */
138
  protected $_decorator;
139
 
@@ -161,13 +146,6 @@ class Frame
161
  */
162
  public $_float_next_line = false;
163
 
164
- /**
165
- * Whether the frame is a split-off frame
166
- *
167
- * @var bool
168
- */
169
- public $_splitted;
170
-
171
  /**
172
  * @var int
173
  */
@@ -188,7 +166,6 @@ class Frame
188
  $this->_prev_sibling = $this->_next_sibling = null;
189
 
190
  $this->_style = null;
191
- $this->_original_style = null;
192
 
193
  $this->_containing_block = [
194
  "x" => null,
@@ -301,13 +278,8 @@ class Frame
301
  $this->_parent->get_node()->removeChild($this->_node);
302
  }
303
 
304
- $this->_style->dispose();
305
  $this->_style = null;
306
  unset($this->_style);
307
-
308
- $this->_original_style->dispose();
309
- $this->_original_style = null;
310
- unset($this->_original_style);
311
  }
312
 
313
  /**
@@ -323,9 +295,7 @@ class Frame
323
  $this->_containing_block["w"] = null;
324
  $this->_containing_block["h"] = null;
325
 
326
- $this->_style = null;
327
- unset($this->_style);
328
- $this->_style = clone $this->_original_style;
329
  }
330
 
331
  /**
@@ -353,11 +323,12 @@ class Frame
353
  }
354
 
355
  /**
 
356
  * @return Style
357
  */
358
  public function get_original_style()
359
  {
360
- return $this->_original_style;
361
  }
362
 
363
  /**
@@ -369,7 +340,7 @@ class Frame
369
  }
370
 
371
  /**
372
- * @return \Dompdf\FrameDecorator\AbstractFrameDecorator
373
  */
374
  public function get_decorator()
375
  {
@@ -409,17 +380,11 @@ class Frame
409
  }
410
 
411
  /**
412
- * @return FrameList|Frame[]
413
  */
414
- public function get_children()
415
  {
416
- if (isset($this->_frame_list)) {
417
- return $this->_frame_list;
418
- }
419
-
420
- $this->_frame_list = new FrameList($this);
421
-
422
- return $this->_frame_list;
423
  }
424
 
425
  // Layout property accessors
@@ -650,11 +615,11 @@ class Frame
650
  }
651
 
652
  /**
653
- * @param null $opacity
654
  *
655
  * @return float
656
  */
657
- public function get_opacity($opacity = null)
658
  {
659
  if ($opacity !== null) {
660
  $this->set_opacity($opacity);
@@ -692,18 +657,14 @@ class Frame
692
  /**
693
  * @param Style $style
694
  */
695
- public function set_style(Style $style)
696
  {
697
- if (is_null($this->_style)) {
698
- $this->_original_style = clone $style;
699
- }
700
-
701
- //$style->set_frame($this);
702
  $this->_style = $style;
703
  }
704
 
705
  /**
706
- * @param \Dompdf\FrameDecorator\AbstractFrameDecorator $decorator
707
  */
708
  public function set_decorator(FrameDecorator\AbstractFrameDecorator $decorator)
709
  {
@@ -761,12 +722,12 @@ class Frame
761
  }
762
 
763
  /**
764
- * @param $opacity
765
  */
766
- public function set_opacity($opacity)
767
  {
768
  $parent = $this->get_parent();
769
- $base_opacity = (($parent && $parent->_opacity !== null) ? $parent->_opacity : 1.0);
770
  $this->_opacity = $base_opacity * $opacity;
771
  }
772
 
@@ -833,7 +794,7 @@ class Frame
833
  *
834
  * @return bool
835
  */
836
- public function is_text_node()
837
  {
838
  if (isset($this->_is_cache["text_node"])) {
839
  return $this->_is_cache["text_node"];
@@ -845,21 +806,21 @@ class Frame
845
  /**
846
  * @return bool
847
  */
848
- public function is_positionned()
849
  {
850
- if (isset($this->_is_cache["positionned"])) {
851
- return $this->_is_cache["positionned"];
852
  }
853
 
854
  $position = $this->get_style()->position;
855
 
856
- return $this->_is_cache["positionned"] = in_array($position, Style::$POSITIONNED_TYPES, true);
857
  }
858
 
859
  /**
860
  * @return bool
861
  */
862
- public function is_absolute()
863
  {
864
  if (isset($this->_is_cache["absolute"])) {
865
  return $this->_is_cache["absolute"];
@@ -873,13 +834,13 @@ class Frame
873
  *
874
  * @return bool
875
  */
876
- public function is_block()
877
  {
878
  if (isset($this->_is_cache["block"])) {
879
  return $this->_is_cache["block"];
880
  }
881
 
882
- return $this->_is_cache["block"] = in_array($this->get_style()->display, Style::$BLOCK_TYPES, true);
883
  }
884
 
885
  /**
@@ -914,23 +875,10 @@ class Frame
914
  return $this->_is_cache["inline_level"] = in_array($display, Style::INLINE_LEVEL_TYPES, true);
915
  }
916
 
917
- /**
918
- * @deprecated
919
- * @return bool
920
- */
921
- public function is_inline_block()
922
- {
923
- if (isset($this->_is_cache["inline_block"])) {
924
- return $this->_is_cache["inline_block"];
925
- }
926
-
927
- return $this->_is_cache["inline_block"] = ($this->get_style()->display === "inline-block");
928
- }
929
-
930
  /**
931
  * @return bool
932
  */
933
- public function is_in_flow()
934
  {
935
  if (isset($this->_is_cache["in_flow"])) {
936
  return $this->_is_cache["in_flow"];
@@ -942,7 +890,7 @@ class Frame
942
  /**
943
  * @return bool
944
  */
945
- public function is_pre()
946
  {
947
  if (isset($this->_is_cache["pre"])) {
948
  return $this->_is_cache["pre"];
@@ -956,7 +904,7 @@ class Frame
956
  /**
957
  * @return bool
958
  */
959
- public function is_table()
960
  {
961
  if (isset($this->_is_cache["table"])) {
962
  return $this->_is_cache["table"];
@@ -964,7 +912,7 @@ class Frame
964
 
965
  $display = $this->get_style()->display;
966
 
967
- return $this->_is_cache["table"] = in_array($display, Style::$TABLE_TYPES, true);
968
  }
969
 
970
 
3
  namespace Dompdf;
4
 
5
  use Dompdf\Css\Style;
6
+ use Dompdf\Frame\FrameListIterator;
7
 
8
  /**
9
  * @package dompdf
57
  */
58
  protected $_style;
59
 
 
 
 
 
 
 
 
 
60
  /**
61
  * This frame's parent in the document tree.
62
  *
64
  */
65
  protected $_parent;
66
 
 
 
 
 
 
 
 
67
  /**
68
  * This frame's first child. All children are handled as a
69
  * doubly-linked list.
118
  /**
119
  * This frame's decorator
120
  *
121
+ * @var FrameDecorator\AbstractFrameDecorator
122
  */
123
  protected $_decorator;
124
 
146
  */
147
  public $_float_next_line = false;
148
 
 
 
 
 
 
 
 
149
  /**
150
  * @var int
151
  */
166
  $this->_prev_sibling = $this->_next_sibling = null;
167
 
168
  $this->_style = null;
 
169
 
170
  $this->_containing_block = [
171
  "x" => null,
278
  $this->_parent->get_node()->removeChild($this->_node);
279
  }
280
 
 
281
  $this->_style = null;
282
  unset($this->_style);
 
 
 
 
283
  }
284
 
285
  /**
295
  $this->_containing_block["w"] = null;
296
  $this->_containing_block["h"] = null;
297
 
298
+ $this->_style->reset();
 
 
299
  }
300
 
301
  /**
323
  }
324
 
325
  /**
326
+ * @deprecated
327
  * @return Style
328
  */
329
  public function get_original_style()
330
  {
331
+ return $this->_style;
332
  }
333
 
334
  /**
340
  }
341
 
342
  /**
343
+ * @return FrameDecorator\AbstractFrameDecorator
344
  */
345
  public function get_decorator()
346
  {
380
  }
381
 
382
  /**
383
+ * @return FrameListIterator
384
  */
385
+ public function get_children(): FrameListIterator
386
  {
387
+ return new FrameListIterator($this);
 
 
 
 
 
 
388
  }
389
 
390
  // Layout property accessors
615
  }
616
 
617
  /**
618
+ * @param float|null $opacity
619
  *
620
  * @return float
621
  */
622
+ public function get_opacity(?float $opacity = null): float
623
  {
624
  if ($opacity !== null) {
625
  $this->set_opacity($opacity);
657
  /**
658
  * @param Style $style
659
  */
660
+ public function set_style(Style $style): void
661
  {
662
+ // $style->set_frame($this);
 
 
 
 
663
  $this->_style = $style;
664
  }
665
 
666
  /**
667
+ * @param FrameDecorator\AbstractFrameDecorator $decorator
668
  */
669
  public function set_decorator(FrameDecorator\AbstractFrameDecorator $decorator)
670
  {
722
  }
723
 
724
  /**
725
+ * @param float $opacity
726
  */
727
+ public function set_opacity(float $opacity): void
728
  {
729
  $parent = $this->get_parent();
730
+ $base_opacity = $parent && $parent->_opacity !== null ? $parent->_opacity : 1.0;
731
  $this->_opacity = $base_opacity * $opacity;
732
  }
733
 
794
  *
795
  * @return bool
796
  */
797
+ public function is_text_node(): bool
798
  {
799
  if (isset($this->_is_cache["text_node"])) {
800
  return $this->_is_cache["text_node"];
806
  /**
807
  * @return bool
808
  */
809
+ public function is_positioned(): bool
810
  {
811
+ if (isset($this->_is_cache["positioned"])) {
812
+ return $this->_is_cache["positioned"];
813
  }
814
 
815
  $position = $this->get_style()->position;
816
 
817
+ return $this->_is_cache["positioned"] = in_array($position, Style::POSITIONED_TYPES, true);
818
  }
819
 
820
  /**
821
  * @return bool
822
  */
823
+ public function is_absolute(): bool
824
  {
825
  if (isset($this->_is_cache["absolute"])) {
826
  return $this->_is_cache["absolute"];
834
  *
835
  * @return bool
836
  */
837
+ public function is_block(): bool
838
  {
839
  if (isset($this->_is_cache["block"])) {
840
  return $this->_is_cache["block"];
841
  }
842
 
843
+ return $this->_is_cache["block"] = in_array($this->get_style()->display, Style::BLOCK_TYPES, true);
844
  }
845
 
846
  /**
875
  return $this->_is_cache["inline_level"] = in_array($display, Style::INLINE_LEVEL_TYPES, true);
876
  }
877
 
 
 
 
 
 
 
 
 
 
 
 
 
 
878
  /**
879
  * @return bool
880
  */
881
+ public function is_in_flow(): bool
882
  {
883
  if (isset($this->_is_cache["in_flow"])) {
884
  return $this->_is_cache["in_flow"];
890
  /**
891
  * @return bool
892
  */
893
+ public function is_pre(): bool
894
  {
895
  if (isset($this->_is_cache["pre"])) {
896
  return $this->_is_cache["pre"];
904
  /**
905
  * @return bool
906
  */
907
+ public function is_table(): bool
908
  {
909
  if (isset($this->_is_cache["table"])) {
910
  return $this->_is_cache["table"];
912
 
913
  $display = $this->get_style()->display;
914
 
915
+ return $this->_is_cache["table"] = in_array($display, Style::TABLE_TYPES, true);
916
  }
917
 
918
 
vendor/dompdf/dompdf/src/Frame/Factory.php CHANGED
@@ -190,7 +190,7 @@ class Factory
190
 
191
  // Handle nodeName
192
  if ($node->nodeName === "img") {
193
- $style->display = "-dompdf-image";
194
  $decorator = "Image";
195
  $reflower = "Image";
196
  }
@@ -239,7 +239,7 @@ class Factory
239
  }
240
 
241
  $new_style = $dompdf->getCss()->create_style();
242
- $new_style->display = "-dompdf-list-bullet";
243
  $new_style->inherit($style);
244
  $b_f->set_style($new_style);
245
 
190
 
191
  // Handle nodeName
192
  if ($node->nodeName === "img") {
193
+ $style->set_prop("display", "-dompdf-image");
194
  $decorator = "Image";
195
  $reflower = "Image";
196
  }
239
  }
240
 
241
  $new_style = $dompdf->getCss()->create_style();
242
+ $new_style->set_prop("display", "-dompdf-list-bullet");
243
  $new_style->inherit($style);
244
  $b_f->set_style($new_style);
245
 
vendor/dompdf/dompdf/src/Frame/FrameList.php DELETED
@@ -1,35 +0,0 @@
1
- <?php
2
- namespace Dompdf\Frame;
3
-
4
- use Dompdf\Frame;
5
- use IteratorAggregate;
6
-
7
- /**
8
- * Linked-list IteratorAggregate
9
- *
10
- * @access private
11
- * @package dompdf
12
- */
13
- class FrameList implements IteratorAggregate
14
- {
15
- /**
16
- * @var Frame
17
- */
18
- protected $_frame;
19
-
20
- /**
21
- * @param Frame $frame
22
- */
23
- function __construct($frame)
24
- {
25
- $this->_frame = $frame;
26
- }
27
-
28
- /**
29
- * @return FrameListIterator
30
- */
31
- function getIterator(): FrameListIterator
32
- {
33
- return new FrameListIterator($this->_frame);
34
- }
35
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
vendor/dompdf/dompdf/src/Frame/FrameTree.php CHANGED
@@ -9,6 +9,7 @@ use DOMXPath;
9
 
10
  use Dompdf\Exception;
11
  use Dompdf\Frame;
 
12
 
13
  /**
14
  * @package dompdf
@@ -27,7 +28,7 @@ use Dompdf\Frame;
27
  *
28
  * @package dompdf
29
  */
30
- class FrameTree
31
  {
32
  /**
33
  * Tags to ignore while parsing the tree
@@ -124,11 +125,22 @@ class FrameTree
124
  /**
125
  * Returns a post-order iterator for all frames in the tree
126
  *
127
- * @return FrameTreeList|Frame[]
 
128
  */
129
- public function get_frames()
130
  {
131
- return new FrameTreeList($this->_root);
 
 
 
 
 
 
 
 
 
 
132
  }
133
 
134
  /**
9
 
10
  use Dompdf\Exception;
11
  use Dompdf\Frame;
12
+ use IteratorAggregate;
13
 
14
  /**
15
  * @package dompdf
28
  *
29
  * @package dompdf
30
  */
31
+ class FrameTree implements IteratorAggregate
32
  {
33
  /**
34
  * Tags to ignore while parsing the tree
125
  /**
126
  * Returns a post-order iterator for all frames in the tree
127
  *
128
+ * @deprecated Iterate the tree directly instead
129
+ * @return FrameTreeIterator
130
  */
131
+ public function get_frames(): FrameTreeIterator
132
  {
133
+ return new FrameTreeIterator($this->_root);
134
+ }
135
+
136
+ /**
137
+ * Returns a post-order iterator for all frames in the tree
138
+ *
139
+ * @return FrameTreeIterator
140
+ */
141
+ public function getIterator(): FrameTreeIterator
142
+ {
143
+ return new FrameTreeIterator($this->_root);
144
  }
145
 
146
  /**
vendor/dompdf/dompdf/src/Frame/FrameTreeIterator.php CHANGED
@@ -70,10 +70,7 @@ class FrameTreeIterator implements Iterator
70
 
71
  public function next(): void
72
  {
73
- $b = end($this->_stack);
74
-
75
- // Pop last element
76
- unset($this->_stack[key($this->_stack)]);
77
  $this->_num++;
78
 
79
  // Push all children onto the stack in reverse order
70
 
71
  public function next(): void
72
  {
73
+ $b = array_pop($this->_stack);
 
 
 
74
  $this->_num++;
75
 
76
  // Push all children onto the stack in reverse order
vendor/dompdf/dompdf/src/Frame/FrameTreeList.php DELETED
@@ -1,35 +0,0 @@
1
- <?php
2
- namespace Dompdf\Frame;
3
-
4
- use IteratorAggregate;
5
- use Dompdf\Frame;
6
-
7
- /**
8
- * Pre-order IteratorAggregate
9
- *
10
- * @access private
11
- * @package dompdf
12
- */
13
- class FrameTreeList implements IteratorAggregate
14
- {
15
- /**
16
- * @var Frame
17
- */
18
- protected $_root;
19
-
20
- /**
21
- * @param Frame $root
22
- */
23
- public function __construct(Frame $root)
24
- {
25
- $this->_root = $root;
26
- }
27
-
28
- /**
29
- * @return FrameTreeIterator
30
- */
31
- public function getIterator(): FrameTreeIterator
32
- {
33
- return new FrameTreeIterator($this->_root);
34
- }
35
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
vendor/dompdf/dompdf/src/FrameDecorator/AbstractFrameDecorator.php CHANGED
@@ -6,13 +6,14 @@ use DOMElement;
6
  use DOMNode;
7
  use Dompdf\Helpers;
8
  use Dompdf\Dompdf;
 
9
  use Dompdf\Frame;
10
- use Dompdf\Frame\FrameTreeList;
11
  use Dompdf\Frame\Factory;
 
 
12
  use Dompdf\FrameReflower\AbstractFrameReflower;
13
  use Dompdf\Css\Style;
14
  use Dompdf\Positioner\AbstractPositioner;
15
- use Dompdf\Exception;
16
 
17
  /**
18
  * @package dompdf
@@ -84,7 +85,7 @@ abstract class AbstractFrameDecorator extends Frame
84
  *
85
  * @var AbstractFrameDecorator
86
  */
87
- private $_positionned_parent;
88
 
89
  /**
90
  * Cache for the get_parent while loop results
@@ -107,6 +108,13 @@ abstract class AbstractFrameDecorator extends Frame
107
  */
108
  public $is_split = false;
109
 
 
 
 
 
 
 
 
110
  /**
111
  * Class constructor
112
  *
@@ -158,7 +166,10 @@ abstract class AbstractFrameDecorator extends Frame
158
  function copy(DOMNode $node)
159
  {
160
  $frame = new Frame($node);
161
- $frame->set_style(clone $this->_frame->get_original_style());
 
 
 
162
 
163
  if ($node instanceof DOMElement && $node->hasAttribute("id")) {
164
  $node->setAttribute("data-dompdf-original-id", $node->getAttribute("id"));
@@ -177,7 +188,10 @@ abstract class AbstractFrameDecorator extends Frame
177
  {
178
  $node = $this->_frame->get_node()->cloneNode();
179
  $frame = new Frame($node);
180
- $frame->set_style(clone $this->_frame->get_original_style());
 
 
 
181
 
182
  if ($node instanceof DOMElement && $node->hasAttribute("id")) {
183
  $node->setAttribute("data-dompdf-original-id", $node->getAttribute("id"));
@@ -205,8 +219,8 @@ abstract class AbstractFrameDecorator extends Frame
205
  {
206
  $style = $this->get_style();
207
  $child_style = $style->get_stylesheet()->create_style();
 
208
  $child_style->inherit($style);
209
- $child_style->display = $display;
210
 
211
  $node = $this->get_node()->ownerDocument->createElement($node_name);
212
  $frame = new Frame($node);
@@ -228,7 +242,7 @@ abstract class AbstractFrameDecorator extends Frame
228
  // clear parent lookup caches
229
  $this->_cached_parent = null;
230
  $this->_block_parent = null;
231
- $this->_positionned_parent = null;
232
 
233
  // Reset all children
234
  foreach ($this->get_children() as $child) {
@@ -295,9 +309,12 @@ abstract class AbstractFrameDecorator extends Frame
295
  return $this->_frame->get_style();
296
  }
297
 
 
 
 
298
  function get_original_style()
299
  {
300
- return $this->_frame->get_original_style();
301
  }
302
 
303
  function get_containing_block($i = null)
@@ -348,7 +365,7 @@ abstract class AbstractFrameDecorator extends Frame
348
  $this->_frame->set_id($id);
349
  }
350
 
351
- function set_style(Style $style)
352
  {
353
  $this->_frame->set_style($style);
354
  }
@@ -539,11 +556,19 @@ abstract class AbstractFrameDecorator extends Frame
539
  }
540
 
541
  /**
542
- * @return FrameTreeList
543
  */
544
- function get_subtree()
545
  {
546
- return new FrameTreeList($this);
 
 
 
 
 
 
 
 
547
  }
548
 
549
  function set_positioner(AbstractPositioner $posn)
@@ -624,16 +649,16 @@ abstract class AbstractFrameDecorator extends Frame
624
  /**
625
  * @return AbstractFrameDecorator
626
  */
627
- function find_positionned_parent()
628
  {
629
  // Find our nearest relative positioned parent
630
- if (isset($this->_positionned_parent)) {
631
- return $this->_positionned_parent;
632
  }
633
 
634
  $p = $this->get_parent();
635
  while ($p) {
636
- if ($p->is_positionned()) {
637
  break;
638
  }
639
 
@@ -644,7 +669,7 @@ abstract class AbstractFrameDecorator extends Frame
644
  $p = $this->_root;
645
  }
646
 
647
- return $this->_positionned_parent = $p;
648
  }
649
 
650
  /**
@@ -671,36 +696,36 @@ abstract class AbstractFrameDecorator extends Frame
671
  }
672
 
673
  $this->revert_counter_increment();
 
674
  $node = $this->_frame->get_node();
675
  $split = $this->copy($node->cloneNode());
676
 
677
  $style = $this->_frame->get_style();
678
- $split_style = $split->get_original_style();
679
 
680
  // Truncate the box decoration at the split, except for the body
681
  if ($node->nodeName !== "body") {
682
- // Style reset on the first and second parts
683
- $style->margin_bottom = 0;
684
- $style->padding_bottom = 0;
685
- $style->border_bottom = 0;
686
- $style->border_bottom_left_radius = 0;
687
- $style->border_bottom_right_radius = 0;
688
-
689
- // second
690
- $split_style->margin_top = 0;
691
- $split_style->padding_top = 0;
692
- $split_style->border_top = 0;
693
- $split_style->border_top_left_radius = 0;
694
- $split_style->border_top_right_radius = 0;
695
  $split_style->page_break_before = "auto";
696
  }
697
 
698
- $split_style->text_indent = 0;
699
  $split_style->counter_reset = "none";
700
 
701
- $split->set_style(clone $split_style);
702
  $this->is_split = true;
703
- $split->_splitted = true;
704
  $split->_already_pushed = true;
705
 
706
  $this->get_parent()->insert_child_after($split, $this);
@@ -719,7 +744,7 @@ abstract class AbstractFrameDecorator extends Frame
719
  if (!$forced) {
720
  // Reset top margin in case of an unforced page break
721
  // https://www.w3.org/TR/CSS21/page.html#allowed-page-breaks
722
- $child->get_original_style()->margin_top = 0;
723
  }
724
 
725
  // Add $child and all following siblings to the new split node
@@ -873,9 +898,9 @@ abstract class AbstractFrameDecorator extends Frame
873
  /**
874
  * @param float $offset_x
875
  * @param float $offset_y
876
- * @param bool $ignore_self
877
  */
878
- final function move($offset_x, $offset_y, $ignore_self = false)
879
  {
880
  $this->_positioner->move($this, $offset_x, $offset_y, $ignore_self);
881
  }
6
  use DOMNode;
7
  use Dompdf\Helpers;
8
  use Dompdf\Dompdf;
9
+ use Dompdf\Exception;
10
  use Dompdf\Frame;
 
11
  use Dompdf\Frame\Factory;
12
+ use Dompdf\Frame\FrameListIterator;
13
+ use Dompdf\Frame\FrameTreeIterator;
14
  use Dompdf\FrameReflower\AbstractFrameReflower;
15
  use Dompdf\Css\Style;
16
  use Dompdf\Positioner\AbstractPositioner;
 
17
 
18
  /**
19
  * @package dompdf
85
  *
86
  * @var AbstractFrameDecorator
87
  */
88
+ private $_positioned_parent;
89
 
90
  /**
91
  * Cache for the get_parent while loop results
108
  */
109
  public $is_split = false;
110
 
111
+ /**
112
+ * Whether the frame is a split-off frame
113
+ *
114
+ * @var bool
115
+ */
116
+ public $is_split_off = false;
117
+
118
  /**
119
  * Class constructor
120
  *
166
  function copy(DOMNode $node)
167
  {
168
  $frame = new Frame($node);
169
+ $style = clone $this->_frame->get_style();
170
+
171
+ $style->reset();
172
+ $frame->set_style($style);
173
 
174
  if ($node instanceof DOMElement && $node->hasAttribute("id")) {
175
  $node->setAttribute("data-dompdf-original-id", $node->getAttribute("id"));
188
  {
189
  $node = $this->_frame->get_node()->cloneNode();
190
  $frame = new Frame($node);
191
+ $style = clone $this->_frame->get_style();
192
+
193
+ $style->reset();
194
+ $frame->set_style($style);
195
 
196
  if ($node instanceof DOMElement && $node->hasAttribute("id")) {
197
  $node->setAttribute("data-dompdf-original-id", $node->getAttribute("id"));
219
  {
220
  $style = $this->get_style();
221
  $child_style = $style->get_stylesheet()->create_style();
222
+ $child_style->set_prop("display", $display);
223
  $child_style->inherit($style);
 
224
 
225
  $node = $this->get_node()->ownerDocument->createElement($node_name);
226
  $frame = new Frame($node);
242
  // clear parent lookup caches
243
  $this->_cached_parent = null;
244
  $this->_block_parent = null;
245
+ $this->_positioned_parent = null;
246
 
247
  // Reset all children
248
  foreach ($this->get_children() as $child) {
309
  return $this->_frame->get_style();
310
  }
311
 
312
+ /**
313
+ * @deprecated
314
+ */
315
  function get_original_style()
316
  {
317
+ return $this->_frame->get_style();
318
  }
319
 
320
  function get_containing_block($i = null)
365
  $this->_frame->set_id($id);
366
  }
367
 
368
+ public function set_style(Style $style): void
369
  {
370
  $this->_frame->set_style($style);
371
  }
556
  }
557
 
558
  /**
559
+ * @return FrameListIterator<AbstractFrameDecorator>
560
  */
561
+ public function get_children(): FrameListIterator
562
  {
563
+ return new FrameListIterator($this);
564
+ }
565
+
566
+ /**
567
+ * @return FrameTreeIterator<AbstractFrameDecorator>
568
+ */
569
+ function get_subtree(): FrameTreeIterator
570
+ {
571
+ return new FrameTreeIterator($this);
572
  }
573
 
574
  function set_positioner(AbstractPositioner $posn)
649
  /**
650
  * @return AbstractFrameDecorator
651
  */
652
+ function find_positioned_parent()
653
  {
654
  // Find our nearest relative positioned parent
655
+ if (isset($this->_positioned_parent)) {
656
+ return $this->_positioned_parent;
657
  }
658
 
659
  $p = $this->get_parent();
660
  while ($p) {
661
+ if ($p->is_positioned()) {
662
  break;
663
  }
664
 
669
  $p = $this->_root;
670
  }
671
 
672
+ return $this->_positioned_parent = $p;
673
  }
674
 
675
  /**
696
  }
697
 
698
  $this->revert_counter_increment();
699
+
700
  $node = $this->_frame->get_node();
701
  $split = $this->copy($node->cloneNode());
702
 
703
  $style = $this->_frame->get_style();
704
+ $split_style = $split->get_style();
705
 
706
  // Truncate the box decoration at the split, except for the body
707
  if ($node->nodeName !== "body") {
708
+ // Clear bottom decoration of original frame
709
+ $style->margin_bottom = 0.0;
710
+ $style->padding_bottom = 0.0;
711
+ $style->border_bottom_width = 0.0;
712
+ $style->border_bottom_left_radius = 0.0;
713
+ $style->border_bottom_right_radius = 0.0;
714
+
715
+ // Clear top decoration of split frame
716
+ $split_style->margin_top = 0.0;
717
+ $split_style->padding_top = 0.0;
718
+ $split_style->border_top_width = 0.0;
719
+ $split_style->border_top_left_radius = 0.0;
720
+ $split_style->border_top_right_radius = 0.0;
721
  $split_style->page_break_before = "auto";
722
  }
723
 
724
+ $split_style->text_indent = 0.0;
725
  $split_style->counter_reset = "none";
726
 
 
727
  $this->is_split = true;
728
+ $split->is_split_off = true;
729
  $split->_already_pushed = true;
730
 
731
  $this->get_parent()->insert_child_after($split, $this);
744
  if (!$forced) {
745
  // Reset top margin in case of an unforced page break
746
  // https://www.w3.org/TR/CSS21/page.html#allowed-page-breaks
747
+ $child->get_style()->margin_top = 0.0;
748
  }
749
 
750
  // Add $child and all following siblings to the new split node
898
  /**
899
  * @param float $offset_x
900
  * @param float $offset_y
901
+ * @param bool $ignore_self
902
  */
903
+ final function move(float $offset_x, float $offset_y, bool $ignore_self = false): void
904
  {
905
  $this->_positioner->move($this, $offset_x, $offset_y, $ignore_self);
906
  }
vendor/dompdf/dompdf/src/FrameDecorator/Block.php CHANGED
@@ -114,7 +114,7 @@ class Block extends AbstractFrameDecorator
114
  * @param Frame $frame
115
  * @return LineBox|null
116
  */
117
- public function add_frame_to_line(Frame $frame)
118
  {
119
  $current_line = $this->_line_boxes[$this->_cl];
120
  $frame->set_containing_line($current_line);
@@ -203,7 +203,7 @@ class Block extends AbstractFrameDecorator
203
  /**
204
  * @param float $w
205
  */
206
- function increase_line_width($w)
207
  {
208
  $this->_line_boxes[$this->_cl]->w += $w;
209
  }
@@ -212,7 +212,7 @@ class Block extends AbstractFrameDecorator
212
  * @param float $val
213
  * @param Frame $frame
214
  */
215
- function maximize_line_height($val, Frame $frame)
216
  {
217
  if ($val > $this->_line_boxes[$this->_cl]->h) {
218
  $this->_line_boxes[$this->_cl]->tallest_frame = $frame;
@@ -223,7 +223,7 @@ class Block extends AbstractFrameDecorator
223
  /**
224
  * @param bool $br
225
  */
226
- function add_line(bool $br = false)
227
  {
228
  $line = $this->_line_boxes[$this->_cl];
229
 
114
  * @param Frame $frame
115
  * @return LineBox|null
116
  */
117
+ public function add_frame_to_line(Frame $frame): ?LineBox
118
  {
119
  $current_line = $this->_line_boxes[$this->_cl];
120
  $frame->set_containing_line($current_line);
203
  /**
204
  * @param float $w
205
  */
206
+ public function increase_line_width(float $w): void
207
  {
208
  $this->_line_boxes[$this->_cl]->w += $w;
209
  }
212
  * @param float $val
213
  * @param Frame $frame
214
  */
215
+ public function maximize_line_height(float $val, Frame $frame): void
216
  {
217
  if ($val > $this->_line_boxes[$this->_cl]->h) {
218
  $this->_line_boxes[$this->_cl]->tallest_frame = $frame;
223
  /**
224
  * @param bool $br
225
  */
226
+ public function add_line(bool $br = false): void
227
  {
228
  $line = $this->_line_boxes[$this->_cl];
229
 
vendor/dompdf/dompdf/src/FrameDecorator/Image.php CHANGED
@@ -57,15 +57,21 @@ class Image extends AbstractFrameDecorator
57
  $dompdf->getProtocol(),
58
  $dompdf->getBaseHost(),
59
  $dompdf->getBasePath(),
60
- $dompdf
61
  );
62
 
63
  if (Cache::is_broken($this->_image_url) &&
64
  $alt = $frame->get_node()->getAttribute("alt")
65
  ) {
 
66
  $style = $frame->get_style();
67
- $style->width = (4 / 3) * $dompdf->getFontMetrics()->getTextWidth($alt, $style->font_family, $style->font_size, $style->word_spacing);
68
- $style->height = $dompdf->getFontMetrics()->getFontHeight($style->font_family, $style->font_size);
 
 
 
 
 
69
  }
70
  }
71
 
57
  $dompdf->getProtocol(),
58
  $dompdf->getBaseHost(),
59
  $dompdf->getBasePath(),
60
+ $dompdf->getOptions()
61
  );
62
 
63
  if (Cache::is_broken($this->_image_url) &&
64
  $alt = $frame->get_node()->getAttribute("alt")
65
  ) {
66
+ $fontMetrics = $dompdf->getFontMetrics();
67
  $style = $frame->get_style();
68
+ $font = $style->font_family;
69
+ $size = $style->font_size;
70
+ $word_spacing = $style->word_spacing;
71
+ $letter_spacing = $style->letter_spacing;
72
+
73
+ $style->width = (4 / 3) * $fontMetrics->getTextWidth($alt, $font, $size, $word_spacing, $letter_spacing);
74
+ $style->height = $fontMetrics->getFontHeight($font, $size);
75
  }
76
  }
77
 
vendor/dompdf/dompdf/src/FrameDecorator/Inline.php CHANGED
@@ -70,18 +70,22 @@ class Inline extends AbstractFrameDecorator
70
  $split = $this->copy($node->cloneNode());
71
 
72
  $style = $this->_frame->get_style();
73
- $split_style = $split->get_original_style();
74
 
75
  // Unset the current node's right style properties
76
- $style->margin_right = 0;
77
- $style->padding_right = 0;
78
- $style->border_right_width = 0;
 
 
79
 
80
  // Unset the split node's left style properties since we don't want them
81
  // to propagate
82
- $split_style->margin_left = 0;
83
- $split_style->padding_left = 0;
84
- $split_style->border_left_width = 0;
 
 
85
 
86
  // If this is a generated node don't propagate the content style
87
  if ($split->get_node()->nodeName == "dompdf_generated") {
@@ -91,14 +95,12 @@ class Inline extends AbstractFrameDecorator
91
  //On continuation of inline element on next line,
92
  //don't repeat non-horizontally repeatable background images
93
  //See e.g. in testcase image_variants, long descriptions
94
- if (($url = $split->get_style()->background_image) && $url !== "none"
95
- && ($repeat = $split->get_style()->background_repeat) && $repeat !== "repeat" && $repeat !== "repeat-x"
96
  ) {
97
  $split_style->background_image = "none";
98
  }
99
 
100
- $split->set_style(clone $split_style);
101
-
102
  $this->get_parent()->insert_child_after($split, $this);
103
 
104
  // Add $child and all following siblings to the new split node
70
  $split = $this->copy($node->cloneNode());
71
 
72
  $style = $this->_frame->get_style();
73
+ $split_style = $split->get_style();
74
 
75
  // Unset the current node's right style properties
76
+ $style->margin_right = 0.0;
77
+ $style->padding_right = 0.0;
78
+ $style->border_right_width = 0.0;
79
+ $style->border_top_right_radius = 0.0;
80
+ $style->border_bottom_right_radius = 0.0;
81
 
82
  // Unset the split node's left style properties since we don't want them
83
  // to propagate
84
+ $split_style->margin_left = 0.0;
85
+ $split_style->padding_left = 0.0;
86
+ $split_style->border_left_width = 0.0;
87
+ $split_style->border_top_left_radius = 0.0;
88
+ $split_style->border_bottom_left_radius = 0.0;
89
 
90
  // If this is a generated node don't propagate the content style
91
  if ($split->get_node()->nodeName == "dompdf_generated") {
95
  //On continuation of inline element on next line,
96
  //don't repeat non-horizontally repeatable background images
97
  //See e.g. in testcase image_variants, long descriptions
98
+ if (($url = $style->background_image) && $url !== "none"
99
+ && ($repeat = $style->background_repeat) && $repeat !== "repeat" && $repeat !== "repeat-x"
100
  ) {
101
  $split_style->background_image = "none";
102
  }
103
 
 
 
104
  $this->get_parent()->insert_child_after($split, $this);
105
 
106
  // Add $child and all following siblings to the new split node
vendor/dompdf/dompdf/src/FrameDecorator/ListBullet.php CHANGED
@@ -39,21 +39,6 @@ class ListBullet extends AbstractFrameDecorator
39
  */
40
  public const MARKER_INDENT = 0.52;
41
 
42
- /**
43
- * @deprecated
44
- */
45
- const BULLET_PADDING = 1;
46
-
47
- /**
48
- * @deprecated
49
- */
50
- const BULLET_DESCENT = 0.3;
51
-
52
- /**
53
- * @deprecated
54
- */
55
- static $BULLET_TYPES = ["disc", "circle", "square"];
56
-
57
  /**
58
  * ListBullet constructor.
59
  * @param Frame $frame
39
  */
40
  public const MARKER_INDENT = 0.52;
41
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
42
  /**
43
  * ListBullet constructor.
44
  * @param Frame $frame
vendor/dompdf/dompdf/src/FrameDecorator/Page.php CHANGED
@@ -178,9 +178,7 @@ class Page extends AbstractFrameDecorator
178
  ) {
179
  // Prevent cascading splits
180
  $frame->split(null, true, true);
181
- // We have to grab the style again here because split() resets
182
- // $frame->style to the frame's original style.
183
- $frame->get_style()->page_break_before = "auto";
184
  $this->_page_full = true;
185
  $frame->_already_pushed = true;
186
 
@@ -472,7 +470,7 @@ class Page extends AbstractFrameDecorator
472
  $prev_group = $frame->get_parent()->get_prev_sibling();
473
 
474
  if ($prev_group
475
- && in_array($prev_group->get_style()->display, Table::$ROW_GROUPS, true)
476
  ) {
477
  $prev = $prev_group->get_last_child();
478
  }
@@ -511,7 +509,7 @@ class Page extends AbstractFrameDecorator
511
 
512
  return true;
513
  } else {
514
- if (in_array($display, Table::$ROW_GROUPS, true)) {
515
 
516
  // Disallow breaks at row-groups: only split at row boundaries
517
  return false;
@@ -640,7 +638,7 @@ class Page extends AbstractFrameDecorator
640
 
641
  if ($next->is_table() && !$iter->is_table()) {
642
  $this->_in_table++;
643
- } else if (!$next->is_table() && $iter->is_table()) {
644
  $this->_in_table--;
645
  }
646
 
178
  ) {
179
  // Prevent cascading splits
180
  $frame->split(null, true, true);
181
+ $style->page_break_before = "auto";
 
 
182
  $this->_page_full = true;
183
  $frame->_already_pushed = true;
184
 
470
  $prev_group = $frame->get_parent()->get_prev_sibling();
471
 
472
  if ($prev_group
473
+ && in_array($prev_group->get_style()->display, Table::ROW_GROUPS, true)
474
  ) {
475
  $prev = $prev_group->get_last_child();
476
  }
509
 
510
  return true;
511
  } else {
512
+ if (in_array($display, Table::ROW_GROUPS, true)) {
513
 
514
  // Disallow breaks at row-groups: only split at row boundaries
515
  return false;
638
 
639
  if ($next->is_table() && !$iter->is_table()) {
640
  $this->_in_table++;
641
+ } elseif (!$next->is_table() && $iter->is_table()) {
642
  $this->_in_table--;
643
  }
644
 
vendor/dompdf/dompdf/src/FrameDecorator/Table.php CHANGED
@@ -20,9 +20,12 @@ use Dompdf\Frame;
20
  */
21
  class Table extends AbstractFrameDecorator
22
  {
23
- public static $VALID_CHILDREN = Style::TABLE_INTERNAL_TYPES;
24
 
25
- public static $ROW_GROUPS = [
 
 
 
26
  "table-row-group",
27
  "table-header-group",
28
  "table-footer-group"
@@ -36,20 +39,6 @@ class Table extends AbstractFrameDecorator
36
  */
37
  protected $_cellmap;
38
 
39
- /**
40
- * The minimum width of the table, in pt
41
- *
42
- * @var float
43
- */
44
- protected $_min_width;
45
-
46
- /**
47
- * The maximum width of the table, in pt
48
- *
49
- * @var float
50
- */
51
- protected $_max_width;
52
-
53
  /**
54
  * Table header rows. Each table header is duplicated when a table
55
  * spans pages.
@@ -81,8 +70,6 @@ class Table extends AbstractFrameDecorator
81
  $this->_cellmap->set_layout_fixed(true);
82
  }
83
 
84
- $this->_min_width = null;
85
- $this->_max_width = null;
86
  $this->_headers = [];
87
  $this->_footers = [];
88
  }
@@ -91,8 +78,6 @@ class Table extends AbstractFrameDecorator
91
  {
92
  parent::reset();
93
  $this->_cellmap->reset();
94
- $this->_min_width = null;
95
- $this->_max_width = null;
96
  $this->_headers = [];
97
  $this->_footers = [];
98
  $this->_reflower->reset();
@@ -134,7 +119,7 @@ class Table extends AbstractFrameDecorator
134
 
135
  parent::split($first_header, $page_break, $forced);
136
 
137
- } elseif (in_array($child->get_style()->display, self::$ROW_GROUPS, true)) {
138
 
139
  // Individual rows should have already been handled
140
  parent::split($child, $page_break, $forced);
@@ -191,46 +176,6 @@ class Table extends AbstractFrameDecorator
191
  return $this->_cellmap;
192
  }
193
 
194
- /**
195
- * Return the minimum width of this table
196
- *
197
- * @return float
198
- */
199
- public function get_min_width()
200
- {
201
- return $this->_min_width;
202
- }
203
-
204
- /**
205
- * Return the maximum width of this table
206
- *
207
- * @return float
208
- */
209
- public function get_max_width()
210
- {
211
- return $this->_max_width;
212
- }
213
-
214
- /**
215
- * Set the minimum width of the table
216
- *
217
- * @param float $width the new minimum width
218
- */
219
- public function set_min_width($width)
220
- {
221
- $this->_min_width = $width;
222
- }
223
-
224
- /**
225
- * Set the maximum width of the table
226
- *
227
- * @param float $width the new maximum width
228
- */
229
- public function set_max_width($width)
230
- {
231
- $this->_max_width = $width;
232
- }
233
-
234
  //........................................................................
235
 
236
  /**
@@ -248,7 +193,7 @@ class Table extends AbstractFrameDecorator
248
  $wsPattern = '/^[^\S\xA0\x{202F}\x{2007}]*$/u';
249
  $validChildOrNull = function ($frame) {
250
  return $frame === null
251
- || in_array($frame->get_style()->display, self::$VALID_CHILDREN, true);
252
  };
253
 
254
  return $frame->is_text_node() && !$frame->is_pre()
@@ -273,7 +218,7 @@ class Table extends AbstractFrameDecorator
273
  foreach ($children as $child) {
274
  $display = $child->get_style()->display;
275
 
276
- if (in_array($display, self::$ROW_GROUPS, true)) {
277
  // Reset anonymous tbody
278
  $tbody = null;
279
 
@@ -314,7 +259,7 @@ class Table extends AbstractFrameDecorator
314
  foreach ($this->get_children() as $child) {
315
  $display = $child->get_style()->display;
316
 
317
- if (in_array($display, self::$ROW_GROUPS, true)) {
318
  $this->normalizeRowGroup($child);
319
  }
320
  }
@@ -395,26 +340,4 @@ class Table extends AbstractFrameDecorator
395
  $frame->append_child($td);
396
  }
397
  }
398
-
399
- //........................................................................
400
-
401
- /**
402
- * @deprecated
403
- */
404
- public function normalise()
405
- {
406
- $this->normalize();
407
- }
408
-
409
- /**
410
- * Moves the specified frame and it's corresponding node outside of
411
- * the table.
412
- *
413
- * @deprecated
414
- * @param Frame $frame the frame to move
415
- */
416
- public function move_after(Frame $frame)
417
- {
418
- $this->get_parent()->insert_child_after($frame, $this);
419
- }
420
  }
20
  */
21
  class Table extends AbstractFrameDecorator
22
  {
23
+ public const VALID_CHILDREN = Style::TABLE_INTERNAL_TYPES;
24
 
25
+ /**
26
+ * List of all row-group display types.
27
+ */
28
+ public const ROW_GROUPS = [
29
  "table-row-group",
30
  "table-header-group",
31
  "table-footer-group"
39
  */
40
  protected $_cellmap;
41
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
42
  /**
43
  * Table header rows. Each table header is duplicated when a table
44
  * spans pages.
70
  $this->_cellmap->set_layout_fixed(true);
71
  }
72
 
 
 
73
  $this->_headers = [];
74
  $this->_footers = [];
75
  }
78
  {
79
  parent::reset();
80
  $this->_cellmap->reset();
 
 
81
  $this->_headers = [];
82
  $this->_footers = [];
83
  $this->_reflower->reset();
119
 
120
  parent::split($first_header, $page_break, $forced);
121
 
122
+ } elseif (in_array($child->get_style()->display, self::ROW_GROUPS, true)) {
123
 
124
  // Individual rows should have already been handled
125
  parent::split($child, $page_break, $forced);
176
  return $this->_cellmap;
177
  }
178
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
179
  //........................................................................
180
 
181
  /**
193
  $wsPattern = '/^[^\S\xA0\x{202F}\x{2007}]*$/u';
194
  $validChildOrNull = function ($frame) {
195
  return $frame === null
196
+ || in_array($frame->get_style()->display, self::VALID_CHILDREN, true);
197
  };
198
 
199
  return $frame->is_text_node() && !$frame->is_pre()
218
  foreach ($children as $child) {
219
  $display = $child->get_style()->display;
220
 
221
+ if (in_array($display, self::ROW_GROUPS, true)) {
222
  // Reset anonymous tbody
223
  $tbody = null;
224
 
259
  foreach ($this->get_children() as $child) {
260
  $display = $child->get_style()->display;
261
 
262
+ if (in_array($display, self::ROW_GROUPS, true)) {
263
  $this->normalizeRowGroup($child);
264
  }
265
  }
340
  $frame->append_child($td);
341
  }
342
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
343
  }
vendor/dompdf/dompdf/src/FrameDecorator/TableCell.php CHANGED
@@ -81,7 +81,7 @@ class TableCell extends BlockFrameDecorator
81
  );
82
 
83
  $new_height = $height - $v_space;
84
- $style->height = $new_height;
85
 
86
  if ($new_height > $this->_content_height) {
87
  $y_offset = 0;
81
  );
82
 
83
  $new_height = $height - $v_space;
84
+ $style->set_used("height", $new_height);
85
 
86
  if ($new_height > $this->_content_height) {
87
  $y_offset = 0;
vendor/dompdf/dompdf/src/FrameDecorator/TableRow.php CHANGED
@@ -9,7 +9,6 @@ namespace Dompdf\FrameDecorator;
9
 
10
  use Dompdf\Dompdf;
11
  use Dompdf\Frame;
12
- use Dompdf\FrameDecorator\Table as TableFrameDecorator;
13
 
14
  /**
15
  * Decorates Frames for table row layout
@@ -27,32 +26,4 @@ class TableRow extends AbstractFrameDecorator
27
  {
28
  parent::__construct($frame, $dompdf);
29
  }
30
-
31
- //........................................................................
32
-
33
- /**
34
- * Remove all non table-cell frames from this row and move them after
35
- * the table.
36
- *
37
- * @deprecated
38
- */
39
- function normalise()
40
- {
41
- // Find our table parent
42
- $p = TableFrameDecorator::find_parent_table($this);
43
-
44
- $erroneous_frames = [];
45
- foreach ($this->get_children() as $child) {
46
- $display = $child->get_style()->display;
47
-
48
- if ($display !== "table-cell") {
49
- $erroneous_frames[] = $child;
50
- }
51
- }
52
-
53
- // dump the extra nodes after the table.
54
- foreach ($erroneous_frames as $frame) {
55
- $p->move_after($frame);
56
- }
57
- }
58
  }
9
 
10
  use Dompdf\Dompdf;
11
  use Dompdf\Frame;
 
12
 
13
  /**
14
  * Decorates Frames for table row layout
26
  {
27
  parent::__construct($frame, $dompdf);
28
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
29
  }
vendor/dompdf/dompdf/src/FrameDecorator/Text.php CHANGED
@@ -22,9 +22,9 @@ use Dompdf\Exception;
22
  class Text extends AbstractFrameDecorator
23
  {
24
  /**
25
- * @var float|null
26
  */
27
- protected $_text_spacing;
28
 
29
  /**
30
  * Text constructor.
@@ -39,23 +39,23 @@ class Text extends AbstractFrameDecorator
39
  }
40
 
41
  parent::__construct($frame, $dompdf);
42
- $this->_text_spacing = null;
43
  }
44
 
45
  function reset()
46
  {
47
  parent::reset();
48
- $this->_text_spacing = null;
49
  }
50
 
51
  // Accessor methods
52
 
53
  /**
54
- * @return float|null
55
  */
56
- function get_text_spacing()
57
  {
58
- return $this->_text_spacing;
59
  }
60
 
61
  /**
@@ -118,15 +118,10 @@ class Text extends AbstractFrameDecorator
118
  /**
119
  * @param float $spacing
120
  */
121
- function set_text_spacing($spacing)
122
  {
123
- $style = $this->_frame->get_style();
124
-
125
- $this->_text_spacing = $spacing;
126
- $char_spacing = (float)$style->length_in_pt($style->letter_spacing);
127
-
128
- // Re-adjust our width to account for the change in spacing
129
- $style->width = $this->_dompdf->getFontMetrics()->getTextWidth($this->get_text(), $style->font_family, $style->font_size, $spacing, $char_spacing);
130
  }
131
 
132
  /**
@@ -134,16 +129,19 @@ class Text extends AbstractFrameDecorator
134
  *
135
  * @return float
136
  */
137
- function recalculate_width()
138
  {
 
139
  $style = $this->get_style();
140
  $text = $this->get_text();
141
- $size = $style->font_size;
142
  $font = $style->font_family;
143
- $word_spacing = (float)$style->length_in_pt($style->word_spacing);
144
- $char_spacing = (float)$style->length_in_pt($style->letter_spacing);
 
 
145
 
146
- return $style->width = $this->_dompdf->getFontMetrics()->getTextWidth($text, $font, $size, $word_spacing, $char_spacing);
 
147
  }
148
 
149
  // Text manipulation methods
22
  class Text extends AbstractFrameDecorator
23
  {
24
  /**
25
+ * @var float
26
  */
27
+ protected $text_spacing;
28
 
29
  /**
30
  * Text constructor.
39
  }
40
 
41
  parent::__construct($frame, $dompdf);
42
+ $this->text_spacing = 0.0;
43
  }
44
 
45
  function reset()
46
  {
47
  parent::reset();
48
+ $this->text_spacing = 0.0;
49
  }
50
 
51
  // Accessor methods
52
 
53
  /**
54
+ * @return float
55
  */
56
+ public function get_text_spacing(): float
57
  {
58
+ return $this->text_spacing;
59
  }
60
 
61
  /**
118
  /**
119
  * @param float $spacing
120
  */
121
+ public function set_text_spacing(float $spacing): void
122
  {
123
+ $this->text_spacing = $spacing;
124
+ $this->recalculate_width();
 
 
 
 
 
125
  }
126
 
127
  /**
129
  *
130
  * @return float
131
  */
132
+ public function recalculate_width(): float
133
  {
134
+ $fontMetrics = $this->_dompdf->getFontMetrics();
135
  $style = $this->get_style();
136
  $text = $this->get_text();
 
137
  $font = $style->font_family;
138
+ $size = $style->font_size;
139
+ $word_spacing = $this->text_spacing + $style->word_spacing;
140
+ $letter_spacing = $style->letter_spacing;
141
+ $text_width = $fontMetrics->getTextWidth($text, $font, $size, $word_spacing, $letter_spacing);
142
 
143
+ $style->set_used("width", $text_width);
144
+ return $text_width;
145
  }
146
 
147
  // Text manipulation methods
vendor/dompdf/dompdf/src/FrameReflower/AbstractFrameReflower.php CHANGED
@@ -57,10 +57,6 @@ abstract class AbstractFrameReflower
57
  $this->_min_max_cache = null;
58
  }
59
 
60
- function dispose()
61
- {
62
- }
63
-
64
  /**
65
  * @return Dompdf
66
  */
@@ -87,7 +83,7 @@ abstract class AbstractFrameReflower
87
 
88
  switch ($style->position) {
89
  case "absolute":
90
- $parent = $frame->find_positionned_parent();
91
  if ($parent !== $frame->get_root()) {
92
  $parent_style = $parent->get_style();
93
  $parent_padding_box = $parent->get_padding_box();
@@ -124,7 +120,7 @@ abstract class AbstractFrameReflower
124
  * Collapse frames margins
125
  * http://www.w3.org/TR/CSS21/box.html#collapsing-margins
126
  */
127
- protected function _collapse_margins()
128
  {
129
  $frame = $this->_frame;
130
 
@@ -143,13 +139,13 @@ abstract class AbstractFrameReflower
143
 
144
  // Handle 'auto' values
145
  if ($t === "auto") {
146
- $style->margin_top = 0;
147
- $t = 0;
148
  }
149
 
150
  if ($b === "auto") {
151
- $style->margin_bottom = 0;
152
- $b = 0;
153
  }
154
 
155
  // Collapse vertical margins:
@@ -171,9 +167,9 @@ abstract class AbstractFrameReflower
171
  $n_style = $n->get_style();
172
  $n_t = (float)$n_style->length_in_pt($n_style->margin_top, $cb["w"]);
173
 
174
- $b = $this->_get_collapsed_margin_length($b, $n_t);
175
- $style->margin_bottom = $b;
176
- $n_style->margin_top = 0;
177
  }
178
 
179
  // Collapse our first child's margin, if there is no border or padding
@@ -197,9 +193,9 @@ abstract class AbstractFrameReflower
197
  $f_style = $f->get_style();
198
  $f_t = (float)$f_style->length_in_pt($f_style->margin_top, $cb["w"]);
199
 
200
- $t = $this->_get_collapsed_margin_length($t, $f_t);
201
- $style->margin_top = $t;
202
- $f_style->margin_top = 0;
203
  }
204
  }
205
 
@@ -224,9 +220,9 @@ abstract class AbstractFrameReflower
224
  $l_style = $l->get_style();
225
  $l_b = (float)$l_style->length_in_pt($l_style->margin_bottom, $cb["w"]);
226
 
227
- $b = $this->_get_collapsed_margin_length($b, $l_b);
228
- $style->margin_bottom = $b;
229
- $l_style->margin_bottom = 0;
230
  }
231
  }
232
  }
@@ -236,21 +232,22 @@ abstract class AbstractFrameReflower
236
  *
237
  * See http://www.w3.org/TR/CSS21/box.html#collapsing-margins.
238
  *
239
- * @param float $length1
240
- * @param float $length2
 
241
  * @return float
242
  */
243
- private function _get_collapsed_margin_length($length1, $length2)
244
  {
245
- if ($length1 < 0 && $length2 < 0) {
246
- return min($length1, $length2); // min(x, y) = - max(abs(x), abs(y)), if x < 0 && y < 0
247
  }
248
 
249
- if ($length1 < 0 || $length2 < 0) {
250
- return $length1 + $length2; // x + y = x - abs(y), if y < 0
251
  }
252
 
253
- return max($length1, $length2);
254
  }
255
 
256
  /**
@@ -275,16 +272,16 @@ abstract class AbstractFrameReflower
275
  if ($left === "auto" && $right === "auto") {
276
  $left = 0;
277
  } elseif ($left === "auto") {
278
- $left = -(float) $right;
279
  }
280
 
281
  if ($top === "auto" && $bottom === "auto") {
282
  $top = 0;
283
  } elseif ($top === "auto") {
284
- $top = -(float) $bottom;
285
  }
286
 
287
- $frame->move((float) $left, (float) $top);
288
  }
289
  }
290
 
@@ -308,7 +305,7 @@ abstract class AbstractFrameReflower
308
  $style = $this->_frame->get_style();
309
  $min_width = $style->min_width;
310
 
311
- return $min_width !== "auto" && $min_width !== "none"
312
  ? $style->length_in_pt($min_width, $cbw ?? 0)
313
  : 0.0;
314
  }
@@ -328,7 +325,7 @@ abstract class AbstractFrameReflower
328
  $style = $this->_frame->get_style();
329
  $max_width = $style->max_width;
330
 
331
- return $max_width !== "none" && $max_width !== "auto"
332
  ? $style->length_in_pt($max_width, $cbw ?? INF)
333
  : INF;
334
  }
@@ -348,7 +345,7 @@ abstract class AbstractFrameReflower
348
  $style = $this->_frame->get_style();
349
  $min_height = $style->min_height;
350
 
351
- return $min_height !== "auto" && $min_height !== "none"
352
  ? $style->length_in_pt($min_height, $cbh ?? 0)
353
  : 0.0;
354
  }
@@ -368,7 +365,7 @@ abstract class AbstractFrameReflower
368
  $style = $this->_frame->get_style();
369
  $max_height = $style->max_height;
370
 
371
- return $max_height !== "none" && $max_height !== "auto"
372
  ? $style->length_in_pt($style->max_height, $cbh ?? INF)
373
  : INF;
374
  }
@@ -388,12 +385,13 @@ abstract class AbstractFrameReflower
388
  $low = [];
389
  $high = [];
390
 
391
- for ($iter = $this->_frame->get_children()->getIterator(); $iter->valid(); $iter->next()) {
392
  $inline_min = 0;
393
  $inline_max = 0;
394
 
395
  // Add all adjacent inline widths together to calculate max width
396
  while ($iter->valid() && ($iter->current()->is_inline_level() || $iter->current()->get_style()->display === "-dompdf-image")) {
 
397
  $child = $iter->current();
398
  $child->get_reflower()->_set_content();
399
  $minmax = $child->get_min_max_width();
@@ -417,6 +415,7 @@ abstract class AbstractFrameReflower
417
 
418
  // Skip children with absolute position
419
  if ($iter->valid() && !$iter->current()->is_absolute()) {
 
420
  $child = $iter->current();
421
  $child->get_reflower()->_set_content();
422
  list($low[], $high[]) = $child->get_min_max_width();
@@ -549,7 +548,6 @@ abstract class AbstractFrameReflower
549
  */
550
  protected function _parse_content(): string
551
  {
552
- // The `content` property will be returned parsed into its components
553
  $style = $this->_frame->get_style();
554
  $content = $style->content;
555
 
57
  $this->_min_max_cache = null;
58
  }
59
 
 
 
 
 
60
  /**
61
  * @return Dompdf
62
  */
83
 
84
  switch ($style->position) {
85
  case "absolute":
86
+ $parent = $frame->find_positioned_parent();
87
  if ($parent !== $frame->get_root()) {
88
  $parent_style = $parent->get_style();
89
  $parent_padding_box = $parent->get_padding_box();
120
  * Collapse frames margins
121
  * http://www.w3.org/TR/CSS21/box.html#collapsing-margins
122
  */
123
+ protected function _collapse_margins(): void
124
  {
125
  $frame = $this->_frame;
126
 
139
 
140
  // Handle 'auto' values
141
  if ($t === "auto") {
142
+ $style->set_used("margin_top", 0.0);
143
+ $t = 0.0;
144
  }
145
 
146
  if ($b === "auto") {
147
+ $style->set_used("margin_bottom", 0.0);
148
+ $b = 0.0;
149
  }
150
 
151
  // Collapse vertical margins:
167
  $n_style = $n->get_style();
168
  $n_t = (float)$n_style->length_in_pt($n_style->margin_top, $cb["w"]);
169
 
170
+ $b = $this->get_collapsed_margin_length($b, $n_t);
171
+ $style->set_used("margin_bottom", $b);
172
+ $n_style->set_used("margin_top", 0.0);
173
  }
174
 
175
  // Collapse our first child's margin, if there is no border or padding
193
  $f_style = $f->get_style();
194
  $f_t = (float)$f_style->length_in_pt($f_style->margin_top, $cb["w"]);
195
 
196
+ $t = $this->get_collapsed_margin_length($t, $f_t);
197
+ $style->set_used("margin_top", $t);
198
+ $f_style->set_used("margin_top", 0.0);
199
  }
200
  }
201
 
220
  $l_style = $l->get_style();
221
  $l_b = (float)$l_style->length_in_pt($l_style->margin_bottom, $cb["w"]);
222
 
223
+ $b = $this->get_collapsed_margin_length($b, $l_b);
224
+ $style->set_used("margin_bottom", $b);
225
+ $l_style->set_used("margin_bottom", 0.0);
226
  }
227
  }
228
  }
232
  *
233
  * See http://www.w3.org/TR/CSS21/box.html#collapsing-margins.
234
  *
235
+ * @param float $l1
236
+ * @param float $l2
237
+ *
238
  * @return float
239
  */
240
+ private function get_collapsed_margin_length(float $l1, float $l2): float
241
  {
242
+ if ($l1 < 0 && $l2 < 0) {
243
+ return min($l1, $l2); // min(x, y) = - max(abs(x), abs(y)), if x < 0 && y < 0
244
  }
245
 
246
+ if ($l1 < 0 || $l2 < 0) {
247
+ return $l1 + $l2; // x + y = x - abs(y), if y < 0
248
  }
249
 
250
+ return max($l1, $l2);
251
  }
252
 
253
  /**
272
  if ($left === "auto" && $right === "auto") {
273
  $left = 0;
274
  } elseif ($left === "auto") {
275
+ $left = -$right;
276
  }
277
 
278
  if ($top === "auto" && $bottom === "auto") {
279
  $top = 0;
280
  } elseif ($top === "auto") {
281
+ $top = -$bottom;
282
  }
283
 
284
+ $frame->move($left, $top);
285
  }
286
  }
287
 
305
  $style = $this->_frame->get_style();
306
  $min_width = $style->min_width;
307
 
308
+ return $min_width !== "auto"
309
  ? $style->length_in_pt($min_width, $cbw ?? 0)
310
  : 0.0;
311
  }
325
  $style = $this->_frame->get_style();
326
  $max_width = $style->max_width;
327
 
328
+ return $max_width !== "none"
329
  ? $style->length_in_pt($max_width, $cbw ?? INF)
330
  : INF;
331
  }
345
  $style = $this->_frame->get_style();
346
  $min_height = $style->min_height;
347
 
348
+ return $min_height !== "auto"
349
  ? $style->length_in_pt($min_height, $cbh ?? 0)
350
  : 0.0;
351
  }
365
  $style = $this->_frame->get_style();
366
  $max_height = $style->max_height;
367
 
368
+ return $max_height !== "none"
369
  ? $style->length_in_pt($style->max_height, $cbh ?? INF)
370
  : INF;
371
  }
385
  $low = [];
386
  $high = [];
387
 
388
+ for ($iter = $this->_frame->get_children(); $iter->valid(); $iter->next()) {
389
  $inline_min = 0;
390
  $inline_max = 0;
391
 
392
  // Add all adjacent inline widths together to calculate max width
393
  while ($iter->valid() && ($iter->current()->is_inline_level() || $iter->current()->get_style()->display === "-dompdf-image")) {
394
+ /** @var AbstractFrameDecorator */
395
  $child = $iter->current();
396
  $child->get_reflower()->_set_content();
397
  $minmax = $child->get_min_max_width();
415
 
416
  // Skip children with absolute position
417
  if ($iter->valid() && !$iter->current()->is_absolute()) {
418
+ /** @var AbstractFrameDecorator */
419
  $child = $iter->current();
420
  $child->get_reflower()->_set_content();
421
  list($low[], $high[]) = $child->get_min_max_width();
548
  */
549
  protected function _parse_content(): string
550
  {
 
551
  $style = $this->_frame->get_style();
552
  $content = $style->content;
553
 
vendor/dompdf/dompdf/src/FrameReflower/Block.php CHANGED
@@ -603,7 +603,7 @@ class Block extends AbstractFrameReflower
603
  $imageHeightDiff = $height * 0.8 - $marginHeight;
604
 
605
  $align = $frame->get_style()->vertical_align;
606
- if (in_array($align, Style::$vertical_align_keywords, true)) {
607
  switch ($align) {
608
  case "middle":
609
  $y_offset = $imageHeightDiff / 2;
@@ -644,7 +644,7 @@ class Block extends AbstractFrameReflower
644
  } else {
645
  $align = $parent->get_style()->vertical_align;
646
  }
647
- if (in_array($align, Style::$vertical_align_keywords, true)) {
648
  switch ($align) {
649
  case "middle":
650
  $y_offset = ($height * 0.8 - $baseline) / 2;
@@ -808,14 +808,14 @@ class Block extends AbstractFrameReflower
808
 
809
  // Determine the constraints imposed by this frame: calculate the width
810
  // of the content area:
811
- list($width, $left_margin, $right_margin, $left, $right) = $this->_calculate_restricted_width();
812
 
813
  // Store the calculated properties
814
- $style->width = $width;
815
- $style->margin_left = $left_margin;
816
- $style->margin_right = $right_margin;
817
- $style->left = $left;
818
- $style->right = $right;
819
 
820
  $margin_top = $style->length_in_pt($style->margin_top, $cb["w"]);
821
  $margin_bottom = $style->length_in_pt($style->margin_bottom, $cb["w"]);
@@ -825,7 +825,7 @@ class Block extends AbstractFrameReflower
825
 
826
  // Update the position
827
  $this->_frame->position();
828
- list($x, $y) = $this->_frame->get_position();
829
 
830
  // Adjust the first line based on the text-indent property
831
  $indent = (float)$style->length_in_pt($style->text_indent, $cb["w"]);
@@ -843,7 +843,7 @@ class Block extends AbstractFrameReflower
843
  $style->padding_bottom
844
  ], $cb["w"]);
845
 
846
- $cb_x = $x + (float)$left_margin + (float)$style->length_in_pt([$style->border_left_width,
847
  $style->padding_left], $cb["w"]);
848
 
849
  $cb_y = $y + $top;
@@ -884,12 +884,13 @@ class Block extends AbstractFrameReflower
884
  }
885
 
886
  // Determine our height
887
- list($height, $margin_top, $margin_bottom, $top, $bottom) = $this->_calculate_restricted_height();
888
- $style->height = $height;
889
- $style->margin_top = $margin_top;
890
- $style->margin_bottom = $margin_bottom;
891
- $style->top = $top;
892
- $style->bottom = $bottom;
 
893
 
894
  if ($this->_frame->is_absolute()) {
895
  if ($auto_top) {
603
  $imageHeightDiff = $height * 0.8 - $marginHeight;
604
 
605
  $align = $frame->get_style()->vertical_align;
606
+ if (in_array($align, Style::VERTICAL_ALIGN_KEYWORDS, true)) {
607
  switch ($align) {
608
  case "middle":
609
  $y_offset = $imageHeightDiff / 2;
644
  } else {
645
  $align = $parent->get_style()->vertical_align;
646
  }
647
+ if (in_array($align, Style::VERTICAL_ALIGN_KEYWORDS, true)) {
648
  switch ($align) {
649
  case "middle":
650
  $y_offset = ($height * 0.8 - $baseline) / 2;
808
 
809
  // Determine the constraints imposed by this frame: calculate the width
810
  // of the content area:
811
+ [$width, $margin_left, $margin_right, $left, $right] = $this->_calculate_restricted_width();
812
 
813
  // Store the calculated properties
814
+ $style->set_used("width", $width);
815
+ $style->set_used("margin_left", $margin_left);
816
+ $style->set_used("margin_right", $margin_right);
817
+ $style->set_used("left", $left);
818
+ $style->set_used("right", $right);
819
 
820
  $margin_top = $style->length_in_pt($style->margin_top, $cb["w"]);
821
  $margin_bottom = $style->length_in_pt($style->margin_bottom, $cb["w"]);
825
 
826
  // Update the position
827
  $this->_frame->position();
828
+ [$x, $y] = $this->_frame->get_position();
829
 
830
  // Adjust the first line based on the text-indent property
831
  $indent = (float)$style->length_in_pt($style->text_indent, $cb["w"]);
843
  $style->padding_bottom
844
  ], $cb["w"]);
845
 
846
+ $cb_x = $x + (float)$margin_left + (float)$style->length_in_pt([$style->border_left_width,
847
  $style->padding_left], $cb["w"]);
848
 
849
  $cb_y = $y + $top;
884
  }
885
 
886
  // Determine our height
887
+ [$height, $margin_top, $margin_bottom, $top, $bottom] = $this->_calculate_restricted_height();
888
+
889
+ $style->set_used("height", $height);
890
+ $style->set_used("margin_top", $margin_top);
891
+ $style->set_used("margin_bottom", $margin_bottom);
892
+ $style->set_used("top", $top);
893
+ $style->set_used("bottom", $bottom);
894
 
895
  if ($this->_frame->is_absolute()) {
896
  if ($auto_top) {
vendor/dompdf/dompdf/src/FrameReflower/Image.php CHANGED
@@ -188,8 +188,8 @@ class Image extends AbstractFrameReflower
188
  print $width . " " . $height . ";";
189
  }
190
 
191
- $style->width = $width;
192
- $style->height = $height;
193
  }
194
 
195
  protected function resolve_margins(): void
@@ -200,16 +200,16 @@ class Image extends AbstractFrameReflower
200
  $style = $this->_frame->get_style();
201
 
202
  if ($style->margin_left === "auto") {
203
- $style->margin_left = 0;
204
  }
205
  if ($style->margin_right === "auto") {
206
- $style->margin_right = 0;
207
  }
208
  if ($style->margin_top === "auto") {
209
- $style->margin_top = 0;
210
  }
211
  if ($style->margin_bottom === "auto") {
212
- $style->margin_bottom = 0;
213
  }
214
  }
215
  }
188
  print $width . " " . $height . ";";
189
  }
190
 
191
+ $style->set_used("width", $width);
192
+ $style->set_used("height", $height);
193
  }
194
 
195
  protected function resolve_margins(): void
200
  $style = $this->_frame->get_style();
201
 
202
  if ($style->margin_left === "auto") {
203
+ $style->set_used("margin_left", 0.0);
204
  }
205
  if ($style->margin_right === "auto") {
206
+ $style->set_used("margin_right", 0.0);
207
  }
208
  if ($style->margin_top === "auto") {
209
+ $style->set_used("margin_top", 0.0);
210
  }
211
  if ($style->margin_bottom === "auto") {
212
+ $style->set_used("margin_bottom", 0.0);
213
  }
214
  }
215
  }
vendor/dompdf/dompdf/src/FrameReflower/Inline.php CHANGED
@@ -43,7 +43,7 @@ class Inline extends AbstractFrameReflower
43
  $style = $frame->get_style();
44
 
45
  // Resolve width, so the margin width can be checked
46
- $style->width = 0;
47
 
48
  $cb = $frame->get_containing_block();
49
  $line = $block->get_current_line_box();
@@ -97,16 +97,16 @@ class Inline extends AbstractFrameReflower
97
  // https://www.w3.org/TR/CSS21/visudet.html#inline-width
98
  // https://www.w3.org/TR/CSS21/visudet.html#inline-non-replaced
99
  if ($style->margin_left === "auto") {
100
- $style->margin_left = 0;
101
  }
102
  if ($style->margin_right === "auto") {
103
- $style->margin_right = 0;
104
  }
105
  if ($style->margin_top === "auto") {
106
- $style->margin_top = 0;
107
  }
108
  if ($style->margin_bottom === "auto") {
109
- $style->margin_bottom = 0;
110
  }
111
 
112
  // Handle line breaks
@@ -140,14 +140,18 @@ class Inline extends AbstractFrameReflower
140
  $f_style = $f->get_style();
141
  $f_style->margin_left = $style->margin_left;
142
  $f_style->padding_left = $style->padding_left;
143
- $f_style->border_left = $style->border_left;
 
 
144
  }
145
 
146
  if (($l = $frame->get_last_child()) && $l instanceof TextFrameDecorator) {
147
  $l_style = $l->get_style();
148
  $l_style->margin_right = $style->margin_right;
149
  $l_style->padding_right = $style->padding_right;
150
- $l_style->border_right = $style->border_right;
 
 
151
  }
152
 
153
  $cb = $frame->get_containing_block();
43
  $style = $frame->get_style();
44
 
45
  // Resolve width, so the margin width can be checked
46
+ $style->set_used("width", 0.0);
47
 
48
  $cb = $frame->get_containing_block();
49
  $line = $block->get_current_line_box();
97
  // https://www.w3.org/TR/CSS21/visudet.html#inline-width
98
  // https://www.w3.org/TR/CSS21/visudet.html#inline-non-replaced
99
  if ($style->margin_left === "auto") {
100
+ $style->set_used("margin_left", 0.0);
101
  }
102
  if ($style->margin_right === "auto") {
103
+ $style->set_used("margin_right", 0.0);
104
  }
105
  if ($style->margin_top === "auto") {
106
+ $style->set_used("margin_top", 0.0);
107
  }
108
  if ($style->margin_bottom === "auto") {
109
+ $style->set_used("margin_bottom", 0.0);
110
  }
111
 
112
  // Handle line breaks
140
  $f_style = $f->get_style();
141
  $f_style->margin_left = $style->margin_left;
142
  $f_style->padding_left = $style->padding_left;
143
+ $f_style->border_left_width = $style->border_left_width;
144
+ $f_style->border_left_style = $style->border_left_style;
145
+ $f_style->border_left_color = $style->border_left_color;
146
  }
147
 
148
  if (($l = $frame->get_last_child()) && $l instanceof TextFrameDecorator) {
149
  $l_style = $l->get_style();
150
  $l_style->margin_right = $style->margin_right;
151
  $l_style->padding_right = $style->padding_right;
152
+ $l_style->border_right_width = $style->border_right_width;
153
+ $l_style->border_right_style = $style->border_right_style;
154
+ $l_style->border_right_color = $style->border_right_color;
155
  }
156
 
157
  $cb = $frame->get_containing_block();
vendor/dompdf/dompdf/src/FrameReflower/ListBullet.php CHANGED
@@ -36,7 +36,7 @@ class ListBullet extends AbstractFrameReflower
36
  $frame = $this->_frame;
37
  $style = $frame->get_style();
38
 
39
- $style->width = $frame->get_width();
40
  $frame->position();
41
 
42
  if ($style->list_style_position === "inside") {
36
  $frame = $this->_frame;
37
  $style = $frame->get_style();
38
 
39
+ $style->set_used("width", $frame->get_width());
40
  $frame->position();
41
 
42
  if ($style->list_style_position === "inside") {
vendor/dompdf/dompdf/src/FrameReflower/Page.php CHANGED
@@ -119,8 +119,7 @@ class Page extends AbstractFrameReflower
119
 
120
  // Only if it's the first page, we save the nodes with a fixed position
121
  if ($current_page == 0) {
122
- $children = $child->get_children();
123
- foreach ($children as $onechild) {
124
  if ($onechild->get_style()->position === "fixed") {
125
  $fixed_children[] = $onechild->deep_copy();
126
  }
@@ -182,20 +181,18 @@ class Page extends AbstractFrameReflower
182
  protected function _check_callbacks(string $event, Frame $frame): void
183
  {
184
  if (!isset($this->_callbacks)) {
185
- $dompdf = $this->_frame->get_dompdf();
186
  $this->_callbacks = $dompdf->getCallbacks();
187
  $this->_canvas = $dompdf->getCanvas();
188
  }
189
 
190
  if (isset($this->_callbacks[$event])) {
191
  $fs = $this->_callbacks[$event];
192
- $info = [
193
- 0 => $this->_canvas, "canvas" => $this->_canvas,
194
- 1 => $frame, "frame" => $frame,
195
- ];
196
 
197
  foreach ($fs as $f) {
198
- $f($info);
199
  }
200
  }
201
  }
119
 
120
  // Only if it's the first page, we save the nodes with a fixed position
121
  if ($current_page == 0) {
122
+ foreach ($child->get_children() as $onechild) {
 
123
  if ($onechild->get_style()->position === "fixed") {
124
  $fixed_children[] = $onechild->deep_copy();
125
  }
181
  protected function _check_callbacks(string $event, Frame $frame): void
182
  {
183
  if (!isset($this->_callbacks)) {
184
+ $dompdf = $this->get_dompdf();
185
  $this->_callbacks = $dompdf->getCallbacks();
186
  $this->_canvas = $dompdf->getCanvas();
187
  }
188
 
189
  if (isset($this->_callbacks[$event])) {
190
  $fs = $this->_callbacks[$event];
191
+ $canvas = $this->_canvas;
192
+ $fontMetrics = $this->get_dompdf()->getFontMetrics();
 
 
193
 
194
  foreach ($fs as $f) {
195
+ $f($frame, $canvas, $fontMetrics);
196
  }
197
  }
198
  }
vendor/dompdf/dompdf/src/FrameReflower/Table.php CHANGED
@@ -93,7 +93,7 @@ class Table extends AbstractFrameReflower
93
  } else {
94
  if ($max_width + $delta < $cb["w"]) {
95
  $width = $max_width;
96
- } else if ($cb["w"] - $delta > $min_width) {
97
  $width = $cb["w"] - $delta;
98
  } else {
99
  $width = $min_width;
@@ -106,7 +106,7 @@ class Table extends AbstractFrameReflower
106
  }
107
 
108
  // Store our resolved width
109
- $style->width = $width;
110
 
111
  $cellmap = $this->_frame->get_cellmap();
112
 
@@ -116,8 +116,8 @@ class Table extends AbstractFrameReflower
116
 
117
  // If the whole table fits on the page, then assign each column it's max width
118
  if ($width == $max_width) {
119
- foreach (array_keys($columns) as $i) {
120
- $cellmap->set_column_width($i, $columns[$i]["max-width"]);
121
  }
122
 
123
  return;
@@ -179,10 +179,9 @@ class Table extends AbstractFrameReflower
179
  $increment = $width - $absolute_used;
180
 
181
  foreach ($absolute as $i) {
182
- $min = $columns[$i]["min-width"];
183
- $abs = $columns[$i]["absolute"];
184
  $f = $absolute_used > 0 ? $abs / $absolute_used : 1 / count($absolute);
185
- $w = $min + $increment * $f;
186
  $cellmap->set_column_width($i, $w);
187
  }
188
  return;
@@ -246,8 +245,8 @@ class Table extends AbstractFrameReflower
246
  } else {
247
  // We are over-constrained:
248
  // Each column gets its minimum width
249
- foreach (array_keys($columns) as $i) {
250
- $cellmap->set_column_width($i, $columns[$i]["min-width"]);
251
  }
252
  }
253
  }
@@ -341,15 +340,14 @@ class Table extends AbstractFrameReflower
341
  // border-spacing to the table as padding. The other half is added to
342
  // the cells themselves.
343
  if ($style->border_collapse === "separate") {
344
- list($h, $v) = $style->border_spacing;
345
-
346
- $v = (float)$style->length_in_pt($v) / 2;
347
- $h = (float)$style->length_in_pt($h) / 2;
348
-
349
- $style->padding_left = (float)$style->length_in_pt($style->padding_left, $cb["w"]) + $h;
350
- $style->padding_right = (float)$style->length_in_pt($style->padding_right, $cb["w"]) + $h;
351
- $style->padding_top = (float)$style->length_in_pt($style->padding_top, $cb["w"]) + $v;
352
- $style->padding_bottom = (float)$style->length_in_pt($style->padding_bottom, $cb["w"]) + $v;
353
  }
354
 
355
  $this->_assign_widths();
@@ -378,11 +376,11 @@ class Table extends AbstractFrameReflower
378
  }
379
  }
380
 
381
- $style->margin_left = $left;
382
- $style->margin_right = $right;
383
 
384
  $frame->position();
385
- list($x, $y) = $frame->get_position();
386
 
387
  // Determine the content edge
388
  $offset_x = (float)$left + (float)$style->length_in_pt([
@@ -435,7 +433,7 @@ class Table extends AbstractFrameReflower
435
  }
436
 
437
  // Assign heights to our cells:
438
- $style->height = $this->_calculate_height();
439
 
440
  $page->table_reflow_end();
441
 
@@ -478,19 +476,19 @@ class Table extends AbstractFrameReflower
478
  $this->_state["auto"] = [];
479
 
480
  $columns =& $cellmap->get_columns();
481
- foreach (array_keys($columns) as $i) {
482
- $this->_state["min_width"] += $columns[$i]["min-width"];
483
- $this->_state["max_width"] += $columns[$i]["max-width"];
484
 
485
- if ($columns[$i]["absolute"] > 0) {
486
  $this->_state["absolute"][] = $i;
487
- $this->_state["absolute_used"] += $columns[$i]["absolute"];
488
- } else if ($columns[$i]["percent"] > 0) {
489
  $this->_state["percent"][] = $i;
490
- $this->_state["percent_used"] += $columns[$i]["percent"];
491
  } else {
492
  $this->_state["auto"][] = $i;
493
- $this->_state["auto_min"] += $columns[$i]["min-width"];
494
  }
495
  }
496
 
93
  } else {
94
  if ($max_width + $delta < $cb["w"]) {
95
  $width = $max_width;
96
+ } elseif ($cb["w"] - $delta > $min_width) {
97
  $width = $cb["w"] - $delta;
98
  } else {
99
  $width = $min_width;
106
  }
107
 
108
  // Store our resolved width
109
+ $style->set_used("width", $width);
110
 
111
  $cellmap = $this->_frame->get_cellmap();
112
 
116
 
117
  // If the whole table fits on the page, then assign each column it's max width
118
  if ($width == $max_width) {
119
+ foreach ($columns as $i => $col) {
120
+ $cellmap->set_column_width($i, $col["max-width"]);
121
  }
122
 
123
  return;
179
  $increment = $width - $absolute_used;
180
 
181
  foreach ($absolute as $i) {
182
+ $abs = $columns[$i]["min-width"];
 
183
  $f = $absolute_used > 0 ? $abs / $absolute_used : 1 / count($absolute);
184
+ $w = $abs + $increment * $f;
185
  $cellmap->set_column_width($i, $w);
186
  }
187
  return;
245
  } else {
246
  // We are over-constrained:
247
  // Each column gets its minimum width
248
+ foreach ($columns as $i => $col) {
249
+ $cellmap->set_column_width($i, $col["min-width"]);
250
  }
251
  }
252
  }
340
  // border-spacing to the table as padding. The other half is added to
341
  // the cells themselves.
342
  if ($style->border_collapse === "separate") {
343
+ [$h, $v] = $style->border_spacing;
344
+ $v = $v / 2;
345
+ $h = $h / 2;
346
+
347
+ $style->set_used("padding_left", (float)$style->length_in_pt($style->padding_left, $cb["w"]) + $h);
348
+ $style->set_used("padding_right", (float)$style->length_in_pt($style->padding_right, $cb["w"]) + $h);
349
+ $style->set_used("padding_top", (float)$style->length_in_pt($style->padding_top, $cb["w"]) + $v);
350
+ $style->set_used("padding_bottom", (float)$style->length_in_pt($style->padding_bottom, $cb["w"]) + $v);
 
351
  }
352
 
353
  $this->_assign_widths();
376
  }
377
  }
378
 
379
+ $style->set_used("margin_left", $left);
380
+ $style->set_used("margin_right", $right);
381
 
382
  $frame->position();
383
+ [$x, $y] = $frame->get_position();
384
 
385
  // Determine the content edge
386
  $offset_x = (float)$left + (float)$style->length_in_pt([
433
  }
434
 
435
  // Assign heights to our cells:
436
+ $style->set_used("height", $this->_calculate_height());
437
 
438
  $page->table_reflow_end();
439
 
476
  $this->_state["auto"] = [];
477
 
478
  $columns =& $cellmap->get_columns();
479
+ foreach ($columns as $i => $col) {
480
+ $this->_state["min_width"] += $col["min-width"];
481
+ $this->_state["max_width"] += $col["max-width"];
482
 
483
+ if ($col["absolute"] > 0) {
484
  $this->_state["absolute"][] = $i;
485
+ $this->_state["absolute_used"] += $col["min-width"];
486
+ } elseif ($col["percent"] > 0) {
487
  $this->_state["percent"][] = $i;
488
+ $this->_state["percent_used"] += $col["percent"];
489
  } else {
490
  $this->_state["auto"][] = $i;
491
+ $this->_state["auto_min"] += $col["min-width"];
492
  }
493
  }
494
 
vendor/dompdf/dompdf/src/FrameReflower/TableCell.php CHANGED
@@ -73,7 +73,8 @@ class TableCell extends Block
73
  $style->border_bottom_width],
74
  $h);
75
 
76
- $style->width = $cb_w = $w - $left_space - $right_space;
 
77
 
78
  $content_x = $x + $left_space;
79
  $content_y = $line_y = $y + $top_space;
@@ -118,7 +119,8 @@ class TableCell extends Block
118
  $cellmap->set_row_height($i, $cell_height);
119
  }
120
 
121
- $style->height = $height;
 
122
  $this->_text_align();
123
  $this->vertical_align();
124
 
73
  $style->border_bottom_width],
74
  $h);
75
 
76
+ $cb_w = $w - $left_space - $right_space;
77
+ $style->set_used("width", $cb_w);
78
 
79
  $content_x = $x + $left_space;
80
  $content_y = $line_y = $y + $top_space;
119
  $cellmap->set_row_height($i, $cell_height);
120
  }
121
 
122
+ $style->set_used("height", $height);
123
+
124
  $this->_text_align();
125
  $this->vertical_align();
126
 
vendor/dompdf/dompdf/src/FrameReflower/TableRow.php CHANGED
@@ -67,8 +67,8 @@ class TableRow extends AbstractFrameReflower
67
 
68
  $table = TableFrameDecorator::find_parent_table($this->_frame);
69
  $cellmap = $table->get_cellmap();
70
- $style->width = $cellmap->get_frame_width($this->_frame);
71
- $style->height = $cellmap->get_frame_height($this->_frame);
72
 
73
  $this->_frame->set_position($cellmap->get_frame_position($this->_frame));
74
  }
67
 
68
  $table = TableFrameDecorator::find_parent_table($this->_frame);
69
  $cellmap = $table->get_cellmap();
70
+ $style->set_used("width", $cellmap->get_frame_width($this->_frame));
71
+ $style->set_used("height", $cellmap->get_frame_height($this->_frame));
72
 
73
  $this->_frame->set_position($cellmap->get_frame_position($this->_frame));
74
  }
vendor/dompdf/dompdf/src/FrameReflower/TableRowGroup.php CHANGED
@@ -64,8 +64,8 @@ class TableRowGroup extends AbstractFrameReflower
64
  return;
65
  }
66
 
67
- $style->width = $cellmap->get_frame_width($frame);
68
- $style->height = $cellmap->get_frame_height($frame);
69
 
70
  $frame->set_position($cellmap->get_frame_position($frame));
71
  }
64
  return;
65
  }
66
 
67
+ $style->set_used("width", $cellmap->get_frame_width($frame));
68
+ $style->set_used("height", $cellmap->get_frame_height($frame));
69
 
70
  $frame->set_position($cellmap->get_frame_position($frame));
71
  }
vendor/dompdf/dompdf/src/FrameReflower/Text.php CHANGED
@@ -129,25 +129,23 @@ class Text extends AbstractFrameReflower
129
  */
130
  protected function line_break(string $text, BlockFrameDecorator $block)
131
  {
 
132
  $frame = $this->_frame;
133
  $style = $frame->get_style();
134
- $size = $style->font_size;
135
  $font = $style->font_family;
136
- $current_line = $block->get_current_line_box();
137
- $fontMetrics = $this->getFontMetrics();
 
138
 
139
  // Determine the available width
 
140
  $line_width = $frame->get_containing_block("w");
141
  $current_line_width = $current_line->left + $current_line->w + $current_line->right;
142
  $available_width = $line_width - $current_line_width;
143
 
144
- // Account for word and letter spacing
145
- $word_spacing = (float) $style->length_in_pt($style->word_spacing);
146
- $char_spacing = (float) $style->length_in_pt($style->letter_spacing);
147
-
148
  // Determine the frame width including margin, padding & border
149
  $visible_text = preg_replace('/\xAD/u', "", $text);
150
- $text_width = $fontMetrics->getTextWidth($visible_text, $font, $size, $word_spacing, $char_spacing);
151
  $mbp_width = (float) $style->length_in_pt([
152
  $style->margin_left,
153
  $style->border_left_width,
@@ -170,7 +168,7 @@ class Text extends AbstractFrameReflower
170
  $width = 0;
171
  $str = "";
172
 
173
- $space_width = $fontMetrics->getTextWidth(" ", $font, $size, $word_spacing, $char_spacing);
174
  $shy_width = $fontMetrics->getTextWidth(self::SOFT_HYPHEN, $font, $size);
175
 
176
  // @todo support <wbr>
@@ -180,7 +178,7 @@ class Text extends AbstractFrameReflower
180
  // handle that for now
181
  $sep = $words[$i + 1] ?? "";
182
  $word = $sep === " " ? $words[$i] : $words[$i] . $sep;
183
- $word_width = $fontMetrics->getTextWidth($word, $font, $size, $word_spacing, $char_spacing);
184
  $used_width = $width + $word_width + $mbp_width;
185
 
186
  if (Helpers::lengthGreater($used_width, $available_width)) {
@@ -224,7 +222,7 @@ class Text extends AbstractFrameReflower
224
 
225
  for ($j = 0; $j < mb_strlen($word); $j++) {
226
  $c = mb_substr($word, $j, 1);
227
- $w = $fontMetrics->getTextWidth($s . $c, $font, $size, $word_spacing, $char_spacing);
228
 
229
  if (Helpers::lengthGreater($w, $available_width)) {
230
  break;
@@ -278,7 +276,7 @@ class Text extends AbstractFrameReflower
278
  // elements in case white space is collapsed
279
  if ($text === "") {
280
  $frame->set_text("");
281
- $style->width = 0.0;
282
  return null;
283
  }
284
 
@@ -391,7 +389,8 @@ class Text extends AbstractFrameReflower
391
  $style = $frame->get_style();
392
  $size = $style->font_size;
393
  $font = $style->font_family;
394
- $style->height = $this->getFontMetrics()->getFontHeight($font, $size);
 
395
 
396
  // Handle text transform and white space
397
  $text = $this->pre_process_text($frame->get_text());
@@ -457,12 +456,14 @@ class Text extends AbstractFrameReflower
457
 
458
  public function get_min_max_width(): array
459
  {
 
460
  $frame = $this->_frame;
461
  $style = $frame->get_style();
462
- $size = $style->font_size;
463
- $font = $style->font_family;
464
  $text = $frame->get_text();
465
- $fontMetrics = $this->getFontMetrics();
 
 
 
466
 
467
  // Handle text transform and white space
468
  $text = $this->pre_process_text($frame->get_text());
@@ -498,10 +499,6 @@ class Text extends AbstractFrameReflower
498
  // Strip soft hyphens for max-line-width calculations
499
  $visible_text = preg_replace('/\xAD/u', "", $text);
500
 
501
- // Account for word and letter spacing
502
- $word_spacing = (float) $style->length_in_pt($style->word_spacing);
503
- $char_spacing = (float) $style->length_in_pt($style->letter_spacing);
504
-
505
  // Determine minimum text width
506
  switch ($style->white_space) {
507
  default:
@@ -515,16 +512,16 @@ class Text extends AbstractFrameReflower
515
  // https://www.w3.org/TR/css-text-3/#overflow-wrap-property
516
  if ($style->overflow_wrap === "anywhere") {
517
  $char = mb_substr($visible_text, 0, 1);
518
- $min = $fontMetrics->getTextWidth($char, $font, $size, $word_spacing, $char_spacing);
519
  } else {
520
  // Find the longest word
521
  $words = preg_split(self::$_wordbreak_pattern, $text, -1, PREG_SPLIT_DELIM_CAPTURE);
522
- $lengths = array_map(function ($chunk) use ($fontMetrics, $font, $size, $word_spacing, $char_spacing) {
523
  // Allow trailing white space to overflow. As in actual
524
  // layout above, only handle a single space for now
525
  $sep = $chunk[1] ?? "";
526
  $word = $sep === " " ? $chunk[0] : $chunk[0] . $sep;
527
- return $fontMetrics->getTextWidth($word, $font, $size, $word_spacing, $char_spacing);
528
  }, array_chunk($words, 2));
529
  $min = max($lengths);
530
  }
@@ -533,15 +530,15 @@ class Text extends AbstractFrameReflower
533
  case "pre":
534
  // Find the longest line
535
  $lines = array_flip(preg_split("/\R/u", $visible_text));
536
- array_walk($lines, function(&$chunked_text_width, $chunked_text) use ($fontMetrics, $font, $size, $word_spacing, $char_spacing) {
537
- $chunked_text_width = $fontMetrics->getTextWidth($chunked_text, $font, $size, $word_spacing, $char_spacing);
538
  });
539
  arsort($lines);
540
  $min = reset($lines);
541
  break;
542
 
543
  case "nowrap":
544
- $min = $fontMetrics->getTextWidth($visible_text, $font, $size, $word_spacing, $char_spacing);
545
  break;
546
  }
547
 
@@ -549,15 +546,15 @@ class Text extends AbstractFrameReflower
549
  switch ($style->white_space) {
550
  default:
551
  case "normal":
552
- $max = $fontMetrics->getTextWidth($visible_text, $font, $size, $word_spacing, $char_spacing);
553
  break;
554
 
555
  case "pre-line":
556
  case "pre-wrap":
557
  // Find the longest line
558
  $lines = array_flip(preg_split("/\R/u", $visible_text));
559
- array_walk($lines, function(&$chunked_text_width, $chunked_text) use ($fontMetrics, $font, $size, $word_spacing, $char_spacing) {
560
- $chunked_text_width = $fontMetrics->getTextWidth($chunked_text, $font, $size, $word_spacing, $char_spacing);
561
  });
562
  arsort($lines);
563
  $max = reset($lines);
129
  */
130
  protected function line_break(string $text, BlockFrameDecorator $block)
131
  {
132
+ $fontMetrics = $this->getFontMetrics();
133
  $frame = $this->_frame;
134
  $style = $frame->get_style();
 
135
  $font = $style->font_family;
136
+ $size = $style->font_size;
137
+ $word_spacing = $style->word_spacing;
138
+ $letter_spacing = $style->letter_spacing;
139
 
140
  // Determine the available width
141
+ $current_line = $block->get_current_line_box();
142
  $line_width = $frame->get_containing_block("w");
143
  $current_line_width = $current_line->left + $current_line->w + $current_line->right;
144
  $available_width = $line_width - $current_line_width;
145
 
 
 
 
 
146
  // Determine the frame width including margin, padding & border
147
  $visible_text = preg_replace('/\xAD/u', "", $text);
148
+ $text_width = $fontMetrics->getTextWidth($visible_text, $font, $size, $word_spacing, $letter_spacing);
149
  $mbp_width = (float) $style->length_in_pt([
150
  $style->margin_left,
151
  $style->border_left_width,
168
  $width = 0;
169
  $str = "";
170
 
171
+ $space_width = $fontMetrics->getTextWidth(" ", $font, $size, $word_spacing, $letter_spacing);
172
  $shy_width = $fontMetrics->getTextWidth(self::SOFT_HYPHEN, $font, $size);
173
 
174
  // @todo support <wbr>
178
  // handle that for now
179
  $sep = $words[$i + 1] ?? "";
180
  $word = $sep === " " ? $words[$i] : $words[$i] . $sep;
181
+ $word_width = $fontMetrics->getTextWidth($word, $font, $size, $word_spacing, $letter_spacing);
182
  $used_width = $width + $word_width + $mbp_width;
183
 
184
  if (Helpers::lengthGreater($used_width, $available_width)) {
222
 
223
  for ($j = 0; $j < mb_strlen($word); $j++) {
224
  $c = mb_substr($word, $j, 1);
225
+ $w = $fontMetrics->getTextWidth($s . $c, $font, $size, $word_spacing, $letter_spacing);
226
 
227
  if (Helpers::lengthGreater($w, $available_width)) {
228
  break;
276
  // elements in case white space is collapsed
277
  if ($text === "") {
278
  $frame->set_text("");
279
+ $style->set_used("width", 0.0);
280
  return null;
281
  }
282
 
389
  $style = $frame->get_style();
390
  $size = $style->font_size;
391
  $font = $style->font_family;
392
+ $font_height = $this->getFontMetrics()->getFontHeight($font, $size);
393
+ $style->set_used("height", $font_height);
394
 
395
  // Handle text transform and white space
396
  $text = $this->pre_process_text($frame->get_text());
456
 
457
  public function get_min_max_width(): array
458
  {
459
+ $fontMetrics = $this->getFontMetrics();
460
  $frame = $this->_frame;
461
  $style = $frame->get_style();
 
 
462
  $text = $frame->get_text();
463
+ $font = $style->font_family;
464
+ $size = $style->font_size;
465
+ $word_spacing = $style->word_spacing;
466
+ $letter_spacing = $style->letter_spacing;
467
 
468
  // Handle text transform and white space
469
  $text = $this->pre_process_text($frame->get_text());
499
  // Strip soft hyphens for max-line-width calculations
500
  $visible_text = preg_replace('/\xAD/u', "", $text);
501
 
 
 
 
 
502
  // Determine minimum text width
503
  switch ($style->white_space) {
504
  default:
512
  // https://www.w3.org/TR/css-text-3/#overflow-wrap-property
513
  if ($style->overflow_wrap === "anywhere") {
514
  $char = mb_substr($visible_text, 0, 1);
515
+ $min = $fontMetrics->getTextWidth($char, $font, $size, $word_spacing, $letter_spacing);
516
  } else {
517
  // Find the longest word
518
  $words = preg_split(self::$_wordbreak_pattern, $text, -1, PREG_SPLIT_DELIM_CAPTURE);
519
+ $lengths = array_map(function ($chunk) use ($fontMetrics, $font, $size, $word_spacing, $letter_spacing) {
520
  // Allow trailing white space to overflow. As in actual
521
  // layout above, only handle a single space for now
522
  $sep = $chunk[1] ?? "";
523
  $word = $sep === " " ? $chunk[0] : $chunk[0] . $sep;
524
+ return $fontMetrics->getTextWidth($word, $font, $size, $word_spacing, $letter_spacing);
525
  }, array_chunk($words, 2));
526
  $min = max($lengths);
527
  }
530
  case "pre":
531
  // Find the longest line
532
  $lines = array_flip(preg_split("/\R/u", $visible_text));
533
+ array_walk($lines, function (&$chunked_text_width, $chunked_text) use ($fontMetrics, $font, $size, $word_spacing, $letter_spacing) {
534
+ $chunked_text_width = $fontMetrics->getTextWidth($chunked_text, $font, $size, $word_spacing, $letter_spacing);
535
  });
536
  arsort($lines);
537
  $min = reset($lines);
538
  break;
539
 
540
  case "nowrap":
541
+ $min = $fontMetrics->getTextWidth($visible_text, $font, $size, $word_spacing, $letter_spacing);
542
  break;
543
  }
544
 
546
  switch ($style->white_space) {
547
  default:
548
  case "normal":
549
+ $max = $fontMetrics->getTextWidth($visible_text, $font, $size, $word_spacing, $letter_spacing);
550
  break;
551
 
552
  case "pre-line":
553
  case "pre-wrap":
554
  // Find the longest line
555
  $lines = array_flip(preg_split("/\R/u", $visible_text));
556
+ array_walk($lines, function (&$chunked_text_width, $chunked_text) use ($fontMetrics, $font, $size, $word_spacing, $letter_spacing) {
557
+ $chunked_text_width = $fontMetrics->getTextWidth($chunked_text, $font, $size, $word_spacing, $letter_spacing);
558
  });
559
  arsort($lines);
560
  $max = reset($lines);
vendor/dompdf/dompdf/src/Helpers.php CHANGED
@@ -57,28 +57,45 @@ class Helpers
57
  public static function build_url($protocol, $host, $base_path, $url)
58
  {
59
  $protocol = mb_strtolower($protocol);
 
 
 
60
  if ($url === "") {
61
- //return $protocol . $host . rtrim($base_path, "/\\") . "/";
62
- return $protocol . $host . $base_path;
63
  }
64
 
 
 
65
  // Is the url already fully qualified, a Data URI, or a reference to a named anchor?
66
  // File-protocol URLs may require additional processing (e.g. for URLs with a relative path)
67
- if ((mb_strpos($url, "://") !== false && substr($url, 0, 7) !== "file://") || mb_substr($url, 0, 1) === "#" || mb_strpos($url, "data:") === 0 || mb_strpos($url, "mailto:") === 0 || mb_strpos($url, "tel:") === 0) {
 
 
 
 
 
 
 
 
 
68
  return $url;
69
  }
70
 
71
- if (strpos($url, "file://") === 0) {
 
72
  $url = substr($url, 7);
73
- $protocol = "";
 
 
 
 
74
  }
75
 
76
  $ret = "";
77
- if ($protocol !== "file://") {
78
- $ret = $protocol;
79
- }
80
 
81
- if (!in_array(mb_strtolower($protocol), ["http://", "https://", "ftp://", "ftps://"], true)) {
 
 
82
  //On Windows local file, an abs path can begin also with a '\' or a drive letter and colon
83
  //drive: followed by a relative path would be a drive specific default folder.
84
  //not known in php app code, treat as abs path
@@ -89,9 +106,18 @@ class Helpers
89
  }
90
  $ret .= $url;
91
  $ret = preg_replace('/\?(.*)$/', "", $ret);
 
 
 
 
 
 
 
 
92
  return $ret;
93
  }
94
 
 
95
  // Protocol relative urls (e.g. "//example.org/style.css")
96
  if (strpos($url, '//') === 0) {
97
  $ret .= substr($url, 2);
@@ -431,14 +457,14 @@ class Helpers
431
  $host = "";
432
  $path = "";
433
  $file = "";
 
434
 
435
  $arr = parse_url($url);
436
  if ( isset($arr["scheme"]) ) {
437
  $arr["scheme"] = mb_strtolower($arr["scheme"]);
438
  }
439
 
440
- // Exclude windows drive letters...
441
- if (isset($arr["scheme"]) && $arr["scheme"] !== "file" && strlen($arr["scheme"]) > 1) {
442
  $protocol = $arr["scheme"] . "://";
443
 
444
  if (isset($arr["user"])) {
@@ -480,42 +506,32 @@ class Helpers
480
 
481
  } else {
482
 
483
- $i = mb_stripos($url, "file://");
484
- if ($i !== false) {
485
- $url = mb_substr($url, $i + 7);
486
- }
487
-
488
- $protocol = ""; // "file://"; ? why doesn't this work... It's because of
489
- // network filenames like //COMPU/SHARENAME
490
-
491
  $host = ""; // localhost, really
492
- $file = basename($url);
493
-
494
- $path = dirname($url);
495
-
496
- // Check that the path exists
497
- if ($path !== false) {
498
- $path .= '/';
499
 
 
 
 
 
500
  } else {
501
- // generate a url to access the file if no real path found.
502
- $protocol = isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] === 'on' ? 'https://' : 'http://';
503
-
504
- $host = isset($_SERVER["HTTP_HOST"]) ? $_SERVER["HTTP_HOST"] : php_uname("n");
505
 
506
- if (substr($arr["path"], 0, 1) === '/') {
507
- $path = dirname($arr["path"]);
508
- } else {
509
- $path = '/' . rtrim(dirname($_SERVER["SCRIPT_NAME"]), '/') . '/' . $arr["path"];
510
- }
511
  }
 
 
 
512
  }
513
 
514
  $ret = [$protocol, $host, $path, $file,
515
  "protocol" => $protocol,
516
  "host" => $host,
517
  "path" => $path,
518
- "file" => $file];
 
519
  return $ret;
520
  }
521
 
@@ -576,12 +592,12 @@ class Helpers
576
  {
577
  if ($c <= 0x7F) {
578
  return chr($c);
579
- } else if ($c <= 0x7FF) {
580
  return chr(0xC0 | $c >> 6) . chr(0x80 | $c & 0x3F);
581
- } else if ($c <= 0xFFFF) {
582
  return chr(0xE0 | $c >> 12) . chr(0x80 | $c >> 6 & 0x3F)
583
  . chr(0x80 | $c & 0x3F);
584
- } else if ($c <= 0x10FFFF) {
585
  return chr(0xF0 | $c >> 18) . chr(0x80 | $c >> 12 & 0x3F)
586
  . chr(0x80 | $c >> 6 & 0x3F)
587
  . chr(0x80 | $c & 0x3F);
@@ -868,9 +884,9 @@ class Helpers
868
  * - curl: if allow_url_fopen is false and curl is available
869
  *
870
  * @param string $uri
871
- * @param resource $context (ignored if curl is used)
872
  * @param int $offset
873
- * @param int $maxlen (ignored if curl is used)
874
  * @return string[]
875
  */
876
  public static function getFileContent($uri, $context = null, $offset = 0, $maxlen = null)
@@ -878,12 +894,13 @@ class Helpers
878
  $content = null;
879
  $headers = null;
880
  [$protocol] = Helpers::explode_url($uri);
881
- $is_local_path = ($protocol === "" || $protocol === "file://");
 
882
 
883
  set_error_handler([self::class, 'record_warnings']);
884
 
885
  try {
886
- if ($is_local_path || ini_get('allow_url_fopen')) {
887
  if ($is_local_path === false) {
888
  $uri = Helpers::encodeURI($uri);
889
  }
@@ -899,18 +916,61 @@ class Helpers
899
  $headers = $http_response_header;
900
  }
901
 
902
- } elseif (function_exists('curl_exec')) {
903
  $curl = curl_init($uri);
904
 
905
- //TODO: use $context to define additional curl options
906
- curl_setopt($curl, CURLOPT_TIMEOUT, 10);
907
- curl_setopt($curl, CURLOPT_CONNECTTIMEOUT, 10);
908
  curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
909
  curl_setopt($curl, CURLOPT_HEADER, true);
910
  if ($offset > 0) {
911
  curl_setopt($curl, CURLOPT_RESUME_FROM, $offset);
912
  }
913
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
914
  $data = curl_exec($curl);
915
 
916
  if ($data !== false && !curl_errno($curl)) {
57
  public static function build_url($protocol, $host, $base_path, $url)
58
  {
59
  $protocol = mb_strtolower($protocol);
60
+ if (empty($protocol)) {
61
+ $protocol = "file://";
62
+ }
63
  if ($url === "") {
64
+ return null;
 
65
  }
66
 
67
+ $url_lc = mb_strtolower($url);
68
+
69
  // Is the url already fully qualified, a Data URI, or a reference to a named anchor?
70
  // File-protocol URLs may require additional processing (e.g. for URLs with a relative path)
71
+ if (
72
+ (
73
+ mb_strpos($url_lc, "://") !== false
74
+ && !in_array(substr($url_lc, 0, 7), ["file://", "phar://"], true)
75
+ )
76
+ || mb_substr($url_lc, 0, 1) === "#"
77
+ || mb_strpos($url_lc, "data:") === 0
78
+ || mb_strpos($url_lc, "mailto:") === 0
79
+ || mb_strpos($url_lc, "tel:") === 0
80
+ ) {
81
  return $url;
82
  }
83
 
84
+ $res = "";
85
+ if (strpos($url_lc, "file://") === 0) {
86
  $url = substr($url, 7);
87
+ $protocol = "file://";
88
+ } elseif (strpos($url_lc, "phar://") === 0) {
89
+ $res = substr($url, strpos($url_lc, ".phar")+5);
90
+ $url = substr($url, 7, strpos($url_lc, ".phar")-2);
91
+ $protocol = "phar://";
92
  }
93
 
94
  $ret = "";
 
 
 
95
 
96
+ $is_local_path = in_array($protocol, ["file://", "phar://"], true);
97
+
98
+ if ($is_local_path) {
99
  //On Windows local file, an abs path can begin also with a '\' or a drive letter and colon
100
  //drive: followed by a relative path would be a drive specific default folder.
101
  //not known in php app code, treat as abs path
106
  }
107
  $ret .= $url;
108
  $ret = preg_replace('/\?(.*)$/', "", $ret);
109
+
110
+ $filepath = realpath($ret);
111
+ if ($filepath === false) {
112
+ return null;
113
+ }
114
+
115
+ $ret = "$protocol$filepath$res";
116
+
117
  return $ret;
118
  }
119
 
120
+ $ret = $protocol;
121
  // Protocol relative urls (e.g. "//example.org/style.css")
122
  if (strpos($url, '//') === 0) {
123
  $ret .= substr($url, 2);
457
  $host = "";
458
  $path = "";
459
  $file = "";
460
+ $res = "";
461
 
462
  $arr = parse_url($url);
463
  if ( isset($arr["scheme"]) ) {
464
  $arr["scheme"] = mb_strtolower($arr["scheme"]);
465
  }
466
 
467
+ if (isset($arr["scheme"]) && $arr["scheme"] !== "file" && $arr["scheme"] !== "phar" && strlen($arr["scheme"]) > 1) {
 
468
  $protocol = $arr["scheme"] . "://";
469
 
470
  if (isset($arr["user"])) {
506
 
507
  } else {
508
 
509
+ $protocol = "";
 
 
 
 
 
 
 
510
  $host = ""; // localhost, really
 
 
 
 
 
 
 
511
 
512
+ $i = mb_stripos($url, "://");
513
+ if ($i !== false) {
514
+ $protocol = mb_strtolower(mb_substr($url, 0, $i + 3));
515
+ $url = mb_substr($url, $i + 3);
516
  } else {
517
+ $protocol = "file://";
518
+ }
 
 
519
 
520
+ if ($protocol === "phar://") {
521
+ $res = substr($url, stripos($url, ".phar")+5);
522
+ $url = substr($url, 7, stripos($url, ".phar")-2);
 
 
523
  }
524
+
525
+ $file = basename($url);
526
+ $path = dirname($url) . "/";
527
  }
528
 
529
  $ret = [$protocol, $host, $path, $file,
530
  "protocol" => $protocol,
531
  "host" => $host,
532
  "path" => $path,
533
+ "file" => $file,
534
+ "resource" => $res];
535
  return $ret;
536
  }
537
 
592
  {
593
  if ($c <= 0x7F) {
594
  return chr($c);
595
+ } elseif ($c <= 0x7FF) {
596
  return chr(0xC0 | $c >> 6) . chr(0x80 | $c & 0x3F);
597
+ } elseif ($c <= 0xFFFF) {
598
  return chr(0xE0 | $c >> 12) . chr(0x80 | $c >> 6 & 0x3F)
599
  . chr(0x80 | $c & 0x3F);
600
+ } elseif ($c <= 0x10FFFF) {
601
  return chr(0xF0 | $c >> 18) . chr(0x80 | $c >> 12 & 0x3F)
602
  . chr(0x80 | $c >> 6 & 0x3F)
603
  . chr(0x80 | $c & 0x3F);
884
  * - curl: if allow_url_fopen is false and curl is available
885
  *
886
  * @param string $uri
887
+ * @param resource $context
888
  * @param int $offset
889
+ * @param int $maxlen
890
  * @return string[]
891
  */
892
  public static function getFileContent($uri, $context = null, $offset = 0, $maxlen = null)
894
  $content = null;
895
  $headers = null;
896
  [$protocol] = Helpers::explode_url($uri);
897
+ $is_local_path = in_array(strtolower($protocol), ["", "file://", "phar://"], true);
898
+ $can_use_curl = in_array(strtolower($protocol), ["http://", "https://"], true);
899
 
900
  set_error_handler([self::class, 'record_warnings']);
901
 
902
  try {
903
+ if ($is_local_path || ini_get('allow_url_fopen') || !$can_use_curl) {
904
  if ($is_local_path === false) {
905
  $uri = Helpers::encodeURI($uri);
906
  }
916
  $headers = $http_response_header;
917
  }
918
 
919
+ } elseif ($can_use_curl && function_exists('curl_exec')) {
920
  $curl = curl_init($uri);
921
 
 
 
 
922
  curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
923
  curl_setopt($curl, CURLOPT_HEADER, true);
924
  if ($offset > 0) {
925
  curl_setopt($curl, CURLOPT_RESUME_FROM, $offset);
926
  }
927
 
928
+ if ($maxlen > 0) {
929
+ curl_setopt($curl, CURLOPT_BUFFERSIZE, 128);
930
+ curl_setopt($curl, CURLOPT_NOPROGRESS, false);
931
+ curl_setopt($curl, CURLOPT_PROGRESSFUNCTION, function ($res, $download_size_total, $download_size, $upload_size_total, $upload_size) use ($maxlen) {
932
+ return ($download_size > $maxlen) ? 1 : 0;
933
+ });
934
+ }
935
+
936
+ $context_options = [];
937
+ if (!is_null($context)) {
938
+ $context_options = stream_context_get_options($context);
939
+ }
940
+ foreach ($context_options as $stream => $options) {
941
+ foreach ($options as $option => $value) {
942
+ $key = strtolower($stream) . ":" . strtolower($option);
943
+ switch ($key) {
944
+ case "curl:curl_verify_ssl_host":
945
+ curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, !$value ? 0 : 2);
946
+ break;
947
+ case "curl:max_redirects":
948
+ curl_setopt($curl, CURLOPT_MAXREDIRS, $value);
949
+ break;
950
+ case "http:follow_location":
951
+ curl_setopt($curl, CURLOPT_FOLLOWLOCATION, $value);
952
+ break;
953
+ case "http:header":
954
+ if (is_string($value)) {
955
+ curl_setopt($curl, CURLOPT_HTTPHEADER, [$value]);
956
+ } else {
957
+ curl_setopt($curl, CURLOPT_HTTPHEADER, $value);
958
+ }
959
+ break;
960
+ case "http:timeout":
961
+ curl_setopt($curl, CURLOPT_TIMEOUT, $value);
962
+ break;
963
+ case "http:user_agent":
964
+ curl_setopt($curl, CURLOPT_USERAGENT, $value);
965
+ break;
966
+ case "curl:curl_verify_ssl_peer":
967
+ case "ssl:verify_peer":
968
+ curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, $value);
969
+ break;
970
+ }
971
+ }
972
+ }
973
+
974
  $data = curl_exec($curl);
975
 
976
  if ($data !== false && !curl_errno($curl)) {
vendor/dompdf/dompdf/src/Image/Cache.php CHANGED
@@ -9,7 +9,7 @@
9
  */
10
  namespace Dompdf\Image;
11
 
12
- use Dompdf\Dompdf;
13
  use Dompdf\Helpers;
14
  use Dompdf\Exception\ImageException;
15
 
@@ -43,13 +43,6 @@ class Cache
43
 
44
  public static $error_message = "Image not found or type unknown";
45
 
46
- /**
47
- * Current dompdf instance
48
- *
49
- * @var Dompdf
50
- */
51
- protected static $_dompdf;
52
-
53
  /**
54
  * Resolve and fetch an image for use.
55
  *
@@ -57,130 +50,124 @@ class Cache
57
  * @param string $protocol Default protocol if none specified in $url
58
  * @param string $host Default host if none specified in $url
59
  * @param string $base_path Default path if none specified in $url
60
- * @param Dompdf $dompdf The Dompdf instance
61
  *
62
- * @throws ImageException
63
- * @return array An array with two elements: The local path to the image and the image extension
64
  */
65
- static function resolve_url($url, $protocol, $host, $base_path, Dompdf $dompdf)
66
  {
67
- self::$_dompdf = $dompdf;
68
-
69
- $protocol = mb_strtolower($protocol);
70
- $parsed_url = Helpers::explode_url($url);
71
  $message = null;
72
-
73
- $remote = ($protocol && $protocol !== "file://") || ($parsed_url['protocol'] !== "");
74
-
75
- $data_uri = strpos($parsed_url['protocol'], "data:") === 0;
76
- $full_url = null;
77
- $enable_remote = $dompdf->getOptions()->getIsRemoteEnabled();
78
- $tempfile = false;
79
-
80
  try {
 
81
 
82
- // Remote not allowed and is not DataURI
83
- if (!$enable_remote && $remote && !$data_uri) {
84
- throw new ImageException("Remote file access is disabled.", E_WARNING);
85
  }
86
-
87
- // remote allowed or DataURI
88
- if (($enable_remote && $remote) || $data_uri) {
89
- // Download remote files to a temporary directory
90
- $full_url = Helpers::build_url($protocol, $host, $base_path, $url);
91
 
92
- // From cache
93
- if (isset(self::$_cache[$full_url])) {
94
- $resolved_url = self::$_cache[$full_url];
95
- } // From remote
96
- else {
97
- $tmp_dir = $dompdf->getOptions()->getTempDir();
98
- if (($resolved_url = @tempnam($tmp_dir, "ca_dompdf_img_")) === false) {
99
- throw new ImageException("Unable to create temporary image in " . $tmp_dir, E_WARNING);
 
 
 
 
 
100
  }
101
- $tempfile = $resolved_url;
102
- $image = null;
103
 
104
- if ($data_uri) {
105
- if ($parsed_data_uri = Helpers::parse_data_uri($url)) {
106
- $image = $parsed_data_uri['data'];
107
- }
108
- } else {
109
- list($image, $http_response_header) = Helpers::getFileContent($full_url, $dompdf->getHttpContext());
110
- }
 
 
 
111
 
112
- // Image not found or invalid
113
- if ($image === null) {
114
- $msg = ($data_uri ? "Data-URI could not be parsed" : "Image not found");
115
- throw new ImageException($msg, E_WARNING);
116
- } // Image found, put in cache and process
117
- else {
118
- //e.g. fetch.php?media=url.jpg&cache=1
119
- //- Image file name might be one of the dynamic parts of the url, don't strip off!
120
- //- a remote url does not need to have a file extension at all
121
- //- local cached file does not have a matching file extension
122
- //Therefore get image type from the content
123
- if (@file_put_contents($resolved_url, $image) === false) {
124
- throw new ImageException("Unable to create temporary image in " . $tmp_dir, E_WARNING);
125
- }
126
  }
 
 
127
  }
128
- } // Not remote, local image
129
- else {
130
- $resolved_url = Helpers::build_url($protocol, $host, $base_path, $url);
131
 
132
- if ($protocol === "" || $protocol === "file://") {
133
- $realfile = realpath($resolved_url);
134
-
135
- $rootDir = realpath($dompdf->getOptions()->getRootDir());
136
- if (strpos($realfile, $rootDir) !== 0) {
137
- $chroot = $dompdf->getOptions()->getChroot();
138
- $chrootValid = false;
139
- foreach ($chroot as $chrootPath) {
140
- $chrootPath = realpath($chrootPath);
141
- if ($chrootPath !== false && strpos($realfile, $chrootPath) === 0) {
142
- $chrootValid = true;
143
- break;
144
- }
145
- }
146
- if ($chrootValid !== true) {
147
- throw new ImageException("Permission denied on $resolved_url. The file could not be found under the paths specified by Options::chroot.", E_WARNING);
148
- }
149
- }
150
-
151
- if (!$realfile) {
152
- throw new ImageException("File '$realfile' not found.", E_WARNING);
153
- }
154
-
155
- $resolved_url = $realfile;
156
  }
 
 
157
  }
158
 
159
  // Check if the local file is readable
160
  if (!is_readable($resolved_url) || !filesize($resolved_url)) {
161
  throw new ImageException("Image not readable or empty", E_WARNING);
162
- } // Check is the file is an image
163
- else {
164
- list($width, $height, $type) = Helpers::dompdf_getimagesize($resolved_url, $dompdf->getHttpContext());
 
 
 
 
165
 
166
- // Known image type
167
- if ($width && $height && in_array($type, ["gif", "png", "jpeg", "bmp", "svg","webp"], true)) {
168
- //Don't put replacement image into cache - otherwise it will be deleted on cache cleanup.
169
- //Only execute on successful caching of remote image.
170
- if ($enable_remote && $remote || $data_uri) {
171
- self::$_cache[$full_url] = $resolved_url;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
172
  }
173
- } // Unknown image type
174
- else {
175
- throw new ImageException("Image type unknown", E_WARNING);
176
  }
 
177
  }
178
  } catch (ImageException $e) {
179
  if ($tempfile) {
180
  unlink($tempfile);
181
  }
182
  $resolved_url = self::$broken_image;
183
- $type = "png";
184
  $message = self::$error_message;
185
  Helpers::record_warnings($e->getCode(), $e->getMessage() . " \n $url", $e->getFile(), $e->getLine());
186
  self::$_cache[$full_url] = $resolved_url;
@@ -229,7 +216,9 @@ class Cache
229
  if ($debugPng) {
230
  print "[clear unlink $file]";
231
  }
232
- unlink($file);
 
 
233
  }
234
 
235
  foreach (self::$tempImages as $versions) {
9
  */
10
  namespace Dompdf\Image;
11
 
12
+ use Dompdf\Options;
13
  use Dompdf\Helpers;
14
  use Dompdf\Exception\ImageException;
15
 
43
 
44
  public static $error_message = "Image not found or type unknown";
45
 
 
 
 
 
 
 
 
46
  /**
47
  * Resolve and fetch an image for use.
48
  *
50
  * @param string $protocol Default protocol if none specified in $url
51
  * @param string $host Default host if none specified in $url
52
  * @param string $base_path Default path if none specified in $url
53
+ * @param Options $options An instance of Dompdf\Options
54
  *
55
+ * @return array An array with three elements: The local path to the image, the image
56
+ * extension, and an error message if the image could not be cached
57
  */
58
+ static function resolve_url($url, $protocol, $host, $base_path, Options $options)
59
  {
60
+ $tempfile = null;
61
+ $resolved_url = null;
62
+ $type = null;
 
63
  $message = null;
64
+
 
 
 
 
 
 
 
65
  try {
66
+ $full_url = Helpers::build_url($protocol, $host, $base_path, $url);
67
 
68
+ if ($full_url === null) {
69
+ throw new ImageException("Unable to parse image URL $url.", E_WARNING);
 
70
  }
 
 
 
 
 
71
 
72
+ $parsed_url = Helpers::explode_url($full_url);
73
+ $protocol = strtolower($parsed_url["protocol"]);
74
+ $is_data_uri = strpos($protocol, "data:") === 0;
75
+
76
+ if (!$is_data_uri) {
77
+ $allowed_protocols = $options->getAllowedProtocols();
78
+ if (!array_key_exists($protocol, $allowed_protocols)) {
79
+ throw new ImageException("Permission denied on $url. The communication protocol is not supported.", E_WARNING);
80
+ }
81
+ foreach ($allowed_protocols[$protocol]["rules"] as $rule) {
82
+ [$result, $message] = $rule($full_url);
83
+ if (!$result) {
84
+ throw new ImageException("Error loading $url: $message", E_WARNING);
85
  }
86
+ }
87
+ }
88
 
89
+ if ($protocol === "file://") {
90
+ $resolved_url = $full_url;
91
+ } elseif (isset(self::$_cache[$full_url])) {
92
+ $resolved_url = self::$_cache[$full_url];
93
+ } else {
94
+ $tmp_dir = $options->getTempDir();
95
+ if (($resolved_url = @tempnam($tmp_dir, "ca_dompdf_img_")) === false) {
96
+ throw new ImageException("Unable to create temporary image in " . $tmp_dir, E_WARNING);
97
+ }
98
+ $tempfile = $resolved_url;
99
 
100
+ $image = null;
101
+ if ($is_data_uri) {
102
+ if (($parsed_data_uri = Helpers::parse_data_uri($url)) !== false) {
103
+ $image = $parsed_data_uri["data"];
 
 
 
 
 
 
 
 
 
 
104
  }
105
+ } else {
106
+ list($image, $http_response_header) = Helpers::getFileContent($full_url, $options->getHttpContext());
107
  }
 
 
 
108
 
109
+ // Image not found or invalid
110
+ if ($image === null) {
111
+ $msg = ($is_data_uri ? "Data-URI could not be parsed" : "Image not found");
112
+ throw new ImageException($msg, E_WARNING);
113
+ }
114
+
115
+ if (@file_put_contents($resolved_url, $image) === false) {
116
+ throw new ImageException("Unable to create temporary image in " . $tmp_dir, E_WARNING);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
117
  }
118
+
119
+ self::$_cache[$full_url] = $resolved_url;
120
  }
121
 
122
  // Check if the local file is readable
123
  if (!is_readable($resolved_url) || !filesize($resolved_url)) {
124
  throw new ImageException("Image not readable or empty", E_WARNING);
125
+ }
126
+
127
+ list($width, $height, $type) = Helpers::dompdf_getimagesize($resolved_url, $options->getHttpContext());
128
+
129
+ if (($width && $height && in_array($type, ["gif", "png", "jpeg", "bmp", "svg","webp"], true)) === false) {
130
+ throw new ImageException("Image type unknown", E_WARNING);
131
+ }
132
 
133
+ if ($type === "svg") {
134
+ $parser = xml_parser_create("utf-8");
135
+ xml_parser_set_option($parser, XML_OPTION_CASE_FOLDING, false);
136
+ xml_set_element_handler(
137
+ $parser,
138
+ function ($parser, $name, $attributes) use ($options, $parsed_url, $full_url) {
139
+ if ($name === "image") {
140
+ $attributes = array_change_key_case($attributes, CASE_LOWER);
141
+ $url = $attributes["xlink:href"] ?? $attributes["href"];
142
+ if (!empty($url)) {
143
+ $inner_full_url = Helpers::build_url($parsed_url["protocol"], $parsed_url["host"], $parsed_url["path"], $url);
144
+ if ($inner_full_url === $full_url) {
145
+ throw new ImageException("SVG self-reference is not allowed", E_WARNING);
146
+ }
147
+ [$resolved_url, $type, $message] = self::resolve_url($url, $parsed_url["protocol"], $parsed_url["host"], $parsed_url["path"], $options);
148
+ if (!empty($message)) {
149
+ throw new ImageException("This SVG document references a restricted resource. $message", E_WARNING);
150
+ }
151
+ }
152
+ }
153
+ },
154
+ false
155
+ );
156
+
157
+ if (($fp = fopen($resolved_url, "r")) !== false) {
158
+ while ($line = fread($fp, 8192)) {
159
+ xml_parse($parser, $line, false);
160
  }
161
+ fclose($fp);
 
 
162
  }
163
+ xml_parser_free($parser);
164
  }
165
  } catch (ImageException $e) {
166
  if ($tempfile) {
167
  unlink($tempfile);
168
  }
169
  $resolved_url = self::$broken_image;
170
+ list($width, $height, $type) = Helpers::dompdf_getimagesize($resolved_url, $options->getHttpContext());
171
  $message = self::$error_message;
172
  Helpers::record_warnings($e->getCode(), $e->getMessage() . " \n $url", $e->getFile(), $e->getLine());
173
  self::$_cache[$full_url] = $resolved_url;
216
  if ($debugPng) {
217
  print "[clear unlink $file]";
218
  }
219
+ if (file_exists($file)) {
220
+ unlink($file);
221
+ }
222
  }
223
 
224
  foreach (self::$tempImages as $versions) {
vendor/dompdf/dompdf/src/LineBox.php CHANGED
@@ -266,7 +266,7 @@ class LineBox
266
  /**
267
  * @param AbstractFrameDecorator $frame
268
  */
269
- public function add_frame(Frame $frame)
270
  {
271
  $this->_frames[] = $frame;
272
 
@@ -377,7 +377,7 @@ class LineBox
377
  *
378
  * @return float
379
  */
380
- public function recalculate_width()
381
  {
382
  $width = 0.0;
383
 
@@ -391,7 +391,7 @@ class LineBox
391
  /**
392
  * @return string
393
  */
394
- public function __toString()
395
  {
396
  $props = ["wc", "y", "w", "h", "left", "right", "br"];
397
  $s = "";
266
  /**
267
  * @param AbstractFrameDecorator $frame
268
  */
269
+ public function add_frame(Frame $frame): void
270
  {
271
  $this->_frames[] = $frame;
272
 
377
  *
378
  * @return float
379
  */
380
+ public function recalculate_width(): float
381
  {
382
  $width = 0.0;
383
 
391
  /**
392
  * @return string
393
  */
394
+ public function __toString(): string
395
  {
396
  $props = ["wc", "y", "w", "h", "left", "right", "br"];
397
  $s = "";
vendor/dompdf/dompdf/src/Options.php CHANGED
@@ -13,7 +13,7 @@ class Options
13
  /**
14
  * The location of a temporary directory.
15
  *
16
- * The directory specified must be writable by the webserver process.
17
  * The temporary directory is required to download remote images and when
18
  * using the PFDLib back end.
19
  *
@@ -25,7 +25,7 @@ class Options
25
  * The location of the DOMPDF font directory
26
  *
27
  * The location of the directory where DOMPDF will store fonts and font metrics
28
- * Note: This directory must exist and be writable by the webserver process.
29
  *
30
  * @var string
31
  */
@@ -37,7 +37,7 @@ class Options
37
  * This directory contains the cached font metrics for the fonts used by DOMPDF.
38
  * This directory can be the same as $fontDir
39
  *
40
- * Note: This directory must exist and be writable by the webserver process.
41
  *
42
  * @var string
43
  */
@@ -46,10 +46,10 @@ class Options
46
  /**
47
  * dompdf's "chroot"
48
  *
49
- * Prevents dompdf from accessing system files or other files on the webserver.
50
- * All local files opened by dompdf must be in a subdirectory of this directory
51
- * or array of directories.
52
- * DO NOT set it to '/' since this could allow an attacker to use dompdf to
53
  * read any files on the server. This should be an absolute path.
54
  *
55
  * ==== IMPORTANT ====
@@ -62,20 +62,31 @@ class Options
62
  */
63
  private $chroot;
64
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
65
  /**
66
  * @var string
67
 
13
  /**
14
  * The location of a temporary directory.
15
  *
16
+ * The directory specified must be writable by the executing process.
17
  * The temporary directory is required to download remote images and when
18
  * using the PFDLib back end.
19
  *
25
  * The location of the DOMPDF font directory
26
  *
27
  * The location of the directory where DOMPDF will store fonts and font metrics
28
+ * Note: This directory must exist and be writable by the executing process.
29
  *
30
  * @var string
31
  */
37
  * This directory contains the cached font metrics for the fonts used by DOMPDF.
38
  * This directory can be the same as $fontDir
39
  *
40
+ * Note: This directory must exist and be writable by the executing process.
41
  *
42
  * @var string
43
  */
46
  /**
47
  * dompdf's "chroot"
48
  *
49
+ * Utilized by Dompdf's default file:// protocol URI validation rule.
50
+ * All local files opened by dompdf must be in a subdirectory of the directory
51
+ * or directories specified by this option.
52
+ * DO NOT set this value to '/' since this could allow an attacker to use dompdf to
53
  * read any files on the server. This should be an absolute path.
54
  *
55
  * ==== IMPORTANT ====
62
  */
63
  private $chroot;
64
 
65
+ /**
66
+ * Protocol whitelist
67
+ *
68
+ * Protocols and PHP wrappers allowed in URIs, and the validation rules
69
+ * that determine if a resouce may be loaded. Full support is not guaranteed
70
+ * for the protocols/wrappers specified
71
+ * by this array.
72
+ *
73
+ * @var array
74
+ */
75
+ private $allowedProtocols = [
76
+ "file://" => ["rules" => []],
77
+ "http://" => ["rules" => []],
78
+ "https://" => ["rules" => []]
79
+ ];
80
+
81
  /**
82
  * @var string
83