diff options
| author | toasted-nutbread <toasted-nutbread@users.noreply.github.com> | 2021-06-27 17:57:00 -0400 | 
|---|---|---|
| committer | GitHub <noreply@github.com> | 2021-06-27 17:57:00 -0400 | 
| commit | 002da9fba8800b796ed98dd83e2ab68b4d37a1cc (patch) | |
| tree | a500cec04f9a587926b2dc20290188a96b4bd209 | |
| parent | 24ef820ba816411288d8bc739f6e69abb511deb0 (diff) | |
Structured content style json (#1771)
* Install css
* Remove unnecessary rule
* Create CSS overrides file
* Create script to generate CSS JSON file
* Generate JSON
* Add test
| -rw-r--r-- | dev/data/structured-content-overrides.css | 50 | ||||
| -rw-r--r-- | dev/generate-structured-content-style.js | 191 | ||||
| -rw-r--r-- | ext/css/structured-content.css | 3 | ||||
| -rw-r--r-- | ext/data/structured-content-style.json | 330 | ||||
| -rw-r--r-- | package-lock.json | 33 | ||||
| -rw-r--r-- | package.json | 1 | ||||
| -rw-r--r-- | test/test-structured-content-style.js | 32 | 
7 files changed, 637 insertions, 3 deletions
| diff --git a/dev/data/structured-content-overrides.css b/dev/data/structured-content-overrides.css new file mode 100644 index 00000000..31873760 --- /dev/null +++ b/dev/data/structured-content-overrides.css @@ -0,0 +1,50 @@ +/* + * Copyright (C) 2021  Yomichan Authors + * + * This program is free software: you can redistribute it and/or modify + * it under the entrys of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program.  If not, see <https://www.gnu.org/licenses/>. + */ + +.gloss-image-container { +    font-size: 1px; +} +.gloss-image-link[data-background=true]>.gloss-image-container { +    /* remove-property background-color */ +} +.gloss-image-container-overlay { +    font-size: initial; +    line-height: initial; +    color: initial; +} +.gloss-image-background { +    background-color: currentColor; +} +:root[data-browser=firefox] .gloss-image-link[data-image-rendering=crisp-edges] .gloss-image, +:root[data-browser=firefox] .gloss-image-link[data-image-rendering=crisp-edges] .gloss-image-background, +:root[data-browser=firefox-mobile] .gloss-image-link[data-image-rendering=crisp-edges] .gloss-image, +:root[data-browser=firefox-mobile] .gloss-image-link[data-image-rendering=crisp-edges] .gloss-image-background { +    /* remove-rule */ +} +.gloss-image-link-text { +    line-height: initial; +} +.gloss-sc-thead, +.gloss-sc-tfoot, +.gloss-sc-th { +    /* remove-property background-color */ +} +.gloss-sc-th, +.gloss-sc-td { +    border-width: 1px; +    border-color: currentColor; +} diff --git a/dev/generate-structured-content-style.js b/dev/generate-structured-content-style.js new file mode 100644 index 00000000..8335efc2 --- /dev/null +++ b/dev/generate-structured-content-style.js @@ -0,0 +1,191 @@ +/* + * Copyright (C) 2021  Yomichan Authors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program.  If not, see <https://www.gnu.org/licenses/>. + */ + +const fs = require('fs'); +const path = require('path'); +const css = require('css'); +const {testMain} = require('./util'); + +function indexOfRule(rules, selectors) { +    const jj = selectors.length; +    for (let i = 0, ii = rules.length; i < ii; ++i) { +        const ruleSelectors = rules[i].selectors; +        if (ruleSelectors.length !== jj) { continue; } +        let okay = true; +        for (let j = 0; j < jj; ++j) { +            if (selectors[j] !== ruleSelectors[j]) { +                okay = false; +                break; +            } +        } +        if (okay) { return i; } +    } +    return -1; +} + +function removeProperty(styles, property, removedProperties) { +    let removeCount = removedProperties.get(property); +    if (typeof removeCount !== 'undefined') { return removeCount; } +    removeCount = 0; +    for (let i = 0, ii = styles.length; i < ii; ++i) { +        const key = styles[i][0]; +        if (key !== property) { continue; } +        styles.splice(i, 1); +        --i; +        --ii; +        ++removeCount; +    } +    removedProperties.set(property, removeCount); +    return removeCount; +} + +function formatRulesJson(rules) { +    // Manually format JSON, for improved compactness +    // return JSON.stringify(rules, null, 4); +    const indent1 = '    '; +    const indent2 = indent1.repeat(2); +    const indent3 = indent1.repeat(3); +    let result = ''; +    result += '['; +    let index1 = 0; +    for (const {selectors, styles} of rules) { +        if (index1 > 0) { result += ','; } +        result += `\n${indent1}{\n${indent2}"selectors": `; +        if (selectors.length === 1) { +            result += `[${JSON.stringify(selectors[0], null, 4)}]`; +        } else { +            result += JSON.stringify(selectors, null, 4).replace(/\n/g, '\n' + indent2); +        } +        result += `,\n${indent2}"styles": [`; +        let index2 = 0; +        for (const [key, value] of styles) { +            if (index2 > 0) { result += ','; } +            result += `\n${indent3}[${JSON.stringify(key)}, ${JSON.stringify(value)}]`; +            ++index2; +        } +        if (index2 > 0) { result += `\n${indent2}`; } +        result += `]\n${indent1}}`; +        ++index1; +    } +    if (index1 > 0) { result += '\n'; } +    result += ']'; +    return result; +} + +function generateRules() { +    const content1 = fs.readFileSync(path.join(__dirname, '..', 'ext/css/structured-content.css'), {encoding: 'utf8'}); +    const content2 = fs.readFileSync(path.join(__dirname, 'data/structured-content-overrides.css'), {encoding: 'utf8'}); +    const stylesheet1 = css.parse(content1, {}).stylesheet; +    const stylesheet2 = css.parse(content2, {}).stylesheet; + +    const removePropertyPattern = /^remove-property\s+([a-zA-Z0-9-]+)$/; +    const removeRulePattern = /^remove-rule$/; + +    const rules = []; + +    // Default stylesheet +    for (const rule of stylesheet1.rules) { +        if (rule.type !== 'rule') { continue; } +        const {selectors, declarations} = rule; +        const styles = []; +        for (const declaration of declarations) { +            if (declaration.type !== 'declaration') { console.log(declaration); continue; } +            const {property, value} = declaration; +            styles.push([property, value]); +        } +        if (styles.length > 0) { +            rules.push({selectors, styles}); +        } +    } + +    // Overrides +    for (const rule of stylesheet2.rules) { +        if (rule.type !== 'rule') { continue; } +        const {selectors, declarations} = rule; +        const removedProperties = new Map(); +        for (const declaration of declarations) { +            switch (declaration.type) { +                case 'declaration': +                    { +                        const index = indexOfRule(rules, selectors); +                        let entry; +                        if (index >= 0) { +                            entry = rules[index]; +                        } else { +                            entry = {selectors, styles: []}; +                            rules.push(entry); +                        } +                        const {property, value} = declaration; +                        removeProperty(entry.styles, property, removedProperties); +                        entry.styles.push([property, value]); +                    } +                    break; +                case 'comment': +                    { +                        const index = indexOfRule(rules, selectors); +                        if (index < 0) { throw new Error('Could not find rule with matching selectors'); } +                        const comment = declaration.comment.trim(); +                        let m; +                        if ((m = removePropertyPattern.exec(comment)) !== null) { +                            const property = m[1]; +                            const removeCount = removeProperty(rules[index].styles, property, removedProperties); +                            if (removeCount === 0) { throw new Error(`Property removal is unnecessary; ${property} does not exist`); } +                        } else if ((m = removeRulePattern.exec(comment)) !== null) { +                            rules.splice(index, 1); +                        } +                    } +                    break; +            } +        } +    } + +    // Remove empty +    for (let i = 0, ii = rules.length; i < ii; ++i) { +        if (rules[i].styles.length > 0) { continue; } +        rules.splice(i, 1); +        --i; +        --ii; +    } + +    return rules; +} + +function generateRulesJson() { +    return formatRulesJson(generateRules()); +} + +function getOutputPath() { +    return path.join(__dirname, '..', 'ext/data/structured-content-style.json'); +} + +function main() { +    const outputFileName = getOutputPath(); +    const json = generateRulesJson(); +    fs.writeFileSync(outputFileName, json, {encoding: 'utf8'}); +} + + +if (require.main === module) { +    testMain(main, process.argv.slice(2)); +} + + +module.exports = { +    generateRules, +    generateRulesJson, +    getOutputPath +}; diff --git a/ext/css/structured-content.css b/ext/css/structured-content.css index cff2c83c..ea0bf5cd 100644 --- a/ext/css/structured-content.css +++ b/ext/css/structured-content.css @@ -201,9 +201,6 @@      table-layout: auto;      border-collapse: collapse;  } -.gloss-sc-tbody { -    background-color: transparent; -}  .gloss-sc-thead,  .gloss-sc-tfoot,  .gloss-sc-th { diff --git a/ext/data/structured-content-style.json b/ext/data/structured-content-style.json new file mode 100644 index 00000000..80a71e1c --- /dev/null +++ b/ext/data/structured-content-style.json @@ -0,0 +1,330 @@ +[ +    { +        "selectors": [".gloss-image-container"], +        "styles": [ +            ["display", "inline-block"], +            ["white-space", "nowrap"], +            ["max-width", "100%"], +            ["position", "relative"], +            ["vertical-align", "top"], +            ["line-height", "0"], +            ["overflow", "hidden"], +            ["font-size", "1px"] +        ] +    }, +    { +        "selectors": [".gloss-image-link"], +        "styles": [ +            ["cursor", "inherit"], +            ["color", "inherit"], +            ["display", "inline-block"], +            ["position", "relative"], +            ["line-height", "1"] +        ] +    }, +    { +        "selectors": [".gloss-image-link[href]:hover"], +        "styles": [ +            ["cursor", "pointer"] +        ] +    }, +    { +        "selectors": [".gloss-image-container-overlay"], +        "styles": [ +            ["position", "absolute"], +            ["left", "0"], +            ["top", "0"], +            ["width", "100%"], +            ["height", "100%"], +            ["display", "table"], +            ["table-layout", "fixed"], +            ["white-space", "normal"], +            ["font-size", "initial"], +            ["line-height", "initial"], +            ["color", "initial"] +        ] +    }, +    { +        "selectors": [".gloss-image-link[data-has-image=true][data-image-load-state=load-error] .gloss-image-container-overlay::after"], +        "styles": [ +            ["content", "'Image failed to load'"], +            ["display", "table-cell"], +            ["width", "100%"], +            ["height", "100%"], +            ["vertical-align", "middle"], +            ["text-align", "center"], +            ["padding", "0.25em"] +        ] +    }, +    { +        "selectors": [".gloss-image-background"], +        "styles": [ +            ["--image", "none"], +            ["position", "absolute"], +            ["left", "0"], +            ["top", "0"], +            ["width", "100%"], +            ["height", "100%"], +            ["-webkit-mask-repeat", "no-repeat"], +            ["-webkit-mask-position", "center center"], +            ["-webkit-mask-mode", "alpha"], +            ["-webkit-mask-size", "contain"], +            ["-webkit-mask-image", "var(--image)"], +            ["mask-repeat", "no-repeat"], +            ["mask-position", "center center"], +            ["mask-mode", "alpha"], +            ["mask-size", "contain"], +            ["mask-image", "var(--image)"], +            ["background-color", "currentColor"] +        ] +    }, +    { +        "selectors": [".gloss-image"], +        "styles": [ +            ["display", "inline-block"], +            ["vertical-align", "top"], +            ["object-fit", "contain"], +            ["border", "none"], +            ["outline", "none"] +        ] +    }, +    { +        "selectors": [".gloss-image-link[data-has-aspect-ratio=true] .gloss-image"], +        "styles": [ +            ["position", "absolute"], +            ["left", "0"], +            ["top", "0"], +            ["width", "100%"], +            ["height", "100%"] +        ] +    }, +    { +        "selectors": [".gloss-image:not([src])"], +        "styles": [ +            ["display", "none"] +        ] +    }, +    { +        "selectors": [ +            ".gloss-image-link[data-image-rendering=pixelated] .gloss-image", +            ".gloss-image-link[data-image-rendering=pixelated] .gloss-image-background" +        ], +        "styles": [ +            ["image-rendering", "auto"], +            ["image-rendering", "-moz-crisp-edges"], +            ["image-rendering", "-webkit-optimize-contrast"], +            ["image-rendering", "pixelated"], +            ["image-rendering", "crisp-edges"] +        ] +    }, +    { +        "selectors": [ +            ".gloss-image-link[data-image-rendering=crisp-edges] .gloss-image", +            ".gloss-image-link[data-image-rendering=crisp-edges] .gloss-image-background" +        ], +        "styles": [ +            ["image-rendering", "auto"], +            ["image-rendering", "-moz-crisp-edges"], +            ["image-rendering", "-webkit-optimize-contrast"], +            ["image-rendering", "crisp-edges"] +        ] +    }, +    { +        "selectors": [".gloss-image-link[data-has-aspect-ratio=true] .gloss-image-aspect-ratio-sizer"], +        "styles": [ +            ["display", "inline-block"], +            ["width", "0"], +            ["vertical-align", "top"], +            ["font-size", "0"] +        ] +    }, +    { +        "selectors": [".gloss-image-link-text"], +        "styles": [ +            ["display", "none"], +            ["line-height", "initial"] +        ] +    }, +    { +        "selectors": [".gloss-image-link-text::before"], +        "styles": [ +            ["content", "'['"] +        ] +    }, +    { +        "selectors": [".gloss-image-link-text::after"], +        "styles": [ +            ["content", "']'"] +        ] +    }, +    { +        "selectors": [".gloss-image-description"], +        "styles": [ +            ["display", "block"], +            ["white-space", "pre-line"] +        ] +    }, +    { +        "selectors": [".gloss-image-link[data-appearance=monochrome] .gloss-image"], +        "styles": [ +            ["visibility", "hidden"] +        ] +    }, +    { +        "selectors": [".gloss-image-link:not([data-appearance=monochrome]) .gloss-image-background"], +        "styles": [ +            ["display", "none"] +        ] +    }, +    { +        "selectors": [".gloss-image-link[data-size-units=em] .gloss-image-container"], +        "styles": [ +            ["font-size", "1em"] +        ] +    }, +    { +        "selectors": [".gloss-image-link[data-vertical-align=baseline]"], +        "styles": [ +            ["vertical-align", "baseline"] +        ] +    }, +    { +        "selectors": [".gloss-image-link[data-vertical-align=sub]"], +        "styles": [ +            ["vertical-align", "sub"] +        ] +    }, +    { +        "selectors": [".gloss-image-link[data-vertical-align=super]"], +        "styles": [ +            ["vertical-align", "super"] +        ] +    }, +    { +        "selectors": [".gloss-image-link[data-vertical-align=text-top]"], +        "styles": [ +            ["vertical-align", "top"] +        ] +    }, +    { +        "selectors": [".gloss-image-link[data-vertical-align=text-bottom]"], +        "styles": [ +            ["vertical-align", "bottom"] +        ] +    }, +    { +        "selectors": [".gloss-image-link[data-vertical-align=middle]"], +        "styles": [ +            ["vertical-align", "middle"] +        ] +    }, +    { +        "selectors": [".gloss-image-link[data-vertical-align=top]"], +        "styles": [ +            ["vertical-align", "top"] +        ] +    }, +    { +        "selectors": [".gloss-image-link[data-vertical-align=bottom]"], +        "styles": [ +            ["vertical-align", "bottom"] +        ] +    }, +    { +        "selectors": [ +            ".gloss-image-link[data-collapsed=true]", +            ":root[data-glossary-layout-mode=compact] .gloss-image-link[data-collapsible=true]" +        ], +        "styles": [ +            ["vertical-align", "baseline"] +        ] +    }, +    { +        "selectors": [ +            ".gloss-image-link[data-collapsed=true] .gloss-image-container", +            ":root[data-glossary-layout-mode=compact] .gloss-image-link[data-collapsible=true] .gloss-image-container" +        ], +        "styles": [ +            ["display", "none"], +            ["position", "absolute"], +            ["left", "0"], +            ["top", "100%"], +            ["z-index", "1"] +        ] +    }, +    { +        "selectors": [ +            ".entry:nth-last-of-type(1):not(:nth-of-type(1)) .gloss-image-link[data-collapsed=true] .gloss-image-container", +            ":root[data-glossary-layout-mode=compact] .entry:nth-last-of-type(1):not(:nth-of-type(1)) .gloss-image-link[data-collapsible=true] .gloss-image-container" +        ], +        "styles": [ +            ["bottom", "100%"], +            ["top", "auto"] +        ] +    }, +    { +        "selectors": [ +            ".gloss-image-link[data-collapsed=true]:hover .gloss-image-container", +            ".gloss-image-link[data-collapsed=true]:focus .gloss-image-container", +            ":root[data-glossary-layout-mode=compact] .gloss-image-link[data-collapsible=true]:hover .gloss-image-container", +            ":root[data-glossary-layout-mode=compact] .gloss-image-link[data-collapsible=true]:focus .gloss-image-container" +        ], +        "styles": [ +            ["display", "block"] +        ] +    }, +    { +        "selectors": [ +            ".gloss-image-link[data-collapsed=true] .gloss-image-link-text", +            ":root[data-glossary-layout-mode=compact] .gloss-image-link[data-collapsible=true] .gloss-image-link-text" +        ], +        "styles": [ +            ["display", "inline"] +        ] +    }, +    { +        "selectors": [ +            ".gloss-image-link[data-collapsed=true]~.gloss-image-description", +            ":root[data-glossary-layout-mode=compact] .gloss-image-description" +        ], +        "styles": [ +            ["display", "inline"] +        ] +    }, +    { +        "selectors": [".gloss-sc-table-container"], +        "styles": [ +            ["display", "block"] +        ] +    }, +    { +        "selectors": [".gloss-sc-table"], +        "styles": [ +            ["table-layout", "auto"], +            ["border-collapse", "collapse"] +        ] +    }, +    { +        "selectors": [ +            ".gloss-sc-thead", +            ".gloss-sc-tfoot", +            ".gloss-sc-th" +        ], +        "styles": [ +            ["font-weight", "bold"] +        ] +    }, +    { +        "selectors": [ +            ".gloss-sc-th", +            ".gloss-sc-td" +        ], +        "styles": [ +            ["border-style", "solid"], +            ["padding", "0.25em"], +            ["vertical-align", "top"], +            ["border-width", "1px"], +            ["border-color", "currentColor"] +        ] +    } +]
\ No newline at end of file diff --git a/package-lock.json b/package-lock.json index bb1336e3..956b433e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1023,6 +1023,12 @@              "integrity": "sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg==",              "dev": true          }, +        "atob": { +            "version": "2.1.2", +            "resolved": "https://registry.npmjs.org/atob/-/atob-2.1.2.tgz", +            "integrity": "sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==", +            "dev": true +        },          "atomic-sleep": {              "version": "1.0.0",              "resolved": "https://registry.npmjs.org/atomic-sleep/-/atomic-sleep-1.0.0.tgz", @@ -2067,6 +2073,17 @@              "integrity": "sha512-v1plID3y9r/lPhviJ1wrXpLeyUIGAZ2SHNYTEapm7/8A9nLPoyvVp3RK/EPFqn5kEznyWgYZNsRtYYIWbuG8KA==",              "dev": true          }, +        "css": { +            "version": "3.0.0", +            "resolved": "https://registry.npmjs.org/css/-/css-3.0.0.tgz", +            "integrity": "sha512-DG9pFfwOrzc+hawpmqX/dHYHJG+Bsdb0klhyi1sDneOgGOXy9wQIC8hzyVp1e4NRYDBdxcylvywPkkXCHAzTyQ==", +            "dev": true, +            "requires": { +                "inherits": "^2.0.4", +                "source-map": "^0.6.1", +                "source-map-resolve": "^0.6.0" +            } +        },          "css-select": {              "version": "4.1.3",              "resolved": "https://registry.npmjs.org/css-select/-/css-select-4.1.3.tgz", @@ -2231,6 +2248,12 @@              "integrity": "sha512-KaL7+6Fw6i5A2XSnsbhm/6B+NuEA7TZ4vqxnd5tXz9sbKtrN9Srj8ab4vKVdK8YAqZO9P1kg45Y6YLoduPf+kw==",              "dev": true          }, +        "decode-uri-component": { +            "version": "0.2.0", +            "resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.0.tgz", +            "integrity": "sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU=", +            "dev": true +        },          "decompress-response": {              "version": "3.3.0",              "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-3.3.0.tgz", @@ -6606,6 +6629,16 @@              "integrity": "sha512-/3GptzWzu0+0MBQFrDKzw/DvvMTUORvgY6k6jd/VS6iCR4RDTKWH6v6WPwQoUO8667uQEf9Oe38DxAYWY5F/Ug==",              "dev": true          }, +        "source-map-resolve": { +            "version": "0.6.0", +            "resolved": "https://registry.npmjs.org/source-map-resolve/-/source-map-resolve-0.6.0.tgz", +            "integrity": "sha512-KXBr9d/fO/bWo97NXsPIAW1bFSBOuCnjbNTBMO7N59hsv5i9yzRDfcYwwt0l04+VqnKC+EwzvJZIP/qkuMgR/w==", +            "dev": true, +            "requires": { +                "atob": "^2.1.2", +                "decode-uri-component": "^0.2.0" +            } +        },          "source-map-support": {              "version": "0.5.19",              "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.19.tgz", diff --git a/package.json b/package.json index ddc2648a..872f7dbb 100644 --- a/package.json +++ b/package.json @@ -36,6 +36,7 @@      },      "devDependencies": {          "browserify": "^17.0.0", +        "css": "^3.0.0",          "eslint": "^7.29.0",          "eslint-plugin-header": "^3.1.1",          "eslint-plugin-no-unsanitized": "^3.1.5", diff --git a/test/test-structured-content-style.js b/test/test-structured-content-style.js new file mode 100644 index 00000000..ed8c24e9 --- /dev/null +++ b/test/test-structured-content-style.js @@ -0,0 +1,32 @@ +/* + * Copyright (C) 2021  Yomichan Authors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program.  If not, see <https://www.gnu.org/licenses/>. + */ + +const fs = require('fs'); +const assert = require('assert'); +const {testMain} = require('../dev/util'); +const {generateRulesJson, getOutputPath} = require('../dev/generate-structured-content-style'); + + +function main() { +    const outputPath = getOutputPath(); +    const actual = fs.readFileSync(outputPath, {encoding: 'utf8'}); +    const expected = generateRulesJson(); +    assert.deepStrictEqual(actual, expected); +} + + +if (require.main === module) { testMain(main); } |