{"sha":"f93be8263f4deab8fb8884db7e03a1767a27bbad","node_id":"C_kwDOBTp1atoAKGY5M2JlODI2M2Y0ZGVhYjhmYjg4ODRkYjdlMDNhMTc2N2EyN2JiYWQ","commit":{"author":{"name":"matti","email":"matti@belle-nuit.com","date":"2026-10-23T12:45:16Z"},"committer":{"name":"matti","email":"matti@belle-nuit.com","date":"2026-10-23T12:45:16Z"},"message":"hotfix PHP2Shell update","tree":{"sha":"ecae5212a0bf24ae9c2720358c3eb63cb64c1487","url":"https://api.github.com/repos/bellenuit/sofawiki/git/trees/ecae5212a0bf24ae9c2720358c3eb63cb64c1487"},"url":"https://api.github.com/repos/bellenuit/sofawiki/git/commits/f93be8263f4deab8fb8884db7e03a1767a27bbad","comment_count":0,"verification":{"verified":false,"reason":"unsigned","signature":null,"payload":null,"verified_at":null}},"url":"https://api.github.com/repos/bellenuit/sofawiki/commits/f93be8263f4deab8fb8884db7e03a1767a27bbad","html_url":"https://github.com/bellenuit/sofawiki/commit/f93be8263f4deab8fb8884db7e03a1767a27bbad","comments_url":"https://api.github.com/repos/bellenuit/sofawiki/commits/f93be8263f4deab8fb8884db7e03a1767a27bbad/comments","author":{"login":"bellenuit","id":18127428,"node_id":"MDQ6VXNlcjE4MTI3NDI4","avatar_url":"https://avatars.githubusercontent.com/u/18127428?v=4","gravatar_id":"","url":"https://api.github.com/users/bellenuit","html_url":"https://github.com/bellenuit","followers_url":"https://api.github.com/users/bellenuit/followers","following_url":"https://api.github.com/users/bellenuit/following{/other_user}","gists_url":"https://api.github.com/users/bellenuit/gists{/gist_id}","starred_url":"https://api.github.com/users/bellenuit/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/bellenuit/subscriptions","organizations_url":"https://api.github.com/users/bellenuit/orgs","repos_url":"https://api.github.com/users/bellenuit/repos","events_url":"https://api.github.com/users/bellenuit/events{/privacy}","received_events_url":"https://api.github.com/users/bellenuit/received_events","type":"User","user_view_type":"public","site_admin":false},"committer":{"login":"bellenuit","id":18127428,"node_id":"MDQ6VXNlcjE4MTI3NDI4","avatar_url":"https://avatars.githubusercontent.com/u/18127428?v=4","gravatar_id":"","url":"https://api.github.com/users/bellenuit","html_url":"https://github.com/bellenuit","followers_url":"https://api.github.com/users/bellenuit/followers","following_url":"https://api.github.com/users/bellenuit/following{/other_user}","gists_url":"https://api.github.com/users/bellenuit/gists{/gist_id}","starred_url":"https://api.github.com/users/bellenuit/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/bellenuit/subscriptions","organizations_url":"https://api.github.com/users/bellenuit/orgs","repos_url":"https://api.github.com/users/bellenuit/repos","events_url":"https://api.github.com/users/bellenuit/events{/privacy}","received_events_url":"https://api.github.com/users/bellenuit/received_events","type":"User","user_view_type":"public","site_admin":false},"parents":[{"sha":"92b6e463e0d71a7aa514c8ca9c8128b9f9cb6680","url":"https://api.github.com/repos/bellenuit/sofawiki/commits/92b6e463e0d71a7aa514c8ca9c8128b9f9cb6680","html_url":"https://github.com/bellenuit/sofawiki/commit/92b6e463e0d71a7aa514c8ca9c8128b9f9cb6680"}],"stats":{"total":12200,"additions":743,"deletions":11457},"files":[{"sha":"2af1a9af3ab14e5881482994977776557fedf253","filename":"inc/fulltext.php","status":"modified","additions":2,"deletions":2,"changes":4,"blob_url":"https://github.com/bellenuit/sofawiki/blob/f93be8263f4deab8fb8884db7e03a1767a27bbad/inc%2Ffulltext.php","raw_url":"https://github.com/bellenuit/sofawiki/raw/f93be8263f4deab8fb8884db7e03a1767a27bbad/inc%2Ffulltext.php","contents_url":"https://api.github.com/repos/bellenuit/sofawiki/contents/inc%2Ffulltext.php?ref=f93be8263f4deab8fb8884db7e03a1767a27bbad","patch":"@@ -333,7 +333,7 @@ function swQueryStar($q)\n \t\t\tcase 'or':\n \t\t\tcase 'not':\n \t\t\tcase 'near':\n-\t\t\tcase '(';\n+\t\t\tcase '(':\n \t\t\tcase ')': $list[] = $w; break;\n \t\t\tcase '': break;\n \t\t\tdefault: if (substr($w,-1) == '*') $list[] = $w; else $list[] = $w.'*';\n@@ -355,7 +355,7 @@ function swQuerySoundex($q)\n \t\t\tcase 'or':\n \t\t\tcase 'not':\n \t\t\tcase 'near':\n-\t\t\tcase '(';\n+\t\t\tcase '(':\n \t\t\tcase ')': $list[] = $w; break;\n \t\t\tcase '': break;\n \t\t\tdefault: $list[] = soundex(str_replace('*','',$w));"},{"sha":"88b3fc33d5708e7ad6a140080bb5562600af4b8f","filename":"inc/functions/tinyps.php","status":"modified","additions":1,"deletions":1,"changes":2,"blob_url":"https://github.com/bellenuit/sofawiki/blob/f93be8263f4deab8fb8884db7e03a1767a27bbad/inc%2Ffunctions%2Ftinyps.php","raw_url":"https://github.com/bellenuit/sofawiki/raw/f93be8263f4deab8fb8884db7e03a1767a27bbad/inc%2Ffunctions%2Ftinyps.php","contents_url":"https://api.github.com/repos/bellenuit/sofawiki/contents/inc%2Ffunctions%2Ftinyps.php?ref=f93be8263f4deab8fb8884db7e03a1767a27bbad","patch":"@@ -38,7 +38,7 @@ function dowork($args)\n  \t\t\t$result .= '\n \n <script>rpnFontURLs = '.$fonts.'</script>\n-<script src=\"inc/skins/tinyps134.js\"></script>\n+<script src=\"inc/skins/tinyps135.js\"></script>\n <script src=\"inc/skins/tinyps-extensions.js\"></script>';\n \n  \t\t    $result .= '"},{"sha":"1c82017399dd8577329f88d4a17ce87d17b495c7","filename":"inc/relationfilter.php","status":"modified","additions":11,"deletions":3,"changes":14,"blob_url":"https://github.com/bellenuit/sofawiki/blob/f93be8263f4deab8fb8884db7e03a1767a27bbad/inc%2Frelationfilter.php","raw_url":"https://github.com/bellenuit/sofawiki/raw/f93be8263f4deab8fb8884db7e03a1767a27bbad/inc%2Frelationfilter.php","contents_url":"https://api.github.com/repos/bellenuit/sofawiki/contents/inc%2Frelationfilter.php?ref=f93be8263f4deab8fb8884db7e03a1767a27bbad","patch":"@@ -2382,7 +2382,10 @@ function swRelationIndexSearch($filter, $globals = array())\n \t\tif (count($journal)>10000)\n \t\t{\n \t\t\t$q = 'BEGIN;'.PHP_EOL.join(PHP_EOL,$journal).PHP_EOL.'COMMIT;';\n-\t\t\t$fielddb->exec($q);\n+\t\t\tif (!$fielddb->exec($q))\n+\t\t\t{\n+\t\t\t    throw new swDbaError('swDba fielddb commit error1 '.$fielddb->lastErrorMsg());\n+\t\t\t}\n \t\t\techotime('synced '.count($journal));\n \t\t\t$journal = array();\n \t\t}\n@@ -2417,6 +2420,7 @@ function swRelationIndexSearch($filter, $globals = array())\n \t\t\t\tforeach($list as $v)\n \t\t\t\t{\n \t\t\t\t\t$lastv = $v = $fielddb->escapeString($v);\n+\t\t\t\t\t$key = $fielddb->escapeString($key);\n \t\t\t\t\t$journal[]= \"INSERT INTO fields (revision,row,key,value) VALUES ('$i','$j','$key','$v');\";\n \t\t\t\t\t$j++;\n \t\t\t\t}\n@@ -2450,8 +2454,12 @@ function swRelationIndexSearch($filter, $globals = array())\n \t\n \tif (count($journal))\n \t{\n-\t\t$q = 'BEGIN;'.PHP_EOL.join(PHP_EOL,$journal).PHP_EOL.'COMMIT;';\n-\t\t$fielddb->exec($q);\n+\t\t$q = 'BEGIN; '.PHP_EOL.join(PHP_EOL,$journal).PHP_EOL.' COMMIT;';\n+\t    if (!$fielddb->exec($q))\n+\t\t{\n+\t\t   throw new swDbaError('swDba fielddb commit error2 '.$fielddb->lastErrorMsg().\" \".$q);\n+\t\t}\t\t\n+\t\t\n \t\techotime('synced '.count($journal));\n \t}\n \t"},{"sha":"067fb4a53ccaca62eed7a4d03e8781d5a5d7b044","filename":"inc/skins/KugiBlack.ttf","status":"added","additions":0,"deletions":0,"changes":0,"blob_url":"https://github.com/bellenuit/sofawiki/blob/f93be8263f4deab8fb8884db7e03a1767a27bbad/inc%2Fskins%2FKugiBlack.ttf","raw_url":"https://github.com/bellenuit/sofawiki/raw/f93be8263f4deab8fb8884db7e03a1767a27bbad/inc%2Fskins%2FKugiBlack.ttf","contents_url":"https://api.github.com/repos/bellenuit/sofawiki/contents/inc%2Fskins%2FKugiBlack.ttf?ref=f93be8263f4deab8fb8884db7e03a1767a27bbad"},{"sha":"ec886bbe690c9516b0076eaa985bb996176b751f","filename":"inc/skins/KugiBlackOblique.ttf","status":"added","additions":0,"deletions":0,"changes":0,"blob_url":"https://github.com/bellenuit/sofawiki/blob/f93be8263f4deab8fb8884db7e03a1767a27bbad/inc%2Fskins%2FKugiBlackOblique.ttf","raw_url":"https://github.com/bellenuit/sofawiki/raw/f93be8263f4deab8fb8884db7e03a1767a27bbad/inc%2Fskins%2FKugiBlackOblique.ttf","contents_url":"https://api.github.com/repos/bellenuit/sofawiki/contents/inc%2Fskins%2FKugiBlackOblique.ttf?ref=f93be8263f4deab8fb8884db7e03a1767a27bbad"},{"sha":"0093118bf56ab82768d09648739e46f133114c77","filename":"inc/skins/KugiBold.ttf","status":"added","additions":0,"deletions":0,"changes":0,"blob_url":"https://github.com/bellenuit/sofawiki/blob/f93be8263f4deab8fb8884db7e03a1767a27bbad/inc%2Fskins%2FKugiBold.ttf","raw_url":"https://github.com/bellenuit/sofawiki/raw/f93be8263f4deab8fb8884db7e03a1767a27bbad/inc%2Fskins%2FKugiBold.ttf","contents_url":"https://api.github.com/repos/bellenuit/sofawiki/contents/inc%2Fskins%2FKugiBold.ttf?ref=f93be8263f4deab8fb8884db7e03a1767a27bbad"},{"sha":"0e052deb064c5f59b5ae1e77c7db137e29aca87f","filename":"inc/skins/KugiBoldOblique.ttf","status":"added","additions":0,"deletions":0,"changes":0,"blob_url":"https://github.com/bellenuit/sofawiki/blob/f93be8263f4deab8fb8884db7e03a1767a27bbad/inc%2Fskins%2FKugiBoldOblique.ttf","raw_url":"https://github.com/bellenuit/sofawiki/raw/f93be8263f4deab8fb8884db7e03a1767a27bbad/inc%2Fskins%2FKugiBoldOblique.ttf","contents_url":"https://api.github.com/repos/bellenuit/sofawiki/contents/inc%2Fskins%2FKugiBoldOblique.ttf?ref=f93be8263f4deab8fb8884db7e03a1767a27bbad"},{"sha":"667949cac59b8cd6dfd6336643ff0d92d5e33e90","filename":"inc/skins/KugiCaps.ttf","status":"added","additions":0,"deletions":0,"changes":0,"blob_url":"https://github.com/bellenuit/sofawiki/blob/f93be8263f4deab8fb8884db7e03a1767a27bbad/inc%2Fskins%2FKugiCaps.ttf","raw_url":"https://github.com/bellenuit/sofawiki/raw/f93be8263f4deab8fb8884db7e03a1767a27bbad/inc%2Fskins%2FKugiCaps.ttf","contents_url":"https://api.github.com/repos/bellenuit/sofawiki/contents/inc%2Fskins%2FKugiCaps.ttf?ref=f93be8263f4deab8fb8884db7e03a1767a27bbad"},{"sha":"54413cc2bfab8a8cece60d13d3bd9ef195e0d525","filename":"inc/skins/KugiItalic.ttf","status":"added","additions":0,"deletions":0,"changes":0,"blob_url":"https://github.com/bellenuit/sofawiki/blob/f93be8263f4deab8fb8884db7e03a1767a27bbad/inc%2Fskins%2FKugiItalic.ttf","raw_url":"https://github.com/bellenuit/sofawiki/raw/f93be8263f4deab8fb8884db7e03a1767a27bbad/inc%2Fskins%2FKugiItalic.ttf","contents_url":"https://api.github.com/repos/bellenuit/sofawiki/contents/inc%2Fskins%2FKugiItalic.ttf?ref=f93be8263f4deab8fb8884db7e03a1767a27bbad"},{"sha":"96f6920eaa8b3599831c248926e4ebe432437e68","filename":"inc/skins/KugiLight.ttf","status":"added","additions":0,"deletions":0,"changes":0,"blob_url":"https://github.com/bellenuit/sofawiki/blob/f93be8263f4deab8fb8884db7e03a1767a27bbad/inc%2Fskins%2FKugiLight.ttf","raw_url":"https://github.com/bellenuit/sofawiki/raw/f93be8263f4deab8fb8884db7e03a1767a27bbad/inc%2Fskins%2FKugiLight.ttf","contents_url":"https://api.github.com/repos/bellenuit/sofawiki/contents/inc%2Fskins%2FKugiLight.ttf?ref=f93be8263f4deab8fb8884db7e03a1767a27bbad"},{"sha":"3af7c1575ece94822986f416c4191c827dbf4666","filename":"inc/skins/KugiLightOblique.ttf","status":"added","additions":0,"deletions":0,"changes":0,"blob_url":"https://github.com/bellenuit/sofawiki/blob/f93be8263f4deab8fb8884db7e03a1767a27bbad/inc%2Fskins%2FKugiLightOblique.ttf","raw_url":"https://github.com/bellenuit/sofawiki/raw/f93be8263f4deab8fb8884db7e03a1767a27bbad/inc%2Fskins%2FKugiLightOblique.ttf","contents_url":"https://api.github.com/repos/bellenuit/sofawiki/contents/inc%2Fskins%2FKugiLightOblique.ttf?ref=f93be8263f4deab8fb8884db7e03a1767a27bbad"},{"sha":"d9b1a0ef057815af52c6d1d87a085d34a0a3f05e","filename":"inc/skins/KugiMono.ttf","status":"added","additions":0,"deletions":0,"changes":0,"blob_url":"https://github.com/bellenuit/sofawiki/blob/f93be8263f4deab8fb8884db7e03a1767a27bbad/inc%2Fskins%2FKugiMono.ttf","raw_url":"https://github.com/bellenuit/sofawiki/raw/f93be8263f4deab8fb8884db7e03a1767a27bbad/inc%2Fskins%2FKugiMono.ttf","contents_url":"https://api.github.com/repos/bellenuit/sofawiki/contents/inc%2Fskins%2FKugiMono.ttf?ref=f93be8263f4deab8fb8884db7e03a1767a27bbad"},{"sha":"2dabf327970a095ff0f60376d7e30fe03fd5e02b","filename":"inc/skins/KugiOblique.ttf","status":"added","additions":0,"deletions":0,"changes":0,"blob_url":"https://github.com/bellenuit/sofawiki/blob/f93be8263f4deab8fb8884db7e03a1767a27bbad/inc%2Fskins%2FKugiOblique.ttf","raw_url":"https://github.com/bellenuit/sofawiki/raw/f93be8263f4deab8fb8884db7e03a1767a27bbad/inc%2Fskins%2FKugiOblique.ttf","contents_url":"https://api.github.com/repos/bellenuit/sofawiki/contents/inc%2Fskins%2FKugiOblique.ttf?ref=f93be8263f4deab8fb8884db7e03a1767a27bbad"},{"sha":"7309c3d4bf1cd34b89646463e8847a2202e43d14","filename":"inc/skins/KugiRegular.ttf","status":"added","additions":0,"deletions":0,"changes":0,"blob_url":"https://github.com/bellenuit/sofawiki/blob/f93be8263f4deab8fb8884db7e03a1767a27bbad/inc%2Fskins%2FKugiRegular.ttf","raw_url":"https://github.com/bellenuit/sofawiki/raw/f93be8263f4deab8fb8884db7e03a1767a27bbad/inc%2Fskins%2FKugiRegular.ttf","contents_url":"https://api.github.com/repos/bellenuit/sofawiki/contents/inc%2Fskins%2FKugiRegular.ttf?ref=f93be8263f4deab8fb8884db7e03a1767a27bbad"},{"sha":"6fbac6929a84ada1442f468e3c9b87cadd257d11","filename":"inc/skins/NimbusMono-Bold 2.ttf","status":"removed","additions":0,"deletions":0,"changes":0,"blob_url":"https://github.com/bellenuit/sofawiki/blob/92b6e463e0d71a7aa514c8ca9c8128b9f9cb6680/inc%2Fskins%2FNimbusMono-Bold%202.ttf","raw_url":"https://github.com/bellenuit/sofawiki/raw/92b6e463e0d71a7aa514c8ca9c8128b9f9cb6680/inc%2Fskins%2FNimbusMono-Bold%202.ttf","contents_url":"https://api.github.com/repos/bellenuit/sofawiki/contents/inc%2Fskins%2FNimbusMono-Bold%202.ttf?ref=92b6e463e0d71a7aa514c8ca9c8128b9f9cb6680"},{"sha":"59ab768a758d37ba012b68222c29370c49a0ca71","filename":"inc/skins/NimbusMono-BoldOblique 2.ttf","status":"removed","additions":0,"deletions":0,"changes":0,"blob_url":"https://github.com/bellenuit/sofawiki/blob/92b6e463e0d71a7aa514c8ca9c8128b9f9cb6680/inc%2Fskins%2FNimbusMono-BoldOblique%202.ttf","raw_url":"https://github.com/bellenuit/sofawiki/raw/92b6e463e0d71a7aa514c8ca9c8128b9f9cb6680/inc%2Fskins%2FNimbusMono-BoldOblique%202.ttf","contents_url":"https://api.github.com/repos/bellenuit/sofawiki/contents/inc%2Fskins%2FNimbusMono-BoldOblique%202.ttf?ref=92b6e463e0d71a7aa514c8ca9c8128b9f9cb6680"},{"sha":"503157b61bd8e7cd76b2e3411625219079f2f3db","filename":"inc/skins/NimbusMono-Oblique 2.ttf","status":"removed","additions":0,"deletions":0,"changes":0,"blob_url":"https://github.com/bellenuit/sofawiki/blob/92b6e463e0d71a7aa514c8ca9c8128b9f9cb6680/inc%2Fskins%2FNimbusMono-Oblique%202.ttf","raw_url":"https://github.com/bellenuit/sofawiki/raw/92b6e463e0d71a7aa514c8ca9c8128b9f9cb6680/inc%2Fskins%2FNimbusMono-Oblique%202.ttf","contents_url":"https://api.github.com/repos/bellenuit/sofawiki/contents/inc%2Fskins%2FNimbusMono-Oblique%202.ttf?ref=92b6e463e0d71a7aa514c8ca9c8128b9f9cb6680"},{"sha":"887a8d85ffd17e928a371f7eff5fbfba276a9a30","filename":"inc/skins/NimbusMono-Regular 2.ttf","status":"removed","additions":0,"deletions":0,"changes":0,"blob_url":"https://github.com/bellenuit/sofawiki/blob/92b6e463e0d71a7aa514c8ca9c8128b9f9cb6680/inc%2Fskins%2FNimbusMono-Regular%202.ttf","raw_url":"https://github.com/bellenuit/sofawiki/raw/92b6e463e0d71a7aa514c8ca9c8128b9f9cb6680/inc%2Fskins%2FNimbusMono-Regular%202.ttf","contents_url":"https://api.github.com/repos/bellenuit/sofawiki/contents/inc%2Fskins%2FNimbusMono-Regular%202.ttf?ref=92b6e463e0d71a7aa514c8ca9c8128b9f9cb6680"},{"sha":"845bd2cf829d2ab4f4d7c91ecdac8ebd308da163","filename":"inc/skins/NimbusRomNo9L-Med 2.ttf","status":"removed","additions":0,"deletions":0,"changes":0,"blob_url":"https://github.com/bellenuit/sofawiki/blob/92b6e463e0d71a7aa514c8ca9c8128b9f9cb6680/inc%2Fskins%2FNimbusRomNo9L-Med%202.ttf","raw_url":"https://github.com/bellenuit/sofawiki/raw/92b6e463e0d71a7aa514c8ca9c8128b9f9cb6680/inc%2Fskins%2FNimbusRomNo9L-Med%202.ttf","contents_url":"https://api.github.com/repos/bellenuit/sofawiki/contents/inc%2Fskins%2FNimbusRomNo9L-Med%202.ttf?ref=92b6e463e0d71a7aa514c8ca9c8128b9f9cb6680"},{"sha":"1fd35bc689eec65571caa85a6e155d45dc48d011","filename":"inc/skins/NimbusRomNo9L-MedIta 2.ttf","status":"removed","additions":0,"deletions":0,"changes":0,"blob_url":"https://github.com/bellenuit/sofawiki/blob/92b6e463e0d71a7aa514c8ca9c8128b9f9cb6680/inc%2Fskins%2FNimbusRomNo9L-MedIta%202.ttf","raw_url":"https://github.com/bellenuit/sofawiki/raw/92b6e463e0d71a7aa514c8ca9c8128b9f9cb6680/inc%2Fskins%2FNimbusRomNo9L-MedIta%202.ttf","contents_url":"https://api.github.com/repos/bellenuit/sofawiki/contents/inc%2Fskins%2FNimbusRomNo9L-MedIta%202.ttf?ref=92b6e463e0d71a7aa514c8ca9c8128b9f9cb6680"},{"sha":"d131c4da3ded7ddfcab44ad8866e62a610882908","filename":"inc/skins/NimbusRomNo9L-Reg 2.ttf","status":"removed","additions":0,"deletions":0,"changes":0,"blob_url":"https://github.com/bellenuit/sofawiki/blob/92b6e463e0d71a7aa514c8ca9c8128b9f9cb6680/inc%2Fskins%2FNimbusRomNo9L-Reg%202.ttf","raw_url":"https://github.com/bellenuit/sofawiki/raw/92b6e463e0d71a7aa514c8ca9c8128b9f9cb6680/inc%2Fskins%2FNimbusRomNo9L-Reg%202.ttf","contents_url":"https://api.github.com/repos/bellenuit/sofawiki/contents/inc%2Fskins%2FNimbusRomNo9L-Reg%202.ttf?ref=92b6e463e0d71a7aa514c8ca9c8128b9f9cb6680"},{"sha":"26534a55c107c45f9910b4c0f359909a39f6b3f6","filename":"inc/skins/NimbusRomNo9L-RegIta 2.ttf","status":"removed","additions":0,"deletions":0,"changes":0,"blob_url":"https://github.com/bellenuit/sofawiki/blob/92b6e463e0d71a7aa514c8ca9c8128b9f9cb6680/inc%2Fskins%2FNimbusRomNo9L-RegIta%202.ttf","raw_url":"https://github.com/bellenuit/sofawiki/raw/92b6e463e0d71a7aa514c8ca9c8128b9f9cb6680/inc%2Fskins%2FNimbusRomNo9L-RegIta%202.ttf","contents_url":"https://api.github.com/repos/bellenuit/sofawiki/contents/inc%2Fskins%2FNimbusRomNo9L-RegIta%202.ttf?ref=92b6e463e0d71a7aa514c8ca9c8128b9f9cb6680"},{"sha":"63631740ecb0a27a51aacd206f784084adba4c37","filename":"inc/skins/NimbusSanL-Bol 2.ttf","status":"removed","additions":0,"deletions":0,"changes":0,"blob_url":"https://github.com/bellenuit/sofawiki/blob/92b6e463e0d71a7aa514c8ca9c8128b9f9cb6680/inc%2Fskins%2FNimbusSanL-Bol%202.ttf","raw_url":"https://github.com/bellenuit/sofawiki/raw/92b6e463e0d71a7aa514c8ca9c8128b9f9cb6680/inc%2Fskins%2FNimbusSanL-Bol%202.ttf","contents_url":"https://api.github.com/repos/bellenuit/sofawiki/contents/inc%2Fskins%2FNimbusSanL-Bol%202.ttf?ref=92b6e463e0d71a7aa514c8ca9c8128b9f9cb6680"},{"sha":"45cd70d033214d9a8570a8a8e9da3aef9c593bcc","filename":"inc/skins/NimbusSanL-BolIta 2.ttf","status":"removed","additions":0,"deletions":0,"changes":0,"blob_url":"https://github.com/bellenuit/sofawiki/blob/92b6e463e0d71a7aa514c8ca9c8128b9f9cb6680/inc%2Fskins%2FNimbusSanL-BolIta%202.ttf","raw_url":"https://github.com/bellenuit/sofawiki/raw/92b6e463e0d71a7aa514c8ca9c8128b9f9cb6680/inc%2Fskins%2FNimbusSanL-BolIta%202.ttf","contents_url":"https://api.github.com/repos/bellenuit/sofawiki/contents/inc%2Fskins%2FNimbusSanL-BolIta%202.ttf?ref=92b6e463e0d71a7aa514c8ca9c8128b9f9cb6680"},{"sha":"efc098eb667b4927cbbfc02530d3baec684d5da3","filename":"inc/skins/NimbusSanL-Reg 2.ttf","status":"removed","additions":0,"deletions":0,"changes":0,"blob_url":"https://github.com/bellenuit/sofawiki/blob/92b6e463e0d71a7aa514c8ca9c8128b9f9cb6680/inc%2Fskins%2FNimbusSanL-Reg%202.ttf","raw_url":"https://github.com/bellenuit/sofawiki/raw/92b6e463e0d71a7aa514c8ca9c8128b9f9cb6680/inc%2Fskins%2FNimbusSanL-Reg%202.ttf","contents_url":"https://api.github.com/repos/bellenuit/sofawiki/contents/inc%2Fskins%2FNimbusSanL-Reg%202.ttf?ref=92b6e463e0d71a7aa514c8ca9c8128b9f9cb6680"},{"sha":"631fdd9fa9c37749c688cff69715f0df8f9278ab","filename":"inc/skins/NimbusSanL-RegIta 2.ttf","status":"removed","additions":0,"deletions":0,"changes":0,"blob_url":"https://github.com/bellenuit/sofawiki/blob/92b6e463e0d71a7aa514c8ca9c8128b9f9cb6680/inc%2Fskins%2FNimbusSanL-RegIta%202.ttf","raw_url":"https://github.com/bellenuit/sofawiki/raw/92b6e463e0d71a7aa514c8ca9c8128b9f9cb6680/inc%2Fskins%2FNimbusSanL-RegIta%202.ttf","contents_url":"https://api.github.com/repos/bellenuit/sofawiki/contents/inc%2Fskins%2FNimbusSanL-RegIta%202.ttf?ref=92b6e463e0d71a7aa514c8ca9c8128b9f9cb6680"},{"sha":"5cd593c378d6f7d8c04ed7aa4bc488d2a9aca65b","filename":"inc/skins/tinyps-extensions.js","status":"modified","additions":9,"deletions":9,"changes":18,"blob_url":"https://github.com/bellenuit/sofawiki/blob/f93be8263f4deab8fb8884db7e03a1767a27bbad/inc%2Fskins%2Ftinyps-extensions.js","raw_url":"https://github.com/bellenuit/sofawiki/raw/f93be8263f4deab8fb8884db7e03a1767a27bbad/inc%2Fskins%2Ftinyps-extensions.js","contents_url":"https://api.github.com/repos/bellenuit/sofawiki/contents/inc%2Fskins%2Ftinyps-extensions.js?ref=f93be8263f4deab8fb8884db7e03a1767a27bbad","patch":"@@ -431,7 +431,7 @@ preparepatterns\n /legendstyle [ {} {} {} {} {} {} {} {} {} {} {}] def\n \n /textsizes { /titlesize exch def /bodysize exch def /labelsize exch def\n-/textfont (KugiRegular) def\n+/textfont (TGL017) def\n textfont cvn bodysize selectfont\n chartmargins 0 ymax numberformat stringwidth pop bodysize add put\n chartmargins 1 bodysize 4 mul put \n@@ -444,7 +444,7 @@ chartmargins 3 titlesize 1.2 mul bodysize 2.2 mul add bodysize 2 mul add put\n /gridlinewidth 1.5 def\n /axislinewidth 1.5 def\n /borderlinewidth 1.5 def\n-/segmentborderlinewidth 1.5 def\n+/segmentborderlinewidth 0 def\n /boldlinewidth 3 def\n \n /gridcolor { 0.7 setgray } def\n@@ -816,7 +816,7 @@ gsave patterns col get exec grestore segmentborderlinewidth setlinewidth 1 setgr\n \n y y0 sub labellimit gt {\n invertedlabelcolor \n-/s y y0 sub round cvs def\n+/s data row get col get cvs def\n \n xcenter y y0 add 2 div cos r1 r2 add 2 div mul add \n ycenter y y0 add 2 div sin r1 r2 add 2 div mul add moveto\n@@ -1203,10 +1203,10 @@ boldlinewidth setlinewidth\n   /x2 x1 v vrest div scx x1 sub mul add def\n   /y2 0 def\n   x1 y1 moveto x2 y1 lineto x2 y2 lineto x1 y2 lineto\n-  gsave fill grestore invertedlabelcolor stroke\n+  gsave fill grestore invertedlabelcolor segmentborderlinewidth setlinewidth  stroke\n   y1 y2 sub bodysize 2.7 mul gt x2 x1 sub 5 sub\n   data i get 0 get stringwidth pop \n-  vlabel stringwidth pop max gt and {\n+  vlabel stringwidth pop max gt and  {\n   x1 y1 moveto 5 bodysize 1.3 mul neg rmoveto data i get 0 get show\n   x1 y1 moveto 5 bodysize 2.5 mul neg rmoveto vlabel show\n    } if\n@@ -1215,10 +1215,10 @@ boldlinewidth setlinewidth\n   } {\n   /y2 y1 v vrest div y1 mul sub def\n   x1 y1 moveto x2 y1 lineto x2 y2 lineto x1 y2 lineto\n-  gsave fill grestore invertedlabelcolor stroke\n-  y1 y2 sub bodysize 2.7 mul gt x2 x1 sub 5 sub data i get 0 get stringwidth pop\n-  vlabel stringwidth pop max \n-   gt and {\n+  gsave fill grestore invertedlabelcolor segmentborderlinewidth setlinewidth  stroke\n+  y1 y2 sub bodysize 2.7 mul gt x2 x1 sub 5 sub\n+  data i get 0 get stringwidth pop\n+  vlabel stringwidth pop max gt and {\n   x1 y1 moveto 5 bodysize 1.3 mul neg rmoveto data i get 0 get show\n   x1 y1 moveto 5 bodysize 2.5 mul neg rmoveto vlabel show\n   } if"},{"sha":"b92ceabf0cdc6ae8e58debd671fe403dc27587d6","filename":"inc/skins/tinyps128.js","status":"removed","additions":0,"deletions":5553,"changes":5553,"blob_url":"https://github.com/bellenuit/sofawiki/blob/92b6e463e0d71a7aa514c8ca9c8128b9f9cb6680/inc%2Fskins%2Ftinyps128.js","raw_url":"https://github.com/bellenuit/sofawiki/raw/92b6e463e0d71a7aa514c8ca9c8128b9f9cb6680/inc%2Fskins%2Ftinyps128.js","contents_url":"https://api.github.com/repos/bellenuit/sofawiki/contents/inc%2Fskins%2Ftinyps128.js?ref=92b6e463e0d71a7aa514c8ca9c8128b9f9cb6680"},{"sha":"46596d1c44bfb293305b0e3779737fbae7f3742d","filename":"inc/skins/tinyps130.js","status":"removed","additions":0,"deletions":5685,"changes":5685,"blob_url":"https://github.com/bellenuit/sofawiki/blob/92b6e463e0d71a7aa514c8ca9c8128b9f9cb6680/inc%2Fskins%2Ftinyps130.js","raw_url":"https://github.com/bellenuit/sofawiki/raw/92b6e463e0d71a7aa514c8ca9c8128b9f9cb6680/inc%2Fskins%2Ftinyps130.js","contents_url":"https://api.github.com/repos/bellenuit/sofawiki/contents/inc%2Fskins%2Ftinyps130.js?ref=92b6e463e0d71a7aa514c8ca9c8128b9f9cb6680"},{"sha":"c338d902605a5333de80fabdf1c48d98305477ca","filename":"inc/skins/tinyps135.js","status":"renamed","additions":665,"deletions":181,"changes":846,"blob_url":"https://github.com/bellenuit/sofawiki/blob/f93be8263f4deab8fb8884db7e03a1767a27bbad/inc%2Fskins%2Ftinyps135.js","raw_url":"https://github.com/bellenuit/sofawiki/raw/f93be8263f4deab8fb8884db7e03a1767a27bbad/inc%2Fskins%2Ftinyps135.js","contents_url":"https://api.github.com/repos/bellenuit/sofawiki/contents/inc%2Fskins%2Ftinyps135.js?ref=f93be8263f4deab8fb8884db7e03a1767a27bbad","patch":"@@ -12,7 +12,7 @@ Version 1.1.1 2025-07-14 new operators forall, pathbbox\n - added worker messages (svgfilnal, device)\n - proper exit for for and repeat operators\n - imroved error messages (currentcode limited in length for speed)\n-- fixed errors that did not stop code (always return with context.error!) \n+- fixed errors that did not stop code (always return  with context.error!) \n - fixed heap errors on arrays ans strings\n - download elements  with jtimestamp in filename\n Version 1.1.2 2025-07-20 \n@@ -61,6 +61,26 @@ Version 1.3.0 2026-07-05\n - textmode 2: create smaller SVG using defs for paths\n Version 1.3.1 2026-07-16\n - GCODE device\n+Version 1.3.2 2026-08-12\n+- kshow, kwidthshow, print operator\n+- moved non standard charpath operator to extensions\n+- refactored show operator for type3mode\n+- implemented type3mode in stringwidth and widthshow\n+Version 1.3.3 2026-09-12\n+- refactored GCODE (set pen up only when moveto is actually different from current point\n+- component creates URL nodes for all devices, but keeps then not displayed (allows setpagedevice to add a device on the fly).\n+Version 1.3.4 2026-09-12\n+- new operator showfile can export multiple named PDFs\n+- fixed numberFormat bug in rpnPDFdevice\n+- fixed bugs in PDF font substitution\n+- arc path segmnents now play nicely within mixed paths\n+- arc angles can now go downwards.\n+- fixed positioning of TrueType fonts with 1000 geometry instead of 2024\n+- PostScript strings can properly escape ) and ( with backslash\n+- fixed nonbreaking hyphen bug in macRoman encoding\n+Version 1.3.5 2026-10-13\n+- Replaced Data URL with Blob URL (Safari 27 blocks Data URL)\n+\n \n Renders a subset of PostScript to Canvas, SVG and PDF (as well as an obsucre raw rendering).\n The output can be displayed or proposed as downloadable link. It can be transparent.\n@@ -113,6 +133,8 @@ if (typeof rpnExtensions  === 'undefined') {\n rpnFonts = {};\n rpnFiles = {};\n rpnFrames = {};\n+rpnPDFs = {};\n+rpnPDFfilename = {};\n rpnImageData = {};\n rpnSVGData = {};\n \n@@ -225,7 +247,8 @@ rpnName = class {\n \n \n rpnNumber = class {\n-   constructor(x) { this.value = x;}\n+   constructor(x) { this.value = x; if (Math.abs(this.value) < 0.0000000001) this.value = 0;\n+   }\n    get type() { return \"number\"; }\n    get dump() {\n        if (Math.abs(this.value) < 0.0000000001) return \"0\";\n@@ -256,9 +279,11 @@ rpnString = class {\n rpnRawDevice = class {\n     constructor() {\n         this.clear(640, 360, 2, 0);\n+        this.canfiles = false;\n \n     }\n     finalize(context) {\n+       if (context.device.raw || context.device.rawurl)  postMessage([\"rawzip\", context.id, null, null]);\n         postMessage([\"rawzip\", context.id, null, null]);\n     }\n \n@@ -369,7 +394,7 @@ rpnRawDevice = class {\n     }\n     showpage(context) {\n         const data = new ImageData(this.data, context.width * context.device.oversampling);\n-        postMessage([\"raw\", context.id, data, null]);       \n+        if (context.device.raw || context.device.rawurl)  postMessage([\"raw\", context.id, data, null]);       \n         this.clear(this.width, this.height, this.oversampling, this.transparent);\n         return context;\n     }\n@@ -383,10 +408,12 @@ rpnCanvasDevice = class {\n         this.finalround = false;\n         this.frames = [];\n         this.clear(640,360,1,0);\n+        this.canshow = false;\n+        this.canfiles = false;\n \n     }\n     finalize(context) {\n-        postMessage([\"canvaszip\", context.id, null, null]);\n+        if (context.device.canvas || context.device.canvasurl) postMessage([\"canvaszip\", context.id, null, null]);\n     }\n     clear(width, height, oversampling, transparent) {\n \t    this.width = width;\n@@ -473,7 +500,7 @@ rpnCanvasDevice = class {\n     showpage(context) {\n \t    const data = this.ctx.getImageData(0, 0, this.node.width, this.node.height);\n \t    \n-\t    postMessage([\"canvas\", context.id, data, null]);  \t\t\n+\t    if (context.device.canvas || context.device.canvasurl) postMessage([\"canvas\", context.id, data, null]);  \t\t\n \t         \n         this.ctx = null;\n         return context;\n@@ -487,14 +514,15 @@ rpnPDFDevice = class {\n     constructor() {\n         \n         this.canshow = true;\n+        this.canfiles = true;\n         this.catalog = {};\n         this.pageslist = {};\n         this.pages = [];\n         this.streams = [];\n         this.alphas = [];\n         this.elements = [];\n-        this.usedfonts = {};\n-        this.currentfont = {};\n+        this.usedfonts = [];\n+        this.currentfont = '';\n         this.imagestack = [];\n         this.timer = null;\n         this.finalround = false;\n@@ -505,7 +533,10 @@ rpnPDFDevice = class {\n         if (Number.isFinite(width*1)) this.width = width;\n         if (Number.isFinite(height*1)) this.height = height;\n     }\n-    finalize(context) {\n+    finalize(context, intern = false) {\n+\t    postMessage([\"log\", context.id, \"intern \" + intern, null]);\n+\t    if (!(context.device.pdf || context.device.pdfurl)) return context;\n+\t    \n         const objects = [];\n         this.catalog.Type = \"/Catalog\";\n         this.catalog.Pages = \"2 0 R \";\n@@ -570,12 +601,22 @@ rpnPDFDevice = class {\n         file += 'startxref'+ rpnEOL;\n         file += startxref + rpnEOL;\n         file +='%%EOF' + rpnEOL;\n-        const url = \"data:application/pdf;base64,\" + btoa(file);\n-        postMessage([\"pdf\", context.id, url, null]);\n+        \n+        // const url = \"data:application/pdf;base64,\" + btoa(file);\n+         postMessage([\"log\", context.id, \"intern2 \" + intern, null]);\n+\n+        if (intern)  postMessage([\"pdffile\", context.id, file+\":\"+intern, null]);\n+        else postMessage([\"pdf\", context.id, file, null]);\n+        \n+       //  if (intern)  postMessage([\"pdffile\", context.id, url + \",\" + intern, null]);\n+       //  else postMessage([\"pdf\", context.id, url, null]);\n+        \n+        return context;\n \n     } \n+    \n     numberFormat(z) {\n-        return Intl.NumberFormat('en-IN', { minimumFractionDigits: 3, maximumFractionDigits: 3 }).format(z);\n+        return Intl.NumberFormat('en-IN', { minimumFractionDigits: 3, maximumFractionDigits: 3 }).format(z).replace(\",\",\"\");\n     }\n     asciiHexEncode(buffer) {\n        const data = new Array(buffer.length);\n@@ -614,6 +655,8 @@ rpnPDFDevice = class {\n         return p.join(\" \");\n     }\n     clip(context) {\n+\t    if (!(context.device.pdf || context.device.pdfurl)) return context;\n+\n         this.elements.push(\"q\");\n         for (let clip of context.graphics.clip) {\n             this.elements.push(this.getPath(clip));\n@@ -622,10 +665,14 @@ rpnPDFDevice = class {\n         return context;\n     }\n     eofill (context) {\n+\t    if (!(context.device.pdf || context.device.pdfurl)) return context;\n+\n         return this.fill(context, false);\n     }\n     fill(context, zerowind = true) {\n-\t    if (context.showmode) return context;\n+\t    if (!(context.device.pdf || context.device.pdfurl)) return context;\n+\n+\t    if (context.showmode && context.device.textmode > 0) return context;\n         if (context.graphics.clip.length) context = this.clip(context);\n         this.elements.push(this.getPath(context.graphics.path));\n         this.setcolor(context);\n@@ -654,28 +701,39 @@ rpnPDFDevice = class {\n         this.elements.push(\"/GS\"+gs+\" gs\");\n     }\n     setfont(context) {\n+\t    if (!(context.device.pdf || context.device.pdfurl)) return context;\n+\n         const f = {};\n         f.Type = \"/Font\";\n         f.Subtype = \"/Type1\";\n-        f.Encoding = \"/rpnMacRomanEncoding\";\n+        f.Encoding = \"/MacRomanEncoding\";\n         f.BaseFont = \"/\" + context.graphics.font;\n- \n- const key = f.BaseFont;\n- var k;\n-  \n- if (Object.prototype.hasOwnProperty(this.usedfonts, key)) {\n-     k = this.usedfonts[key].key;\n-        } else {\n-     k = Object.keys(this.usedfonts).length + 1;\n-     f.key = k;\n-     this.usedfonts[key] = f;\n+        \n+        const substitute = rpnFontSubstitution(f.BaseFont);\n+        postMessage([\"log\", context.id, \"font \"+ f.BaseFont + \"  \" +substitute , null])\n+        f.BaseFont = substitute;\n+\n+        \n+        var k = 0;\n+        for (let i = 0; i < this.usedfonts.length; i++) {\n+\t        let uf = this.usedfonts[i];\n+\t        if (uf.BaseFont == f.BaseFont) {\n+\t\t        k = i;\n+\t        }\n         }\n- if (f != this.currentfont) {\n-     this.currentfont = f;\n-     this.elements.push(\"BT /F\" + k + \"  \" + this.numberFormat(context.graphics.size) + \" Tf ET\"); }\n+        if (!k) {\n+\t        this.usedfonts.push(f);\n+\t        k = this.usedfonts.length-1;\n+        }\n+ \n+        if (context.graphics.font != this.currentfont) {\n+        this.currentfont = context.graphics.font;\n+        this.elements.push(\"BT /F\" + (k+1) + \"  \" + this.numberFormat(context.graphics.size) + \" Tf ET\"); }\n \n     }\n     show(s, context, targetwidth = 0, extraspace = 0) {\n+\t    if (!(context.device.pdf || context.device.pdfurl)) return context;\n+\n         if (context.graphics.clip.length) context = this.clip(context);\n         this.setfont(context);\n         this.setcolor(context);\n@@ -699,18 +757,41 @@ rpnPDFDevice = class {\n         \n         return context;\n     }\n+    showfile(context, filename) {\n+\t    if (!(context.device.pdf || context.device.pdfurl)) return context;\n+\t    \n+\t    context = this.finalize(context, filename) ;\n+\t    \n+\t    this.catalog = {};\n+        this.pageslist = {};\n+        this.pages = [];\n+        this.streams = [];\n+        this.alphas = [];\n+        this.elements = [];\n+        this.usedfonts = [];\n+        this.currentfont = '';\n+        this.imagestack = [];\n+        this.timer = null;\n+        this.finalround = false;\n+        \n+\t    return context;\n+\t    \n+    }\n     showpage(context) {\n+\t    if (!(context.device.pdf || context.device.pdfurl)) return context;\n          const dict = {};\n         dict.Type = \"/Page\";\n         dict.Parent = \"2 0 R\";\n         const ressourcesdict = {};\n         const fontdict= {};\n+        // postMessage([\"log\", context.id, \"usedfonts \"+ JSON.stringify(this.usedfonts) , null])\n         for (let k in this.usedfonts) {    \n-     const f = this.usedfonts[k];\n-            const substitute = rpnFontSubstitution(f.BaseFont);\n-            f.BaseFont = substitute;\n-            fontdict[\"F\" + f.key] = f;\n+            const f = this.usedfonts[k];\n+            let key = k * 1 + 1.0;\n+            fontdict[\"F\" + key] = f;\n         }\n+        this.usedfonts = [];\n+        this.currentfont = '';\n         ressourcesdict.Font = fontdict;\n         const alphadict = {};\n         ressourcesdict.ExtGState = alphadict;\n@@ -738,6 +819,7 @@ rpnPDFDevice = class {\n         return context;\n     }\n     stroke(context) {\n+\t    if (!(context.device.pdf || context.device.pdfurl)) return context;\n         if (context.graphics.clip.length) context = this.clip(context);\n         this.elements.push(this.getPath(context.graphics.path, false));\n         this.setcolor(context);\n@@ -755,6 +837,7 @@ rpnSVGDevice = class {\n     constructor() {\n         this.canshow = true;\n         this.cannofill = true;\n+        this.canfiles = false;\n         this.fonts = {};\n         this.glyphs = {};\n         this.clear( 640, 360, 1, 0);\n@@ -786,7 +869,7 @@ rpnSVGDevice = class {\n \n     }\n     finalize(context) {\n-    \tpostMessage([\"svgfinal\", context.id, null, null])\n+    \tif (context.device.svg || context.device.svgurl) postMessage([\"svgfinal\", context.id, null, null])\n     }\n     getPath(path, close = true) {\n        const p = [];\n@@ -800,14 +883,17 @@ rpnSVGDevice = class {\n            for (let line of subpath) {\n                if (line[0] == \"C\") {\n                    p.push(\"C \" + (Math.round(line[3]*precision)/precision) + \" \" + (Math.round((this.height - line[4])*precision)/precision)+ \" \" + (Math.round(line[5]*precision)/precision) + \" \" + (Math.round((this.height - line[6])*precision)/precision) + \" \" + (Math.round((line[7])*precision)/precision) + \" \" + (Math.round((this.height - line[8])*precision)/precision));\n-               } else {\n+               } else if (line[0] == \"Z\") {\n+               \t    p.push(\"Z\"); \n+                }\n+                 else\n+                {\n                    if (line[3] == current[0]) {\n \t                    p.push(\"V \" + (Math.round((this.height - line[4])*precision)/precision));\n                    } else if (line[3] == current[0]) {\n \t                    p.push(\"H \" + (Math.round(line[3]*precision)/precision));\n                    } else {\n                         p.push(\"L \"+ (Math.round(line[3]*precision)/precision) + \" \" + (Math.round((this.height - line[4])*precision)/precision));\n-\t\t\t\t   \t\tif (line[0] == \"Z\") p.push(\"Z\"); \n \t\t\t\t   }\n                    current = [line[3],line[4]];\n                 }\n@@ -912,6 +998,7 @@ rpnSVGDevice = class {\n         const y = this.height - context.graphics.current[1];\n         node.setAttribute(\"text-anchor\",\"start\");\n         node.setAttribute(\"transform\", \"translate(\" + (Math.round(x*1000)/1000) + \" \" + (Math.round(y*1000)/1000)  +\") scale(\" + decomposed.scale[0] + \" \" +  decomposed.scale[1]  + \") rotate(\" +  -decomposed.rotation*180/Math.PI + \")\" );\n+\n         if (targetwidth) {\n             node.setAttribute(\"textLength\", targetwidth);\n             node.setAttribute(\"lengthAdjust\", \"spacing\");\n@@ -926,9 +1013,13 @@ rpnSVGDevice = class {\n \t        const font = rpnFonts[context.graphics.font];\n \t        const fontid = Object.keys(rpnFonts).indexOf(context.graphics.font);\n \t        let scale = context.graphics.size / font.head.unitsPerEm;\n+\t        let korr = 2048 / font.head.unitsPerEm ;\n \t        const glyphtextnode = rpnDocument.createElement(\"g\");\n \t        glyphtextnode.setAttribute(\"text\",s);\n-            glyphtextnode.setAttribute(\"transform\", \"translate(\" + (Math.round(x*1000)/1000) + \" \" + (Math.round((y-context.height*scale)*1000)/1000) +\") scale(\" + decomposed.scale[0]*scale + \" \" +  decomposed.scale[1]*scale  + \") rotate(\" +  -decomposed.rotation*180/Math.PI + \")\" );\n+\t        let m = context.graphics.matrix; \n+\t        // experimental baseline correction magic\n+\t        //postMessage([\"status\",context.id,\"descent \"+font.hhea.descent,null])\n+            glyphtextnode.setAttribute(\"transform\", \"translate(\" + (Math.round(x*1000)/1000) + \" \" + (Math.round((y-decomposed.scale[1]*context.graphics.size/6 * korr)*1000)/1000) +\") scale(\" + Math.round(decomposed.scale[0]*scale*100000)/100000 + \" \" +  Math.round(decomposed.scale[1]*scale*100000)/100000  + \") rotate(\" + decomposed.rotation*180/Math.PI + \") \" );\n             if (context.device.transparent == 1)\n             glyphtextnode.setAttribute(\"fill\", this.rgba2hex(context.graphics.color[0], context.graphics.color[1], context.graphics.color[2], context.graphics.color[3]));\n         else \n@@ -995,7 +1086,7 @@ rpnSVGDevice = class {\n         \n         \n         \n-        postMessage([\"svg\", context.id, this.node.outerHTML(), null])\n+        if (context.device.svg || context.device.svgurl) postMessage([\"svg\", context.id, this.node.outerHTML(), null])\n         this.clear(this.width, this.height, this.oversampling, this.transparent);\n         return context;\n     }\n@@ -1017,6 +1108,7 @@ rpnSVGDevice = class {\n     constructor() {\n         this.canshow = false;\n         this.cannofill = false;\n+        this.canfiles = false;\n         this.clear( 640, 360, 1, 0);\n         this.currentcolor = {};\n     }\n@@ -1040,6 +1132,7 @@ rpnSVGDevice = class {\n \n     }\n     finalize(context) {\n+    \tif (!(context.device.gcode || context.device.gcodeurl)) return context;\n     \tpostMessage([\"gcodefinal\", context.id, null, null])\n     }\n     getPath(path, context, close = true) {\n@@ -1066,6 +1159,8 @@ rpnSVGDevice = class {\n            return [xscaled,yscaled];       \n        }\n        \n+       var [lastx, lasty] = [-1,-1];\n+       \n        for (let subpath of path) {\n            if (!subpath.length) continue;\n \n@@ -1075,26 +1170,34 @@ rpnSVGDevice = class {\n            [x, y] = tf([\"M\",context.width/2,context.height/2,subpath[0][1],subpath[0][2]],context);\n \n            p.push(\";\");\n-           p.push(`G0 X${ x } Y${ y  }`)\n-           p.push(\"G0 Z8\"); // pen down \n-\n+           \n+           if (lastx != x || lasty != y) {\n+               p.push(\"G0 Z0\"); // pen up \n+               p.push(`G0 X${ x } Y${ y  }`)\n+               p.push(\"G0 Z8\"); // pen down \n+           }\n \n            for (let line of subpath) {\n                if (line[0] == \"C\") {\n                    let bezierpath = rpnBezier(line);\n \n                    for (let bline of bezierpath) {\n                         [x, y] = tf(bline,context);\n-                        p.push(`G1 X${ x } Y${ y  }`)\n+                        p.push(`G1 X${ x } Y${ y  }`);\n+                        [lastx, lasty] = [x, y];\n                    }\n \n                 } else {\n                         [x, y] = tf(line,context);\n-                        p.push(`G1 X${ x } Y${ y  }`)\n+                        p.push(`G1 X${ x } Y${ y  }`);\n+                        [lastx, lasty] = [x, y];\n                 }\n            }\n-          p.push(\"G0 Z0\"); // pen up \n+          \n         } \n+        \n+        p.push(\"G0 Z0\"); // pen up \n+        \n         return p;\n     }\n     clip(context) {\n@@ -1105,13 +1208,15 @@ rpnSVGDevice = class {\n     }\n     eofill (context) {\n         // not supported\n+        if (!(context.device.gcode || context.device.gcodeurl)) return context;\n         return context;\n     }\n     fill(context, zerowind = true) {\n-\t    // not supported\n+        // not supported\n         return context;\n     }\n     stroke(context) {\n+\t    if (!(context.device.gcode || context.device.gcodeurl)) return context;\n         context = this.clip(context);\n         if (this.color.length && this.color.toString() !== context.graphics.color.toString()) {\n             this.lines.push(`; color changes from ${ this.color.toString()} to ${ context.graphics.color.toString()}`); \n@@ -1127,10 +1232,11 @@ rpnSVGDevice = class {\n         return context;\n     }\n     showpage(context) {\n+        if (!(context.device.gcode || context.device.gcodeurl)) return context;\n         this.lines.push(\"; clean up\");\n         this.lines.push(\"G0 X0 Y0\"); // go to origin       \n         this.lines.push(\"M2\"); // end\n-        postMessage([\"gcode\", context.id, this.lines, null])\n+        if (context.device.gcode || context.device.gcodeurl) postMessage([\"gcode\", context.id, this.lines, null])\n         this.clear(this.width, this.height, this.oversampling, this.transparent);\n         return context;\n     }\n@@ -1423,7 +1529,7 @@ rpn = function(s, context = null, outerContext = false, silent = false ) {\n                    current += elem;\n                }\n                break;\n-           case \"string\":\n+           case \"string\": // console.log(elem); console.log(elem.charCodeAt(0));\n                if (elem == \")\") {\n                    if (depth) {\n                       depth--;\n@@ -1438,10 +1544,17 @@ rpn = function(s, context = null, outerContext = false, silent = false ) {\n                } else if (elem == \"(\") {\n                   depth++;\n                   current += elem;\n+               } else if (elem.charCodeAt(0) == 92)  {\n+\t               postMessage([\"print\",context.id,\"stringescape\",null]);\n+\t               state = \"stringescape\";\n                } else {\n                    current += elem;\n                }\n                break;\n+           case \"stringescape\":\n+               current += elem;\n+               state = \"string\";\n+               break; \n            case \"comment\":\n              if (elem == rpnEOL ) {\n                 state = \"start\";\n@@ -1706,35 +1819,67 @@ rpnOperators.arc = function(context) {\n     if (!centerx) return context;\n \n     // assure angle2 > angle1!\n-    while (angle2.value <= angle1.value) angle2.value += 360;\n+//    while (angle2.value <= angle1.value) angle2.value += 360;\n     \n     // approximation works for 90 dregrees max, we split\n     var first = true;\n-    var target = angle1.value;\n     const subpath = [];\n+    var a1 = angle1.value;\n+    var a2 = angle2.value;\n+    const reverse = (a2 < a1);\n+    const existingpath = context.graphics.current.length;\n+    var subpath0;\n+    if (existingpath) {\n+        subpath0 = context.graphics.path.pop();\n+    } else {\n+    \tsubpath0 = [];\n+    }\n+    \n+    const [existingx, existingy] = [context.graphics.current[0],context.graphics.current[1]];\n+    if (reverse) [a1, a2] = [a2, a1];\n+    var target = a1;\n+    \n+    const r = radius.value;\n+    const cx = centerx.value;\n+    const cy = centery.value;\n     \n-    while(angle1.value < angle2.value) {\n+    while(a1 < a2) {\n          \n-        target = angle1.value + Math.min(90, angle2.value - target);   // starting point\n-        let [x1, y1] = [centerx.value + radius.value * Math.cos(angle1.value * Math.PI/180), centery.value + radius.value * Math.sin(angle1.value * Math.PI/180)];\n+        target = a1 + Math.min(90, a2 - target);   // starting point \n+        console.log(target);\n+        let [x1, y1] = [cx + r * Math.cos(a1 * Math.PI/180), cy + r * Math.sin(a1 * Math.PI/180)];\n         // end point\n-        let [x4, y4] = [centerx.value + radius.value * Math.cos(target * Math.PI/180), centery.value + radius.value * Math.sin(target * Math.PI/180)];\n+        let [x4, y4] = [cx + r * Math.cos(target * Math.PI/180), cy + r * Math.sin(target * Math.PI/180)];\n+        // if (reverse) [x1, y1, x4, y4] = [x4, y4, x1, y1];       \n         // control points\n-        let lt = radius.value * (target-angle1.value)/90 * 4 /3 * (Math.sqrt(2)-1) ;\n-        let [x2, y2] = [x1 + lt * Math.cos((angle1.value + 90)*Math.PI/180), y1 + lt * Math.sin((angle1.value + 90)*Math.PI/180)];\n+        let lt = r * (target-a1)/90 * 4 /3 * (Math.sqrt(2)-1) ;\n+        let [x2, y2] = [x1 + lt * Math.cos((a1 + 90)*Math.PI/180), y1 + lt * Math.sin((a1 + 90)*Math.PI/180)];\n         let [x3, y3] = [x4 + lt * Math.cos((target - 90)*Math.PI/180), y4 + lt * Math.sin((target - 90)*Math.PI/180)];\n         let [x1t, y1t] = context.transform(x1, y1);\n         let [x2t, y2t] = context.transform(x2, y2);\n         let [x3t, y3t] = context.transform(x3, y3);\n         let [x4t, y4t] = context.transform(x4, y4);\n-        if (context.graphics.current.length) {\n-            subpath.push([\"L\", context.graphics.current[0], context.graphics.current[1], x1t, y1t]);\n-        }\n-        subpath.push([\"C\", x1t, y1t, x2t, y2t, x3t, y3t, x4t, y4t]);\n-        angle1.value += 90;\n-        context.graphics.current = [ x4t, y4t ];\n+        if (reverse)\n+            subpath.push([\"C\", x4t, y4t, x3t, y3t, x2t, y2t, x1t, y1t]);\n+        else \n+\t\t\tsubpath.push([\"C\", x1t, y1t, x2t, y2t, x3t, y3t, x4t, y4t]);\n+        a1 += 90;\n+        if (reverse)\n+            context.graphics.current = [ x1t, y1t ];\n+        else \n+        \tcontext.graphics.current = [ x4t, y4t ];\n     }\n-    context.graphics.path.push(subpath);\n+    if (reverse) subpath.reverse();\n+    if (existingpath && subpath.length) {\n+    \tlet [firstx,firsty] = [subpath[0][1],subpath[0][2]];\n+    \tsubpath.unshift([\"L\",existingx,existingy,firstx,firsty])\n+    };\n+    \n+    for(let p of subpath) {\n+    \tsubpath0.push(p);\n+    }\n+    \n+    context.graphics.path.push(subpath0);\n     return context;\n };\n \n@@ -1828,31 +1973,7 @@ rpnOperators.ceil = function(context) {\n     return context;\n };\n \n-rpnOperators.charpath = function(context) {\n-    const [s] = context.pop(\"string\");\n-    if (!context.graphics.current.length) {\n-       return context.error(\"nocurrentpoint\");\n-    }\n-    if (!context.graphics.font) {\n-       return context.error(\"nocurrentfont\");\n-    }\n-    if (!s) return context;\n-    const font = rpnFonts[context.graphics.font];\n-    if (!font) {\n-       return context.error(\"nocurrentfont\");\n-    }\n-    const scale = context.graphics.size / font.head.unitsPerEm;\n-    var ps = \" currentpoint currentpoint translate currentmatrix \" + scale + \" \" + scale + \" scale \";\n-    for (let i = 0; i < s.value.length; i++) {\n-        const c = s.value.charCodeAt(i);\n-        const gi = font.glyphIndex(c); \n-        ps += font.glyphPath(gi);\n-        ps += font.glyphWidth(gi) + \" 0 translate \";\n-    }\n-    ps += \" setmatrix neg exch neg exch translate \";\n-    context = rpn(ps, context);\n-    return context;\n-};\n+\n \n rpnOperators.clear = function(context) {\n     context.stack = [];\n@@ -1931,19 +2052,19 @@ rpnOperators.currentalpha = function(context) {\n     return context;\n };\n \n-rpnOperators.currentflat = function(context) {\n-\t// not implemented\n-    context.stack.push(new rpnNumber(1));\n-    return context;\n-};\n-\n rpnOperators.currentdict = function(context) {\n     const d = new rpnDictionary(1);\n     d.value = context.dict;\n     context.stack.push(d);\n     return context;\n };\n \n+rpnOperators.currentflat = function(context) {\n+\t// not implemented\n+    context.stack.push(new rpnNumber(1));\n+    return context;\n+};\n+\n rpnOperators.currentgray = function(context) {\n     const [r, g, b] = context.graphics.color;\n     const y = (0.30*r + 0.61*g +0.09*b) / 255.0;\n@@ -2250,6 +2371,8 @@ rpnOperators.findfont = function(context) {\n };\n \n \n+\n+\n rpnOperators.fill = function(context) {\n     for (let n of context.nodes) context = n.fill(context);\n     context.graphics.path = [];\n@@ -2538,6 +2661,206 @@ rpnOperators.known = function(context) {\n rpnUnitTest(\"/foo 1 def currentdict /foo known\", \"1\");\n rpnUnitTest(\"/foo 1 def 1 dict /foo known\", \"0\");\n \n+rpnOperators.kshow = function(context) {\n+    const [s, p ] = context.pop(\"string\", \"procedure\");\n+    if (!context.graphics.current.length) {\n+       return context.error(\"nocurrentpoint\");\n+    }\n+    if (!context.graphics.font) {\n+       return context.error(\"nocurrentfont\");\n+    }\n+    if (!s) return context;\n+    const font = rpnFonts[context.graphics.font];\n+    if (!font) {\n+       return context.error(\"invalidfont\");\n+    }\n+    const type3mode = font.value && font.value.FontType.value == 3;\n+    var ps = \"\";\n+    var scale = 1;\n+    var targetwidth = 0;\n+    let [currentx0, currenty0] = context.graphics.current;\n+    if (type3mode) { \n+        context.stack.push(font);\n+    \tps += \" /currentfontdict exch def \";\n+//         ps += \" (START) print currentfontdict /FontName get print\"\n+        ps += \" currentpoint currentpoint translate \";\n+        ps += \" currentmatrix \";\n+        ps += \" currentfontdict /FontMatrix get 0 get currentfontdict /FontMatrix get 3 get  scale \";\n+        ps +=  context.graphics.size + \" \" + context.graphics.size + \" scale  \";\n+        for (let i = 0; i < s.value.length; i++) {\n+        \t let c = rpnMacRomanChar(s.value,i); // s.value.charCodeAt(i)\n+//         \t % ps += \" (CHAR) print currentfontdict /FontName get print \"\n+             ps += \" currentfontdict \" + c + \" currentfontdict begin BuildChar end 0 translate \";\n+             if (i+1 < s.value.length)\n+             ps += \" \" +c + \" \" + rpnMacRomanChar(s.value,i+1) + \" \" + p.dump + \" exec \";\n+        \n+    \t}\n+    } else {\n+    \tscale = context.graphics.size / font.head.unitsPerEm;\n+\n+    \tps += \" currentpoint currentpoint translate currentmatrix \" + scale + \" \" + scale + \" scale \";\n+    \tfor (let i = 0; i < s.value.length; i++) {\n+        \tconst c = s.value.charCodeAt(i);\n+        \tconst gi = font.glyphIndex(c);\n+        \tps += font.glyphPath(gi);\n+        \tps += \" fill \";\n+        \tps += font.glyphWidth(gi) + \" 0 translate \";\n+        \ttargetwidth +=  font.glyphWidth(gi) ;\n+       \t \t// ttf kerning not implented\n+       \t \t if (i+1 < s.value.length)\n+             \tps += \" \" + c + \" \" + s.value.charCodeAt(i+1)+ \" \" + p.dump + \" exec \";\n+\n+    \t}\n+\n+    }\n+\n+    ps += \" setmatrix neg exch neg exch translate \";   \n+    context.showmode = false;\n+    context = rpn(ps, context);\n+    context.showmode = false;\n+    \n+    if (context.device.textmode == 1 && !type3mode) {    \n+        targetwidth *= scale;\n+        context.graphics.current = [currentx0, currenty0];\n+        for (let n of context.nodes) { \n+           if (n.canshow) context = n.show(s.value, context, targetwidth);\n+        }\n+    }\n+    return context;\n+};\n+\n+rpnOperators.kstringwidth = function(context) {\n+    const [s, p] = context.pop(\"string\", \"procedure\");\n+    if (!context.graphics.font) {\n+       return context.error(\"nocurrentfont\");\n+    }\n+    if (!s) return context;\n+    const font = rpnFonts[context.graphics.font];\n+    if (!font) {\n+       return context.error(\"invalidfont\");\n+    }\n+    const type3mode = font.value && font.value.FontType.value == 3;\n+    var width = 0;\n+    if (type3mode) {\n+    \tlet scale = context.graphics.size * font.value.FontMatrix.value[0].value;\n+    \tfor (let i = 0; i < s.value.length; i++) {\n+        \tconst c = rpnMacRomanChar(s.value,i);\n+    \t\tlet gn = font.value.Encoding.value[c].value;\n+    \t\t// postMessage([\"log\",context.id,\"code \" + gn,null]);\n+    \t\twidth += font.value.Metrics.value[gn].value * scale;\n+    \t}\n+    \t\n+    } else {\n+    \n+    \tlet scale = context.graphics.size / font.head.unitsPerEm;\n+    \tfor (let i = 0; i < s.value.length; i++) {\n+        \tlet c = s.value.charCodeAt(i);\n+        \tlet gi = font.glyphIndex(c);\n+        \twidth += font.glyphWidth(gi) * scale;\n+    \t}\n+    }\n+    \n+    // remove kerning\n+    var ps = \"/\"+ context.graphics.font + \" \" + context.graphics.size + \" selectfont \";\n+    ps += \" 0 \";\n+    for (let i = 0; i+1 < s.value.length; i++) {\n+    \tif (type3mode)\n+    \t   ps += \" \" + rpnMacRomanChar(s.value,i) + \" \" + rpnMacRomanChar(s.value,i+1) + \" \";\n+    \telse \n+\t\t\tps += \" \" + s.value.charCodeAt(i) + \" \" + s.value.charCodeAt(i+1) + \" \";\n+\t\t\t\n+\t\tps += \" {\" + p.value + \"} exec add \";\n+     }\n+     // postMessage([\"status\",context.id,ps,null])\n+    context2 = new rpnContext;\n+    context2.showmode = false;\n+    context2 = rpn(ps, context2);\n+    context2.showmode = false;\n+\tlet kern = context2.stack.pop().value;\n+\tlet scale = (type3mode) ? context.graphics.size / 1000 : context.graphics.size / font.head.unitsPerEm;\n+\t\n+\t\n+\twidth -= kern * scale;\n+    \n+    context.stack.push(new rpnNumber(width));\n+    context.stack.push(new rpnNumber(0));\n+    return context;\n+};\n+\n+\n+rpnOperators.kwidthshow = function(context) {\n+    const [s, ch, cy, cx, p ] = context.pop(\"string\", \"number\", \"number\", \"number\", \"procedure\");\n+    if (!context.graphics.current.length) {\n+       return context.error(\"nocurrentpoint\");\n+    }\n+    if (!context.graphics.font) {\n+       return context.error(\"nocurrentfont\");\n+    }\n+    if (!s) return context;\n+    const font = rpnFonts[context.graphics.font];\n+    if (!font) {\n+       return context.error(\"invalidfont\");\n+    }\n+    const type3mode = font.value && font.value.FontType.value == 3;\n+    var ps = \"\";\n+    var scale = 1;\n+    var targetwidth = 0;\n+    let [currentx0, currenty0] = context.graphics.current;\n+    if (type3mode) { \n+        context.stack.push(font);\n+    \tps += \" /currentfontdict exch def \";\n+//         ps += \" (START) print currentfontdict /FontName get print\"\n+        ps += \" currentpoint currentpoint translate \";\n+        ps += \" currentmatrix \";\n+        ps += \" currentfontdict /FontMatrix get 0 get currentfontdict /FontMatrix get 3 get  scale \";\n+        ps +=  context.graphics.size + \" \" + context.graphics.size + \" scale  \";\n+        for (let i = 0; i < s.value.length; i++) {\n+        \t let c = rpnMacRomanChar(s.value,i); // s.value.charCodeAt(i)\n+//         \t % ps += \" (CHAR) print currentfontdict /FontName get print \"\n+             ps += \" currentfontdict \" + c + \" currentfontdict begin BuildChar end 0 translate \";\n+             if (i+1 < s.value.length)\n+             ps += \" \" +c + \" \" + rpnMacRomanChar(s.value,i+1) + \" \" + p.dump + \" exec \";\n+             if (c == ch.value)\n+                 ps += cx.value + \" currentfontdict /FontMatrix get 0 get div \" + context.graphics.size + \" div \" + cy.value + \"  currentfontdict /FontMatrix get 3 get div \" + context.graphics.size + \" div translate \";\n+\n+    \t}\n+    } else {\n+    \tscale = context.graphics.size / font.head.unitsPerEm;\n+    \tlet [cx0, cy0] = [cx.value / scale, cy.value / scale];\n+    \tps += \" currentpoint currentpoint translate currentmatrix \" + scale + \" \" + scale + \" scale \";\n+    \tfor (let i = 0; i < s.value.length; i++) {\n+        \tconst c = s.value.charCodeAt(i);\n+        \tconst gi = font.glyphIndex(c);\n+        \tps += font.glyphPath(gi);\n+        \tps += \" fill \";\n+        \tps += font.glyphWidth(gi) + \" 0 translate \";\n+        \ttargetwidth +=  font.glyphWidth(gi) ;\n+       \t \t// ttf kerning not implented\n+       \t \t if (i+1 < s.value.length)\n+             \tps += \" \" + c + \" \" + s.value.charCodeAt(i+1)+ \" \" + p.dump + \" exec \";\n+            if (c == ch.value) {\n+           \t\tps += cx0 + \" \" + cy0 + \" translate \";\n+           \t\ttargetwidth +=  cx0 ;\n+        \t}\n+    \t}\n+\n+    }\n+\n+    ps += \" setmatrix neg exch neg exch translate \";   \n+    context.showmode = false;\n+    context = rpn(ps, context);\n+    context.showmode = false;\n+    \n+    if (context.device.textmode == 1 && !type3mode) {    \n+        targetwidth *= scale;\n+        context.graphics.current = [currentx0, currenty0];\n+        for (let n of context.nodes) { \n+           if (n.canshow) context = n.show(s.value, context, targetwidth);\n+        }\n+    }\n+    return context;\n+};\n+\n rpnOperators.le = function(context) {\n     const [b, a] = context.pop(\"number,string\", \"number,string\");\n     if (!b) return context;\n@@ -2825,6 +3148,12 @@ rpnOperators.pathbbox = function(context) {\n     return context;\n };\n \n+rpnOperators.print = function(context) {\n+\tconst [a] = context.pop(\"any\");\n+\tif (!a) return context;\n+\tpostMessage([\"status\",context.id,a.dump+\"<br>\",null])\n+\treturn context;\n+}\n \n \n rpnOperators.put = function(context) {\n@@ -3179,6 +3508,7 @@ rpnOperators.setdash = function(context) {\n rpnOperators.setfont = function(context) {\n     const [font] = context.pop(\"dictionary\");\n     context.graphics.font =  font.value.FontName.value;\n+    // postMessage([\"log\",context.id,\"setfont \" + context.graphics.font,null]);\n     return context;\n };\n \n@@ -3316,6 +3646,7 @@ rpnOperators.setrgbcolor = function(context) {\n \n \n rpnOperators.show = function(context) {\n+\n     const [s] = context.pop(\"string\");\n     // postMessage([\"log\",context.id,\"show : \"+s.value,null])\n     if (!s) return context;\n@@ -3325,63 +3656,94 @@ rpnOperators.show = function(context) {\n     if (!context.graphics.font.length) {\n        return context.error(\"nocurrentfont\");\n     }\n+    \n     const font = rpnFonts[context.graphics.font];\n-    if (context.device.textmode && (!font || !font.value)) { // postMessage([\"log\",context.id,\"show textmode : \"+s.value,null])\n-        for (let n of context.nodes) { \n-           if (n.canshow) context = n.show(s.value, context);\n-        }\n-    } else  if (!font) {\n-\t     return context.error(\"nocurrentfont\");\n-    }\n-    else { // postMessage([\"log\",context.id,\"show else : \"+s.value,null])\n-\t    for (let n of context.nodes) {\n-           if (n.cannofill) { \n-\t           context.nofill = true;\n-\t           context = n.show(s.value, context);\n-\t           context.nofill = false;\n-\t       }\n-        }\n+    if (!font) {\n+       return context.error(\"invalidfont\");\n     }\n-\n-        var ps = \"\";\n-    var type3mode = false;\n-    if (font.value && font.value.FontType.value == 3) {\n-        type3mode = true;\n-//        postMessage([\"status\",context.id,\"type 3\",null]);\n+    \n+    const type3mode = font.value && font.value.FontType.value == 3;\n+//     postMessage([\"log\",context.id,\"FONTVALUE \"+font.value,null]);\n+//    if (font.value) postMessage([\"log\",context.id,\"FONTVALUE \"+Object.keys(font.value),null]);\n+//     postMessage([\"log\",context.id,\"type 3 \"+type3mode,null]);\n+    if (!type3mode) {\n+    \tif (context.device.textmode) { \n+        \tfor (let n of context.nodes) { \n+           \t\tif (n.canshow) context = n.show(s.value, context);\n+        \t}\n+        }\n+    \telse { \n+\t    \tfor (let n of context.nodes) {\n+           \t\tif (n.cannofill) { \n+\t           \t\tcontext.nofill = true;\n+\t           \t\tcontext = n.show(s.value, context);\n+\t           \t\tcontext.nofill = false;\n+\t       \t\t}\n+        \t}\n+    \t}\n+    }\n+\n+    var ps = \"10 dict begin\";\n+    if (type3mode) {\n         context.stack.push(font);\n+\n         ps += \" /currentfontdict exch def \";\n+//         ps += \" (START) print currentfontdict /FontName get print\"\n         ps += \" currentpoint currentpoint translate \";\n         ps += \" currentmatrix \";\n         ps += \" currentfontdict /FontMatrix get 0 get currentfontdict /FontMatrix get 3 get  scale \";\n         ps +=  context.graphics.size + \" \" + context.graphics.size + \" scale  \";\n         for (let i = 0; i < s.value.length; i++) {\n-             ps += \" currentfontdict \" + s.value.charCodeAt(i) + \" currentfontdict begin BuildChar end 0 translate\";\n+        \t let c = rpnMacRomanChar(s.value,i); // s.value.charCodeAt(i)\n+//         \t % ps += \" (CHAR) print currentfontdict /FontName get print \"\n+             ps += \" currentfontdict \" + c + \" currentfontdict begin BuildChar end 0 translate\";\n         }\n     } else {\n-    let scale = context.graphics.size / font.head.unitsPerEm;\n-    ps += \" currentpoint currentpoint translate currentmatrix \" + scale + \" \" + scale + \" scale  \";\n-    for (let i = 0; i < s.value.length; i++) {\n-        const c = s.value.charCodeAt(i);\n-        const gi = font.glyphIndex(c);\n-\t\t// if (c > 127) console.log(\"encoding \" + s.value.substr(i,1) + \" \" + c + \" \" + gi);\n-        ps += font.glyphPath(gi);\n-        ps += \" fill \";\n-        ps += font.glyphWidth(gi) + \" 0 translate \";\n-    }\n-    }\n-    ps += \" 0 0 moveto setmatrix neg exch neg exch translate \";\n+    \tps += \" currentpoint currentpoint translate \";\n+\n+\n+    \tps += \" currentmatrix \";\n+\n+ \n+    \tps +=  1/ font.head.unitsPerEm + \" \" + 1/ font.head.unitsPerEm + \" scale  \";\n+    \tps +=  context.graphics.size + \" \" + context.graphics.size + \" scale  \";\n+    \t// ps += \" currentmatrix print \"\n+    \t\n+    \tfor (let i = 0; i < s.value.length; i++) {\n+        \t// const c = s.value.charCodeAt(i);\n+        \tlet c = s.value.charCodeAt(i);\n+        \tconst gi = font.glyphIndex(c);\n+\n+        \tps += \" \" + font.glyphPath(gi) + \" \";        \t\n+        \t\n+        \tps += \" fill \";\n+        \t\n+        \t/*\n+        \tps += \"  (\" + font.glyphPath(gi) + \") print \";\n+        \tps += \"gsave 0 0 1 setrgbcolor 194 729 moveto 194 729 lineto 194 0 lineto 100 0 lineto 100 729 lineto closepath stroke grestore \" \n+        \t*/\n+        \tps += font.glyphWidth(gi) + \" 0 translate \";\n+    \t}\n+    }\n+    ps += \" 0 0 moveto setmatrix neg exch neg exch translate end \";\n     context.showmode = !type3mode;\n+ \n     context = rpn(ps, context);\n+    // postMessage([\"status\",context.id,\"showend dstack \" + context.dictstack.length,null]);\n     context.showmode = false;\n     return context;\n };\n \n+rpnOperators.showfile = function(context) {\n+\tconst [filename] = context.pop(\"string\");\n+    for (let n of context.nodes) \n+    if (n.canfiles) context = n.showfile(context, filename.value);\n+    return context;\n+};\n+\n rpnOperators.showpage = function(context) {\n     for (let n of context.nodes) context = n.showpage(context);\n-    // context.refresh();\n     context.initgraphics();\n-//   console.log(\"SHOWPAGE\")\n-// context.error(\"refreshpage\");\n     return context;\n };\n \n@@ -3424,14 +3786,27 @@ rpnOperators.stringwidth = function(context) {\n     if (!s) return context;\n     const font = rpnFonts[context.graphics.font];\n     if (!font) {\n-       return context.error(\"nocurrentfont\");\n+       return context.error(\"invalidfont\");\n     }\n-    const scale = context.graphics.size / font.head.unitsPerEm;\n+    const type3mode = font.value && font.value.FontType.value == 3;\n     var width = 0;\n-    for (let i = 0; i < s.value.length; i++) {\n-        const c = s.value.charCodeAt(i);\n-        const gi = font.glyphIndex(c);\n-        width += font.glyphWidth(gi) * scale;\n+    if (type3mode) {\n+    \tlet scale = context.graphics.size * font.value.FontMatrix.value[0].value;\n+    \tfor (let i = 0; i < s.value.length; i++) {\n+        \tconst c = rpnMacRomanChar(s.value,i);\n+    \t\tlet gn = font.value.Encoding.value[c].value;\n+    \t\t// postMessage([\"log\",context.id,\"code \" + gn,null]);\n+    \t\twidth += font.value.Metrics.value[gn].value * scale;\n+    \t}\n+    \t\n+    } else {\n+    \n+    \tlet scale = context.graphics.size / font.head.unitsPerEm;\n+    \tfor (let i = 0; i < s.value.length; i++) {\n+        \tlet c = s.value.charCodeAt(i);\n+        \tlet gi = font.glyphIndex(c);\n+        \twidth += font.glyphWidth(gi) * scale;\n+    \t}\n     }\n     context.stack.push(new rpnNumber(width));\n     context.stack.push(new rpnNumber(0));\n@@ -3533,31 +3908,56 @@ rpnOperators.widthshow = function(context) {\n     if (!s) return context;\n     const font = rpnFonts[context.graphics.font];\n     if (!font) {\n-       return context.error(\"nocurrentfont\");\n+       return context.error(\"invalidfont\");\n     }\n-    const scale = context.graphics.size / font.head.unitsPerEm;\n-    const [cx0, cy0] = [cx.value / scale, cy.value / scale];\n-    const [currentx0, currenty0] = context.graphics.current;\n+    const type3mode = font.value && font.value.FontType.value == 3;\n+    var ps = \"\";\n+    var scale = 1;\n     var targetwidth = 0;\n-    var ps = \" currentpoint currentpoint translate currentmatrix \" + scale + \" \" + scale + \" scale \";\n-    for (let i = 0; i < s.value.length; i++) {\n-        const c = s.value.charCodeAt(i);\n-        const gi = font.glyphIndex(c);\n-        ps += font.glyphPath(gi);\n-        ps += \" fill \";\n-        ps += font.glyphWidth(gi) + \" 0 translate \";\n-        targetwidth +=  font.glyphWidth(gi) ;\n-        if (c == ch.value) {\n-           ps += cx0 + \" \" + cy0 + \" translate \";\n-           targetwidth +=  cx0 ;\n-        }\n+    let [currentx0, currenty0] = context.graphics.current;\n+    if (type3mode) { \n+        context.stack.push(font);\n+    \tps += \" /currentfontdict exch def \";\n+//         ps += \" (START) print currentfontdict /FontName get print\"\n+        ps += \" currentpoint currentpoint translate \";\n+        ps += \" currentmatrix \";\n+        ps += \" currentfontdict /FontMatrix get 0 get currentfontdict /FontMatrix get 3 get  scale \";\n+        ps +=  context.graphics.size + \" \" + context.graphics.size + \" scale  \";\n+        for (let i = 0; i < s.value.length; i++) {\n+        \t let c = rpnMacRomanChar(s.value,i); // s.value.charCodeAt(i)\n+//         \t % ps += \" (CHAR) print currentfontdict /FontName get print \"\n+             ps += \" currentfontdict \" + c + \" currentfontdict begin BuildChar end 0 translate \";\n+                if (c == ch.value)\n+                     ps += cx.value + \" currentfontdict /FontMatrix get 0 get div \" + context.graphics.size + \" div \" + cy.value + \"  currentfontdict /FontMatrix get 3 get div \" + context.graphics.size + \" div translate \";\n+        \n+    \t}\n+    } else {\n+    \tscale = context.graphics.size / font.head.unitsPerEm;\n+    \tlet [cx0, cy0] = [cx.value / scale, cy.value / scale];\n+\n+\n+    \tps += \" currentpoint currentpoint translate currentmatrix \" + scale + \" \" + scale + \" scale \";\n+    \tfor (let i = 0; i < s.value.length; i++) {\n+        \tconst c = s.value.charCodeAt(i);\n+        \tconst gi = font.glyphIndex(c);\n+        \tps += font.glyphPath(gi);\n+        \tps += \" fill \";\n+        \tps += font.glyphWidth(gi) + \" 0 translate \";\n+        \ttargetwidth +=  font.glyphWidth(gi) ;\n+       \t \tif (c == ch.value) {\n+           \t\tps += cx0 + \" \" + cy0 + \" translate \";\n+           \t\ttargetwidth +=  cx0 ;\n+        \t}\n+    \t}\n+\n     }\n+\n     ps += \" setmatrix neg exch neg exch translate \";   \n-    context.showmode = true;\n+    context.showmode = !type3mode;\n     context = rpn(ps, context);\n     context.showmode = false;\n     \n-    if (context.device.textmode) {\n+    if (context.device.textmode && !type3mode) {    \n         targetwidth *= scale;\n         context.graphics.current = [currentx0, currenty0];\n         for (let n of context.nodes) { \n@@ -4329,18 +4729,37 @@ rpnRomanMapping = `0x20    0x0020    # SPACE\n 0xFF    0x02C7    # CARON`;\n \n rpnRomanDict = {};\n-for (let lines of rpnRomanMapping.split(rpnEOL)) {\n-    rpnRomanDict[parseInt(lines.substr(5, 6))] = parseInt(lines.substr(0, 4));\n+rpnInverseRomanDict = {};\n+for (let line of rpnRomanMapping.split(rpnEOL)) {\n+//\tconsole.log(line)\n+//\tconsole.log(line.substr(8, 6)); console.log(parseInt(line.substr(8, 6),16));\n+//\tconsole.log(line.substr(0, 4)); console.log(parseInt(line.substr(0,4),16));\n+\tif (parseInt(line.substr(0, 4),16)) {\n+    \trpnRomanDict[parseInt(line.substr(8, 6),16)] = parseInt(line.substr(0, 4),16);\n+    \trpnInverseRomanDict[parseInt(line.substr(0, 4),16)] = parseInt(line.substr(8, 6),16) ;\n+    }\n+}\n+// console.log(\"rpnRomanDict\")\n+// console.log(JSON.stringify(rpnRomanDict))\n+\n+rpnMacRomanChar = function(s,i) {\n+\tlet u = s.codePointAt(i);\n+\t   if (u == 8209) u = 45; // nonbreaking hyphen\n+       var r = rpnRomanDict[u];\n+       if (!r) r = 32;\n+       return r;\n }\n \n rpnMacRomanEncoding = function(s) {\n    const chars = [];\n    for (let i = 0; i < s.length; i++) {\n-       const u = s.codePointAt(i);\n+\t   let ch = s.substr(i, 1);\n+       let u = s.codePointAt(i);\n        var r = rpnRomanDict[u];\n        if (!r) r = 32;\n+       if (u == 8209) { r=45; ch = '-'; } // nonbreaking hyphen\n        if (r < 128) {\n-          chars.push(s.substr(i, 1));\n+          chars.push(ch);\n        } else {\n           chars.push(rpnBackSlash + r.toString(8));\n        }\n@@ -4377,6 +4796,19 @@ rpnSubstitutionDict[\"/Nimbus-Mono-Regular\"] = \"/Courier\";\n rpnSubstitutionDict[\"/Nimbus-Mono-Oblique\"] = \"/Courier-Oblique\";\n rpnSubstitutionDict[\"/Nimbus-Mono-Bold\"] = \"/Courier-Bold\";\n rpnSubstitutionDict[\"/Nimbus-Mono-BoldOblique\"] = \"/Courier-BoldOblique\";\n+rpnSubstitutionDict[\"/NimbusRoman\"] = \"/Times-Roman\";\n+rpnSubstitutionDict[\"/NimbusRomanItalic\"] = \"/Times-Italic\";\n+rpnSubstitutionDict[\"/NimbusRomanBold\"] = \"/Times-Bold\";\n+rpnSubstitutionDict[\"/NimbusRomanBoldItalic\"] = \"/Times-BoldItalic\";\n+rpnSubstitutionDict[\"/NimbusSans\"] = \"/Helvetica\";\n+rpnSubstitutionDict[\"/NimbusSansRegular\"] = \"/Helvetica\";\n+rpnSubstitutionDict[\"/NimbusSansItalic\"] = \"/Helvetica-Oblique\";\n+rpnSubstitutionDict[\"/NimbusSansBold\"] = \"/Helvetica-Bold\";\n+rpnSubstitutionDict[\"/NimbusSansBoldItalic\"] = \"/Helvetica-BoldOblique\";\n+rpnSubstitutionDict[\"/NimbusMonoRegular\"] = \"/Courier\";\n+rpnSubstitutionDict[\"/NimbusMonoOblique\"] = \"/Courier-Oblique\";\n+rpnSubstitutionDict[\"/NimbusMonoBold\"] = \"/Courier-Bold\";\n+rpnSubstitutionDict[\"/NimbusMonoBoldOblique\"] = \"/Courier-BoldOblique\";\n \n \n \n@@ -5405,7 +5837,7 @@ class tinyPStag extends HTMLElement {\n \t            urlnode4.part = \"pdfurl\";\n \t            urlnode4.className = \"pdfurl\";\n \t            urlnode4.innerHTML = \"PDF\";\n-\t            urlnode4.style.display = \"inline\";\n+\t            urlnode4.style.display = \"none\";\n \t            this.nodes.pdf = node4;\n \t            this.nodes.pdfurl = urlnode4;\n \t            context.device.pdfurl = 1;\n@@ -5441,14 +5873,14 @@ class tinyPStag extends HTMLElement {\n                 urlnode5.part = \"gcodeurl\";\n                 urlnode5.className = \"gcodeurl\";\n                 urlnode5.innerHTML = \"GCODE\";\n-                urlnode5.style.display = \"inline\";\n+                urlnode5.style.display = \"none\";\n                 this.nodes.gcode = node5;\n                 this.nodes.gcodeurl = urlnode5;\n                 context.device.gcodeurl = 1;\n             } else {\n                 this.nodes.gcode = node5;\n             }\n-        }\n+        } \n \n \n \t\tif (node) divnode.appendChild(node);\n@@ -5472,7 +5904,9 @@ class tinyPStag extends HTMLElement {\n \t    this.shadow.appendChild(divurlnode);\n \t    this.shadow.appendChild(errornode);\n \t   }\n-    \n+\t   \n+    this.shadowRoot.querySelector('.error').innerHTML = \"\";\n+        \n     const worker = rpnWorker(this.innerHTML, context);\n     \n     worker.onmessage = function (e) {\n@@ -5495,10 +5929,12 @@ class tinyPStag extends HTMLElement {\n \t\t\t\t\t\t\tctx.putImageData(data,0,0);\n \t\t\t\t\t\t\turlnode = shadow.querySelector('.jsrawurl');\n \t\t\t\t\t\t\tif (urlnode) {\n-\t\t\t\t\t\t\t\turl = ctx.canvas.toDataURL();\n-\t\t\t\t\t\t\t\turlnode.href = url;\n+\t\t\t\t\t\t\t\t// url = ctx.canvas.toDataURL();\n+\t\t\t\t\t\t\t\t// urlnode.href = url;\n \t\t\t\t\t\t\t\turlnode.setAttribute(\"download\", \"PS.png\");\n+\t\t\t\t\t\t\t\tctx.canvas.toBlob( (blob) => { urlnode.href = URL.createObjectURL(blob); } );\n \t\t\t\t\t\t\t}\n+\t\t\t\t\t\t\t\n \t\t\t\t\t\t\tbreak;\n \t\t\tcase \"rawzip\":  break;\n \t\t\tcase \"canvas\":  node = document.getElementById(id);\n@@ -5510,17 +5946,22 @@ class tinyPStag extends HTMLElement {\n \t\t\t\t\t\t\tctx.putImageData(data,0,0);\n \t\t\t\t\t\t\turlnode = shadow.querySelector('.jscanvasurl');\n \t\t\t\t\t\t\tif (urlnode) {\n-\t\t\t\t\t\t\t\tlet url = ctx.canvas.toDataURL();\n+\t\t\t\t\t\t\t\tlet url = ctx.canvas.toDataURL(); // we need that for zip later\n+\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\n \t\t\t\t\t\t\t\tlet oc = document.createElement(\"CANVAS\");\n \t\t\t\t\t\t\t\toc.width = context.width*2;\n \t\t\t\t\t\t\t\toc.height = context.height*2;\n \t\t\t\t\t\t\t\tlet ctxo = oc.getContext(\"2d\"); \n                                 ctxo.drawImage(canvasnode, 0, 0, oc.width, oc.height);\n-\t\t\t\t\t\t\t\tlet urljpg = oc.toDataURL(\"image/jpeg\", 0.8);\n-\t\t\t\t\t\t\t\turlnode.href = url;\n+\t\t\t\t\t\t\t\tlet urljpg = oc.toDataURL(\"image/png\", 0.8);\n                                 let d = new Date();\n \t                            let fn = \"PS\" + '_' + d.toISOString().replaceAll(\"-\", \"\").replaceAll(\":\", \"\").replaceAll(\"T\", \"_\").substr(0, 13) + \".png\";\n                                 urlnode.setAttribute(\"download\", fn);\n+                                \n+                                ctx.canvas.toBlob( (blob) => { urlnode.href = URL.createObjectURL(blob); } );\n+                                \n+                                \n+                                \n                                 if (context.device.zip) {\n                                 \tlet urlnodez = shadow.querySelector('.jscanvasurlz');\n \t\t\t\t\t\t\t\t    //rpnFrames[urlnodez.id].push(url);\n@@ -5581,7 +6022,11 @@ class tinyPStag extends HTMLElement {\n \t\t\t\t\t\t\turlnode = shadow.querySelector('.svgurl');\n \t\t\t\t\t\t\tif (urlnode) {\n \t\t\t\t\t\t\t\tlet file = rpnStartTag + \"?xml version='1.0' encoding='UTF-8'?\" + rpnEndTag + data;\n-                                url = \"data:image/svg+xml;base64,\" + rpnBbtoaUnicode(file);\n+                               //  url = \"data:image/svg+xml;base64,\" + rpnBbtoaUnicode(file);\n+                                \n+                                let blob = new Blob([file], { type: 'image/svg' });\n+\t\t\t\t\t\t\t\turl = URL.createObjectURL(blob);\n+                                                                \n                                 urlnode.href = url;\n                                 let d = new Date();\n \t                            let fn = \"PS\" + '_'+d.toISOString().replaceAll(\"-\",\"\").replaceAll(\":\",\"\").replaceAll(\"T\",\"_\").substr(0,13)+\".svg\";\n@@ -5621,7 +6066,10 @@ class tinyPStag extends HTMLElement {\n \t\t\t\t\t\t\t\n \t\t\t\t\t\t\t    }\n \t\t\t\t\t\t\t\tlet file = rpnStartTag + \"?xml version='1.0' encoding='UTF-8'?\" + rpnEndTag + globalnode.outerHTML;\n-\t\t\t\t\t\t\t    url = \"data:image/svg+xml;base64,\" + rpnBbtoaUnicode(file);\n+\t\t\t\t\t\t\t    // url = \"data:image/svg+xml;base64,\" + rpnBbtoaUnicode(file);\n+\t\t\t\t\t\t\t    \n+\t\t\t\t\t\t\t    let blob = new Blob([file], { type: 'image/svg' });\n+\t\t\t\t\t\t\t\turl = URL.createObjectURL(blob);\n \t\t\t\t\t\t\t    \n                                 urlnodea.href = url;\n                                 let d = new Date();\n@@ -5632,19 +6080,23 @@ class tinyPStag extends HTMLElement {\n \t\t\t\t\t\t\t}}\t\t \t\t            \n \t\t\t\t\t\t\tbreak;\n \n-            case \"gcode\":   node = document.getElementById(id); console.log(\"gcode\");\n+            case \"gcode\":   node = document.getElementById(id);\n                             shadow = node.shadowRoot; \n                             gcodenode = shadow.querySelector('.jsgcode');\n                             if (gcodenode) {\n                                 gcodenode.innerHTML = data.join(\"\\n\");\n                                 urlnode = shadow.querySelector('.gcodeurl');\n                                 if (urlnode) { console.log(\"gcodeurl setting\")\n                                     let file =  data.join(\"\\n\");\n-                                    url = \"data:text/x-gcode;base64,\" + rpnBbtoaUnicode(file);\n+                                    // url = \"data:text/x-gcode;base64,\" + rpnBbtoaUnicode(file);\n+                                    let blob = new Blob([file], { type: 'text/x-gcode' });\n+\t\t\t\t\t\t\t\t    url = URL.createObjectURL(blob);\n+\n                                     urlnode.href = url;\n                                     let d = new Date();\n                                     let fn = \"PS\" + '_'+d.toISOString().replaceAll(\"-\",\"\").replaceAll(\":\",\"\").replaceAll(\"T\",\"_\").substr(0,13)+\".gcode\";\n                                     urlnode.setAttribute(\"download\", fn);\n+                                    urlnode.style.display = \"inline\";\n                                 }\n                             }\n                             break;\n@@ -5655,27 +6107,53 @@ class tinyPStag extends HTMLElement {\n                             gcodenode = shadow.querySelector('.jsgcode');\n                             if (gcodenode)\n                                 gcodenode.outerHTML = gcodenode.outerHTML + \" \";\n-                           \n-                                               \n-                             break;\n+                            break;\n \n \t\t\t\n-\t\t\tcase \"pdf\":     node = document.getElementById(id);\n+\t\t\tcase \"pdf\":     console.log(\"pdf\");\n+\t\t\t                node = document.getElementById(id);\n \t\t\t\t            shadow = node.shadowRoot; \n \t\t\t\t\t\t\tpdfnode = shadow.querySelector('.pdf');\n \t\t\t                pdfnode.src = data;\n \t\t\t\t\t\t\turlnode = shadow.querySelector('.pdfurl');\n-\t\t\t\t\t\t\turlnode.href = data;\n-\t\t\t                urlnode.setAttribute(\"download\", \"PS.pdf\");\n+\t\t\t\t\t\t\t\n+\t\t\t\t\t\t\tif (rpnPDFs[id]) {\n+\t\t\t\t\t\t\t\tconsole.log(\"zip\");\n+\t\t\t\t\t\t\t\trpnPDFs[id].makeZip(urlnode);\n+\t\t\t\t\t\t\t\turlnode.setAttribute(\"download\", \"PS-PDF.zip\");\n+\t\t\t\t\t\t\t\trpnPDFs[id] = null;\n+\n+\t\t\t\t\t\t\t} else {\n+\t\t\t\t\t\t\t\tlet blob = new Blob([data], { type: 'application/pdf' });\n+\t\t\t\t\t\t\t\turl = URL.createObjectURL(blob);\n+\t\t\t\t\t\t\t\turlnode.href = url;\n+\t\t\t\t\t\t\t\turlnode.setAttribute(\"download\", \"PS.pdf\");\n+\t\t\t\t\t\t\t}\n+\t\t\t                \n+\t\t\t                urlnode.style.display = \"inline\";\n \t\t\t                break;\n+\t\t\t\n+\t\t\tcase \"pdffile\":  \n+\t\t\t                let blist = data.split(\":\");\n+\t\t\t\t            let filename = blist.pop().replaceAll(/[^A-Za-z0-9]/g,\"-\").toLowerCase();\n+\t\t\t\t            let b = blist.join(\":\");\n+\t\t\t\t            console.log(\"pdffile \"+filename);\n+\t\t\t\t            \n+\t\t\t\t            if (!rpnPDFs[id]) {\n+\t\t\t\t            \tlet z = new rpnZip(\"PDF.zip\");\n+\t\t\t\t\t\t\t\trpnPDFs[id] = z;\n+\t\t\t\t            }\n+\t\t\t\t            rpnPDFs[id].binary2zip(filename+\".pdf\",b);\n+\t\t\t\t\t\t\tbreak;\n+\t\t\t\t\t\t\t\n \t\t\t                \n \t\t\tcase \"device\":  node = document.getElementById(id);\n \t\t\t                node.setAttribute(\"width\", data.width);\n \t\t\t                node.setAttribute(\"height\", data.height);\n \t\t\t                node.parentNode.setAttribute(\"width\", data.width);\n-\t\t\t                node.parentNode.setAttribute(\"height\", data.height + 20);\n+\t\t\t                // node.parentNode.setAttribute(\"height\", data.height + 20);\n \t\t\t                node.parentNode.style.width = (data.width) + \"px\"; \n-\t\t\t                node.parentNode.style.height = (data.height + 20) + \"px\"; \n+\t\t\t                // node.parentNode.style.height = (data.height + 20) + \"px\"; \n \t\t\t                shadow = node.shadowRoot; \n \t\t\t\t\t\t\tsvgnode = shadow.querySelector('.divsvg');\n \t\t\t\t\t\t\tif (svgnode) {\n@@ -5702,7 +6180,7 @@ class tinyPStag extends HTMLElement {\n \t\t\t                errornode.style.color = \"#00825A\";\n \t\t\t                errornode.width = context.width;\n \t\t\t                errornode.height = context.height;\n-                            errornode.innerHTML = data;\n+                            errornode.innerHTML += data;\n                             break;\n                             \n             case \"log\":     console.log(data);\n@@ -5712,8 +6190,12 @@ class tinyPStag extends HTMLElement {\n     \n     };\n \n+\n+let rpnMessage = this.innerHTML.replaceAll(\"&amp;\",\"&\").replaceAll(\"&gt;\",\">\").replaceAll(\"&lt;\",\"<\");\n     \n-    worker.postMessage([\"rpn\",this.id, this.innerHTML,JSON.stringify(context)])\n+worker.postMessage([\"rpn\",this.id,rpnMessage,JSON.stringify(context) ]);\n+    \n+\n     \n     /*\n     context = rpn(this.innerHTML, context, true);\n@@ -5771,10 +6253,11 @@ workeronmessage = function (e) {\n \t}\n \t\n \tconsole.log(\"worker in: \" + action + \" \" + id)\n-\t\n+\t\t\n \tswitch (action)\n \t{\n-\t\tcase \"rpn\": console.log(\"rpn \" + data);\n+\t\tcase \"rpn\": \n+\t\t            // postMessage([\"log\", context.id,data,null])\n \t\t            if (data.slice(0,1)==\"!\") {\n \t\t\t            data += \" currentpointexists { 0 0 1 setrgbcolor 1 setalpha gsave currentpoint newpath 4 0 360 arc fill grestore stroke showpage } if \";\n \t\t\t            \n@@ -5806,7 +6289,7 @@ workeronmessage = function (e) {\n \n function rpnWorker() {\n \tconst workercode = []\n-\tworkercode.push('rpnFonts = ' + JSON.stringify(rpnFonts));\n+\tworkercode.push('rpnFonts = {}'); // + JSON.stringify(rpnFonts));\n \tworkercode.push('rpnFiles = ' + JSON.stringify(rpnFiles));\n \tworkercode.push('rpnOperators = ' + JSON.stringify(rpnOperators));\n \tworkercode.push('rpnHeapElement = ' + rpnHeapElement.toString());\n@@ -5845,6 +6328,7 @@ function rpnWorker() {\n \tworkercode.push('rpnBinary = ' + rpnBinary.toString());\n \tworkercode.push('rpnTTF = ' + rpnTTF.toString());\n \tworkercode.push('rpnRomanDict = ' + JSON.stringify(rpnRomanDict));\n+   workercode.push('rpnMacRomanChar = ' + rpnMacRomanChar.toString());\n \tworkercode.push('rpnMacRomanEncoding = ' + rpnMacRomanEncoding.toString());\n \tworkercode.push('rpnSubstitutionDict = ' + JSON.stringify(rpnSubstitutionDict));\n \tworkercode.push('rpnBbtoaUnicode = ' + rpnBbtoaUnicode.toString());","previous_filename":"inc/skins/tinyps131.js"},{"sha":"815b846a7de3268dd7f5415aa3e42e9a9be06049","filename":"inc/special/deny.php","status":"modified","additions":11,"deletions":7,"changes":18,"blob_url":"https://github.com/bellenuit/sofawiki/blob/f93be8263f4deab8fb8884db7e03a1767a27bbad/inc%2Fspecial%2Fdeny.php","raw_url":"https://github.com/bellenuit/sofawiki/raw/f93be8263f4deab8fb8884db7e03a1767a27bbad/inc%2Fspecial%2Fdeny.php","contents_url":"https://api.github.com/repos/bellenuit/sofawiki/contents/inc%2Fspecial%2Fdeny.php?ref=f93be8263f4deab8fb8884db7e03a1767a27bbad","patch":"@@ -82,16 +82,20 @@\n \t\t$t = file_get_contents($file);\n \t\t$ts = explode(']]',$t);\n \t\t$ts2 = [];\n-\t\tforeach($ts as $v) {\n+\t\tforeach($ts as $v)\n+\t\t{\n \t\t\t$fs = explode('::',$v);\n-\t\t\t$origin = new DateTimeImmutable($fs[1]);\n-\t\t\t$target = new DateTimeImmutable(date('Y-m-d',time()));\n-            $interval = $target->diff($origin);\n-            if ($interval->days < 32)\n-               $ts2 []= $v;\n+\t\t\tif (is_array($fs) and count($fs) > 1 && $fs[1] && DateTime::createFromFormat('Y-m-d', $fs[1]) !== false) \n+\t\t\t{   \n+\t\t\t\t$origin = new DateTimeImmutable($fs[1]);\n+\t\t\t\t$target = new DateTimeImmutable(date('Y-m-d',time()));\n+\t\t\t\t$interval = $target->diff($origin);\n+\t\t\t\tif ($interval->days < 32)\n+\t\t\t\t$ts2 []= $v;\n+\t\t\t}\n \t\t}\n \t\tnatsort($ts2);\n-\t\t$ts2 = array_filter($ts2); // remove empry\n+\t\t$ts2 = array_filter($ts2); // remove empty\n \t\t$swParsedContent .= \"<p>Denied: <br><b>\".join(']]<br>',$ts2).\"]]</b>\";\n \t}\n \t"},{"sha":"2241ec3874fa4afecfe03fcb11277c31e764f631","filename":"inc/special/indexes.php","status":"modified","additions":38,"deletions":10,"changes":48,"blob_url":"https://github.com/bellenuit/sofawiki/blob/f93be8263f4deab8fb8884db7e03a1767a27bbad/inc%2Fspecial%2Findexes.php","raw_url":"https://github.com/bellenuit/sofawiki/raw/f93be8263f4deab8fb8884db7e03a1767a27bbad/inc%2Fspecial%2Findexes.php","contents_url":"https://api.github.com/repos/bellenuit/sofawiki/contents/inc%2Fspecial%2Findexes.php?ref=f93be8263f4deab8fb8884db7e03a1767a27bbad","patch":"@@ -62,15 +62,15 @@\n $done = '';\n \tif (isset($_REQUEST['submitreset'])||isset($_REQUEST['submitresetfields']))\n \t{\n-\t\tunlink($swRoot.'/site/indexes/fields.db');\n+\t\tif (file_exists($swRoot.'/site/indexes/fields.db')) unlink($swRoot.'/site/indexes/fields.db');\n \t\t//$swOvertime = true;\n \t\t\n \t}\n if (isset($_REQUEST['submitreset'])||isset($_REQUEST['submitresetfulltext']))\n \t{\n \t\t\n \t\t\n-\t\tunlink($swRoot.'/site/indexes/fulltext.db');\n+\t\tif (file_exists($swRoot.'/site/indexes/fulltext.db'))  unlink($swRoot.'/site/indexes/fulltext.db');\n \t\t\n \t\t\n \t\t\n@@ -132,15 +132,15 @@\n \tif (isset($_REQUEST['submitreset'])||isset($_REQUEST['submitresetbloom']))\n \t{\n \t \t\n-\t \tunlink($swRoot.'/site/indexes/bloom.raw');\n-\t \tunlink($swRoot.'/site/indexes/bloombitmap.txt');\n+\t \tif (file_exists($swRoot.'/site/indexes/bloom.raw'))  unlink($swRoot.'/site/indexes/bloom.raw');\n+\t \tif (file_exists($swRoot.'/site/indexes/bloombitmap.txt'))  unlink($swRoot.'/site/indexes/bloombitmap.txt');\n \t\t\n \t\tswClearBloom();\t\n \t\t\n-\t\tunlink($swRoot.'/site/indexes/bloom.raw');\n-\t \tunlink($swRoot.'/site/indexes/bloombitmap.txt');\n+\t\tif (file_exists($swRoot.'/site/indexes/bloom.raw')) unlink($swRoot.'/site/indexes/bloom.raw');\n+\t \tif (file_exists($swRoot.'/site/indexes/bloombitmap.txt'))  unlink($swRoot.'/site/indexes/bloombitmap.txt');\n \t \t\n-\t \tunlink($swRoot.'/site/indexes/monogram.db');\n+\t \tif (file_exists($swRoot.'/site/indexes/monogram.txt'))  unlink($swRoot.'/site/indexes/monogram.db');\n \t\n \t\t$done .= '<p>Deleted bloom and monogram';\n \t\t\n@@ -181,8 +181,8 @@\n \t\t$db->currentbitmap = new swBitmap;\n \t\t$db->deletedbitmap = new swBitmap;\n \t\t$db->protectedbitmap = new swBitmap;\n-\t\tunlink($swRoot.'/site/indexes/urls.txt');\n-\t\tunlink($swRoot.'/site/indexes/urls.db');\n+\t\tif (file_exists($swRoot.'/site/indexes/urls.txt')) unlink($swRoot.'/site/indexes/urls.txt');\n+\t\tif (file_exists($swRoot.'/site/indexes/urls.db')) unlink($swRoot.'/site/indexes/urls.db');\n \t\n \t\t//$done .= '<p>Deleted urls, short and bitmaps';\n \t\t$done .= '<p>Deleted urls and bitmaps';\n@@ -727,7 +727,7 @@\n \t\t\t\t\t\t\t\t\t\t\t}\n \t\t\t\t\t\t\t\t\t\t\telse\n \t\t\t\t\t\t\t\t\t\t\t{\n-\t\t\t\t\t\t\t\t\t\t\t\t$bdb = swDba($querypath.$v,'r');\n+\t\t\t\t\t\t\t\t\t\t\t\t$bdb = new swDba($querypath.$v);\n \t\t\t\t\t\t\t\t\t\t\t\tif ($bdb)\n \t\t\t\t\t\t\t\t\t\t\t\t\t$results['filter'] = $bdb->fetch('_filter');\n \t\t\t\t\t\t\t\t\t\t\t\t\n@@ -773,25 +773,53 @@ function querylist()\n \t foreach($files as $file)\n \t {\n \t  \t$key = sprintf('%05d',filesize($file));\n+\t  \t\n+\t  \t// delete queries older than 365 days\n+\t  \t$t = filemtime($file);\n+\t\t$d = date('Y-m-d H:i',$t);\n+\t\tif (time() - $t > 60*60*24*365) \n+\t\t{ \n+\t\t\tunlink($file);\n+\t\t\tcontinue;\n+\t\t}\n+\t  \t\n+\t  \t\n \t   \t$fn = str_replace($querypath,'',$file);\n \t\tif (stristr($fn,'-')) continue;\n \t   \t// $fn = substr($fn,0,-4);\n \t   \t$list[$key.' '.$fn] = $fn;\n \t }\n \t \n \t $files = glob($querypath.'*.db');\n+\t \n \t  \n \t foreach($files as $file)\n \t {\n \t  \t$key = sprintf('%05d',filesize($file));\n+\t  \t\n+\t  \t// ignore queries older than 365 days\n+\t  \t$t = filemtime($file);\n+\t\t$d = date('Y-m-d H:i',$t);\n+\t\tif (time() - $t > 60*60*24*365) \n+\t\t{ \n+\t\t\tunlink($file);\n+\t\t\tcontinue;\n+\t\t}\n+\t  \t\n \t   \t$fn = str_replace($querypath,'',$file);\n \t\tif (stristr($fn,'-')) continue;\n \t   \t// $fn = substr($fn,0,-4);\n \t   \t$list[$key.' '.$fn] = $fn;\n \t }\n+\t \n+\t \n+\t \n \t krsort($list);\n \t return $list;\n }\n+\n+\n+\n function bitmap2canvas($bm,$listrevisions=0,$id='1')\n {\n \t$h = ceil($bm->length /512);"},{"sha":"1f1c88240a8919d36419294f9ed5723536b5aaae","filename":"inc/special/tinyps.php","status":"modified","additions":2,"deletions":2,"changes":4,"blob_url":"https://github.com/bellenuit/sofawiki/blob/f93be8263f4deab8fb8884db7e03a1767a27bbad/inc%2Fspecial%2Ftinyps.php","raw_url":"https://github.com/bellenuit/sofawiki/raw/f93be8263f4deab8fb8884db7e03a1767a27bbad/inc%2Fspecial%2Ftinyps.php","contents_url":"https://api.github.com/repos/bellenuit/sofawiki/contents/inc%2Fspecial%2Ftinyps.php?ref=f93be8263f4deab8fb8884db7e03a1767a27bbad","patch":"@@ -22,8 +22,8 @@\n <textarea id=\"shadoweditor\" name=\"q\" rows=8>100 100 moveto 200 100 lineto 150 166 lineto closepath fill showpage</textarea>\n </form>\n </div><!-- editzone -->\n-<tiny-ps id=\"ps\" width=\"640\" height=\"360\" format=\"svg,svgurl,canvasurl,gcodeurl\" oversampling=\"4\"></tiny-ps>\n-<script src=\"inc/skins/tinyps131.js\"></script>\n+<tiny-ps id=\"ps\" width=\"640\" height=\"360\" format=\"svg,svgurl,canvasurl,gcodeurl,pdfurl\" oversampling=\"4\" textmode=\"1\" transparent=\"1\"></tiny-ps>\n+<script src=\"inc/skins/tinyps135.js\"></script>\n <script src=\"inc/skins/tinyps-extensions.js\"></script>\n <script>rpnFontURLs = '.json_encode($rpnFontURLs).';</script>\n <script>"},{"sha":"8e236ac98d33056947f16a063edd9dc7a8d98ead","filename":"inc/special/update.php","status":"modified","additions":1,"deletions":1,"changes":2,"blob_url":"https://github.com/bellenuit/sofawiki/blob/f93be8263f4deab8fb8884db7e03a1767a27bbad/inc%2Fspecial%2Fupdate.php","raw_url":"https://github.com/bellenuit/sofawiki/raw/f93be8263f4deab8fb8884db7e03a1767a27bbad/inc%2Fspecial%2Fupdate.php","contents_url":"https://api.github.com/repos/bellenuit/sofawiki/contents/inc%2Fspecial%2Fupdate.php?ref=f93be8263f4deab8fb8884db7e03a1767a27bbad","patch":"@@ -30,7 +30,7 @@ function wgets($FromLocation,$ToLocation)\n curl_exec($Channel);\n  \n // Close the CURL channel\n-curl_close($Channel);\n+// curl_close($Channel); depreceated in PHP 8\n  \n // Close file handle\n fclose($File);"},{"sha":"5d4c3e4ac9df60de1dd3d5e5c2702db02f84ca72","filename":"inc/zip.php","status":"modified","additions":3,"deletions":3,"changes":6,"blob_url":"https://github.com/bellenuit/sofawiki/blob/f93be8263f4deab8fb8884db7e03a1767a27bbad/inc%2Fzip.php","raw_url":"https://github.com/bellenuit/sofawiki/raw/f93be8263f4deab8fb8884db7e03a1767a27bbad/inc%2Fzip.php","contents_url":"https://api.github.com/repos/bellenuit/sofawiki/contents/inc%2Fzip.php?ref=f93be8263f4deab8fb8884db7e03a1767a27bbad","patch":"@@ -194,11 +194,11 @@ function unzip($file,$destination)\n \t\t{\n \t\t\tif (@zip_entry_open($zip, $zip_entry, 'r')) \n \t\t\t{\n-\t\t\t  $fp = fopen($destination.'/'.@zip_entry_name($zip_entry), 'w');\n+\t\t\t  $fp = @fopen($destination.'/'.@zip_entry_name($zip_entry), 'w');\n \t\t\t  $buf = @zip_entry_read($zip_entry, @zip_entry_filesize($zip_entry));\n-\t\t\t  fwrite($fp,$buf);\n+\t\t\t  if ($fp) fwrite($fp,$buf);\n \t\t\t  @zip_entry_close($zip_entry);\n-\t\t\t  fclose($fp);\n+\t\t\t  if ($fp) fclose($fp);\n \t\t\t}\n \t\t}\n \t\telse"}]}