├── .github ├── dependabot.yml └── workflows │ ├── ci.yml │ └── release.yml ├── .gitignore ├── Cargo.lock ├── Cargo.toml ├── LICENSE ├── README.md ├── assets ├── README.md ├── fonts │ ├── Hack-Bold.ttf │ ├── Hack-BoldItalic.ttf │ ├── Hack-Italic.ttf │ └── Hack-Regular.ttf ├── sync_from_bat.sh ├── syntaxes.bin ├── syntaxes │ └── .gitignore ├── themes.bin └── themes │ └── .gitignore ├── example ├── example.png ├── example.rs └── example.sh └── src ├── assets.rs ├── bin └── silicon │ ├── config.rs │ └── main.rs ├── blur.rs ├── directories.rs ├── error.rs ├── font.rs ├── formatter.rs ├── hb_wrapper.rs ├── lib.rs └── utils.rs /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: cargo 4 | directory: "/" 5 | schedule: 6 | interval: daily 7 | time: "21:00" 8 | open-pull-requests-limit: 10 9 | assignees: 10 | - Aloxaf 11 | ignore: 12 | - dependency-name: anyhow 13 | versions: 14 | - 1.0.38 15 | - 1.0.39 16 | - dependency-name: env_logger 17 | versions: 18 | - 0.8.3 19 | - dependency-name: image 20 | versions: 21 | - 0.23.13 22 | - dependency-name: log 23 | versions: 24 | - 0.4.14 25 | - dependency-name: tempfile 26 | versions: 27 | - 3.2.0 28 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | pull_request: 5 | push: 6 | branches: 7 | - master 8 | 9 | env: 10 | RUST_BACKTRACE: 1 11 | 12 | jobs: 13 | 14 | test-linux: 15 | name: x86_64-unknown-linux-gnu 16 | runs-on: ubuntu-latest 17 | steps: 18 | - name: Checkout 19 | uses: actions/checkout@v1 20 | with: 21 | fetch-depth: 1 22 | 23 | - name: Install rust 24 | uses: actions-rs/toolchain@v1 25 | with: 26 | profile: minimal 27 | toolchain: stable 28 | components: rustfmt, clippy 29 | override: true 30 | 31 | - name: APT update 32 | run: sudo apt-get update 33 | 34 | - name: Install dependencies 35 | run: sudo apt-get install libx11-xcb-dev libxcb-shape0-dev libxcb-xfixes0-dev libharfbuzz-dev 36 | 37 | - name: Cargo fmt 38 | uses: actions-rs/cargo@v1 39 | with: 40 | command: fmt 41 | args: -- --check 42 | 43 | - name: Cargo clippy 44 | uses: actions-rs/clippy-check@v1 45 | with: 46 | token: ${{ secrets.GITHUB_TOKEN }} 47 | args: --all-features 48 | 49 | - name: Cargo check 50 | uses: actions-rs/cargo@v1 51 | with: 52 | command: check 53 | 54 | - name: Cargo test 55 | uses: actions-rs/cargo@v1 56 | with: 57 | command: test 58 | args: -- --nocapture 59 | 60 | test-windows: 61 | name: x86_64-pc-windows-msvc 62 | runs-on: windows-latest 63 | steps: 64 | - name: Checkout 65 | uses: actions/checkout@v1 66 | with: 67 | fetch-depth: 1 68 | 69 | - name: Install rust 70 | uses: actions-rs/toolchain@v1 71 | with: 72 | profile: minimal 73 | toolchain: stable 74 | override: true 75 | 76 | - name: Cargo check 77 | uses: actions-rs/cargo@v1 78 | with: 79 | command: check 80 | args: --no-default-features --features=bin 81 | 82 | - name: Cargo test 83 | uses: actions-rs/cargo@v1 84 | with: 85 | command: test 86 | args: --no-default-features --features=bin -- --nocapture 87 | 88 | test-macos: 89 | name: x86_64-apple-darwin 90 | runs-on: macos-latest 91 | steps: 92 | - name: Checkout 93 | uses: actions/checkout@v1 94 | with: 95 | fetch-depth: 1 96 | 97 | - name: Install rust 98 | uses: actions-rs/toolchain@v1 99 | with: 100 | profile: minimal 101 | toolchain: stable 102 | override: true 103 | 104 | - name: Cargo check 105 | uses: actions-rs/cargo@v1 106 | with: 107 | command: check 108 | 109 | - name: Cargo test 110 | uses: actions-rs/cargo@v1 111 | with: 112 | command: test 113 | args: -- --nocapture 114 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | on: 2 | push: 3 | tags: 4 | - 'v*' 5 | 6 | name: Release 7 | 8 | jobs: 9 | create_release: 10 | runs-on: ubuntu-latest 11 | outputs: 12 | upload_url: ${{ steps.create_release.outputs.upload_url }} 13 | steps: 14 | - id: create_release 15 | uses: actions/create-release@v1 16 | env: 17 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 18 | with: 19 | tag_name: ${{ github.ref }} 20 | release_name: ${{ github.ref }} 21 | draft: false 22 | prerelease: false 23 | 24 | release-linux: 25 | name: x86_64-unknown-linux-gnu 26 | needs: create_release 27 | runs-on: ubuntu-latest 28 | steps: 29 | - run: | 30 | sudo apt-get update 31 | sudo apt-get install libx11-xcb-dev libxcb-shape0-dev libxcb-xfixes0-dev libharfbuzz-dev 32 | - uses: actions/checkout@v2 33 | with: 34 | fetch-depth: 1 35 | - uses: actions-rs/toolchain@v1 36 | with: 37 | toolchain: stable 38 | override: true 39 | - uses: actions-rs/cargo@v1 40 | with: 41 | command: build 42 | args: --release 43 | - id: get_name 44 | run: | 45 | echo ::set-output name=NAME::silicon-${GITHUB_REF/refs\/tags\//}-x86_64-unknown-linux-gnu.tar.gz 46 | - run: | 47 | tar czf ${{ steps.get_name.outputs.NAME }} -C ./target/release silicon 48 | - uses: actions/upload-release-asset@v1 49 | env: 50 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 51 | with: 52 | upload_url: ${{ needs.create_release.outputs.upload_url }} 53 | asset_path: ./${{ steps.get_name.outputs.NAME }} 54 | asset_name: ${{ steps.get_name.outputs.NAME }} 55 | asset_content_type: application/zip 56 | 57 | release-macos: 58 | name: x86_64-apple-darwin 59 | needs: create_release 60 | runs-on: macos-latest 61 | steps: 62 | - uses: actions/checkout@v2 63 | - uses: actions-rs/toolchain@v1 64 | with: 65 | toolchain: stable 66 | override: true 67 | - uses: actions-rs/cargo@v1 68 | with: 69 | command: build 70 | args: --release 71 | - id: get_name 72 | run: | 73 | echo ::set-output name=NAME::silicon-${GITHUB_REF/refs\/tags\//}-x86_64-apple-darwin.tar.gz 74 | - run: | 75 | tar czf ${{ steps.get_name.outputs.NAME }} -C ./target/release silicon 76 | - uses: actions/upload-release-asset@v1 77 | env: 78 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 79 | with: 80 | upload_url: ${{ needs.create_release.outputs.upload_url }} 81 | asset_path: ./${{ steps.get_name.outputs.NAME }} 82 | asset_name: ${{ steps.get_name.outputs.NAME }} 83 | asset_content_type: application/zip 84 | 85 | release-windows: 86 | name: x86_64-pc-windows-msvc 87 | needs: create_release 88 | runs-on: windows-latest 89 | steps: 90 | - uses: actions/checkout@v2 91 | - uses: actions-rs/toolchain@v1 92 | with: 93 | toolchain: stable 94 | override: true 95 | - uses: actions-rs/cargo@v1 96 | with: 97 | command: build 98 | args: --release --no-default-features --features=bin 99 | - id: get_name 100 | shell: bash 101 | run: | 102 | echo ::set-output name=NAME::silicon-${GITHUB_REF/refs\/tags\//}-x86_64-pc-windows-msvc.tar.gz 103 | echo ::set-output name=VERSION::${GITHUB_REF/refs\/tags\//} 104 | - shell: bash 105 | run: | 106 | shopt -s extglob 107 | tar czf ${{ steps.get_name.outputs.NAME }} -C ./target/release silicon.exe 108 | - uses: actions/upload-release-asset@v1 109 | env: 110 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 111 | with: 112 | upload_url: ${{ needs.create_release.outputs.upload_url }} 113 | asset_path: ./${{ steps.get_name.outputs.NAME }} 114 | asset_name: ${{ steps.get_name.outputs.NAME }} 115 | asset_content_type: application/zip 116 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | **/*.rs.bk 3 | /.idea 4 | .vscode 5 | hello.png 6 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "ab_glyph_rasterizer" 7 | version = "0.1.8" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "c71b1793ee61086797f5c80b6efa2b8ffa6d5dd703f118545808a7f2e27f7046" 10 | 11 | [[package]] 12 | name = "adler" 13 | version = "1.0.2" 14 | source = "registry+https://github.com/rust-lang/crates.io-index" 15 | checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" 16 | 17 | [[package]] 18 | name = "adler2" 19 | version = "2.0.0" 20 | source = "registry+https://github.com/rust-lang/crates.io-index" 21 | checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627" 22 | 23 | [[package]] 24 | name = "ansi_term" 25 | version = "0.12.1" 26 | source = "registry+https://github.com/rust-lang/crates.io-index" 27 | checksum = "d52a9bb7ec0cf484c551830a7ce27bd20d67eac647e1befb56b0be4ee39a55d2" 28 | dependencies = [ 29 | "winapi", 30 | ] 31 | 32 | [[package]] 33 | name = "anstream" 34 | version = "0.6.15" 35 | source = "registry+https://github.com/rust-lang/crates.io-index" 36 | checksum = "64e15c1ab1f89faffbf04a634d5e1962e9074f2741eef6d97f3c4e322426d526" 37 | dependencies = [ 38 | "anstyle", 39 | "anstyle-parse", 40 | "anstyle-query", 41 | "anstyle-wincon", 42 | "colorchoice", 43 | "is_terminal_polyfill", 44 | "utf8parse", 45 | ] 46 | 47 | [[package]] 48 | name = "anstyle" 49 | version = "1.0.8" 50 | source = "registry+https://github.com/rust-lang/crates.io-index" 51 | checksum = "1bec1de6f59aedf83baf9ff929c98f2ad654b97c9510f4e70cf6f661d49fd5b1" 52 | 53 | [[package]] 54 | name = "anstyle-parse" 55 | version = "0.2.5" 56 | source = "registry+https://github.com/rust-lang/crates.io-index" 57 | checksum = "eb47de1e80c2b463c735db5b217a0ddc39d612e7ac9e2e96a5aed1f57616c1cb" 58 | dependencies = [ 59 | "utf8parse", 60 | ] 61 | 62 | [[package]] 63 | name = "anstyle-query" 64 | version = "1.1.1" 65 | source = "registry+https://github.com/rust-lang/crates.io-index" 66 | checksum = "6d36fc52c7f6c869915e99412912f22093507da8d9e942ceaf66fe4b7c14422a" 67 | dependencies = [ 68 | "windows-sys 0.52.0", 69 | ] 70 | 71 | [[package]] 72 | name = "anstyle-wincon" 73 | version = "3.0.4" 74 | source = "registry+https://github.com/rust-lang/crates.io-index" 75 | checksum = "5bf74e1b6e971609db8ca7a9ce79fd5768ab6ae46441c572e46cf596f59e57f8" 76 | dependencies = [ 77 | "anstyle", 78 | "windows-sys 0.52.0", 79 | ] 80 | 81 | [[package]] 82 | name = "anyhow" 83 | version = "1.0.89" 84 | source = "registry+https://github.com/rust-lang/crates.io-index" 85 | checksum = "86fdf8605db99b54d3cd748a44c6d04df638eb5dafb219b135d0149bd0db01f6" 86 | 87 | [[package]] 88 | name = "approx" 89 | version = "0.5.1" 90 | source = "registry+https://github.com/rust-lang/crates.io-index" 91 | checksum = "cab112f0a86d568ea0e627cc1d6be74a1e9cd55214684db5561995f6dad897c6" 92 | dependencies = [ 93 | "num-traits", 94 | ] 95 | 96 | [[package]] 97 | name = "atty" 98 | version = "0.2.14" 99 | source = "registry+https://github.com/rust-lang/crates.io-index" 100 | checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" 101 | dependencies = [ 102 | "hermit-abi", 103 | "libc", 104 | "winapi", 105 | ] 106 | 107 | [[package]] 108 | name = "autocfg" 109 | version = "1.3.0" 110 | source = "registry+https://github.com/rust-lang/crates.io-index" 111 | checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0" 112 | 113 | [[package]] 114 | name = "base64" 115 | version = "0.22.1" 116 | source = "registry+https://github.com/rust-lang/crates.io-index" 117 | checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" 118 | 119 | [[package]] 120 | name = "bincode" 121 | version = "1.3.3" 122 | source = "registry+https://github.com/rust-lang/crates.io-index" 123 | checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad" 124 | dependencies = [ 125 | "serde", 126 | ] 127 | 128 | [[package]] 129 | name = "bitflags" 130 | version = "1.3.2" 131 | source = "registry+https://github.com/rust-lang/crates.io-index" 132 | checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" 133 | 134 | [[package]] 135 | name = "bitflags" 136 | version = "2.6.0" 137 | source = "registry+https://github.com/rust-lang/crates.io-index" 138 | checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" 139 | 140 | [[package]] 141 | name = "block" 142 | version = "0.1.6" 143 | source = "registry+https://github.com/rust-lang/crates.io-index" 144 | checksum = "0d8c1fef690941d3e7788d328517591fecc684c084084702d6ff1641e993699a" 145 | 146 | [[package]] 147 | name = "bytemuck" 148 | version = "1.18.0" 149 | source = "registry+https://github.com/rust-lang/crates.io-index" 150 | checksum = "94bbb0ad554ad961ddc5da507a12a29b14e4ae5bda06b19f575a3e6079d2e2ae" 151 | 152 | [[package]] 153 | name = "byteorder" 154 | version = "1.5.0" 155 | source = "registry+https://github.com/rust-lang/crates.io-index" 156 | checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" 157 | 158 | [[package]] 159 | name = "cc" 160 | version = "1.1.21" 161 | source = "registry+https://github.com/rust-lang/crates.io-index" 162 | checksum = "07b1695e2c7e8fc85310cde85aeaab7e3097f593c91d209d3f9df76c928100f0" 163 | dependencies = [ 164 | "shlex", 165 | ] 166 | 167 | [[package]] 168 | name = "cfg-if" 169 | version = "1.0.0" 170 | source = "registry+https://github.com/rust-lang/crates.io-index" 171 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 172 | 173 | [[package]] 174 | name = "clap" 175 | version = "2.34.0" 176 | source = "registry+https://github.com/rust-lang/crates.io-index" 177 | checksum = "a0610544180c38b88101fecf2dd634b174a62eef6946f84dfc6a7127512b381c" 178 | dependencies = [ 179 | "ansi_term", 180 | "atty", 181 | "bitflags 1.3.2", 182 | "strsim", 183 | "term_size", 184 | "textwrap", 185 | "unicode-width", 186 | "vec_map", 187 | ] 188 | 189 | [[package]] 190 | name = "clipboard" 191 | version = "0.5.0" 192 | source = "registry+https://github.com/rust-lang/crates.io-index" 193 | checksum = "25a904646c0340239dcf7c51677b33928bf24fdf424b79a57909c0109075b2e7" 194 | dependencies = [ 195 | "clipboard-win 2.2.0", 196 | "objc", 197 | "objc-foundation", 198 | "objc_id", 199 | "x11-clipboard", 200 | ] 201 | 202 | [[package]] 203 | name = "clipboard-win" 204 | version = "2.2.0" 205 | source = "registry+https://github.com/rust-lang/crates.io-index" 206 | checksum = "e3a093d6fed558e5fe24c3dfc85a68bb68f1c824f440d3ba5aca189e2998786b" 207 | dependencies = [ 208 | "winapi", 209 | ] 210 | 211 | [[package]] 212 | name = "clipboard-win" 213 | version = "5.4.0" 214 | source = "registry+https://github.com/rust-lang/crates.io-index" 215 | checksum = "15efe7a882b08f34e38556b14f2fb3daa98769d06c7f0c1b076dfd0d983bc892" 216 | dependencies = [ 217 | "error-code", 218 | ] 219 | 220 | [[package]] 221 | name = "cocoa" 222 | version = "0.24.1" 223 | source = "registry+https://github.com/rust-lang/crates.io-index" 224 | checksum = "f425db7937052c684daec3bd6375c8abe2d146dca4b8b143d6db777c39138f3a" 225 | dependencies = [ 226 | "bitflags 1.3.2", 227 | "block", 228 | "cocoa-foundation", 229 | "core-foundation", 230 | "core-graphics", 231 | "foreign-types", 232 | "libc", 233 | "objc", 234 | ] 235 | 236 | [[package]] 237 | name = "cocoa-foundation" 238 | version = "0.1.2" 239 | source = "registry+https://github.com/rust-lang/crates.io-index" 240 | checksum = "8c6234cbb2e4c785b456c0644748b1ac416dd045799740356f8363dfe00c93f7" 241 | dependencies = [ 242 | "bitflags 1.3.2", 243 | "block", 244 | "core-foundation", 245 | "core-graphics-types", 246 | "libc", 247 | "objc", 248 | ] 249 | 250 | [[package]] 251 | name = "color_quant" 252 | version = "1.1.0" 253 | source = "registry+https://github.com/rust-lang/crates.io-index" 254 | checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b" 255 | 256 | [[package]] 257 | name = "colorchoice" 258 | version = "1.0.2" 259 | source = "registry+https://github.com/rust-lang/crates.io-index" 260 | checksum = "d3fd119d74b830634cea2a0f58bbd0d54540518a14397557951e79340abc28c0" 261 | 262 | [[package]] 263 | name = "conv" 264 | version = "0.3.3" 265 | source = "registry+https://github.com/rust-lang/crates.io-index" 266 | checksum = "78ff10625fd0ac447827aa30ea8b861fead473bb60aeb73af6c1c58caf0d1299" 267 | dependencies = [ 268 | "custom_derive", 269 | ] 270 | 271 | [[package]] 272 | name = "core-foundation" 273 | version = "0.9.4" 274 | source = "registry+https://github.com/rust-lang/crates.io-index" 275 | checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" 276 | dependencies = [ 277 | "core-foundation-sys", 278 | "libc", 279 | ] 280 | 281 | [[package]] 282 | name = "core-foundation-sys" 283 | version = "0.8.7" 284 | source = "registry+https://github.com/rust-lang/crates.io-index" 285 | checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" 286 | 287 | [[package]] 288 | name = "core-graphics" 289 | version = "0.22.3" 290 | source = "registry+https://github.com/rust-lang/crates.io-index" 291 | checksum = "2581bbab3b8ffc6fcbd550bf46c355135d16e9ff2a6ea032ad6b9bf1d7efe4fb" 292 | dependencies = [ 293 | "bitflags 1.3.2", 294 | "core-foundation", 295 | "core-graphics-types", 296 | "foreign-types", 297 | "libc", 298 | ] 299 | 300 | [[package]] 301 | name = "core-graphics-types" 302 | version = "0.1.3" 303 | source = "registry+https://github.com/rust-lang/crates.io-index" 304 | checksum = "45390e6114f68f718cc7a830514a96f903cccd70d02a8f6d9f643ac4ba45afaf" 305 | dependencies = [ 306 | "bitflags 1.3.2", 307 | "core-foundation", 308 | "libc", 309 | ] 310 | 311 | [[package]] 312 | name = "core-text" 313 | version = "19.2.0" 314 | source = "registry+https://github.com/rust-lang/crates.io-index" 315 | checksum = "99d74ada66e07c1cefa18f8abfba765b486f250de2e4a999e5727fc0dd4b4a25" 316 | dependencies = [ 317 | "core-foundation", 318 | "core-graphics", 319 | "foreign-types", 320 | "libc", 321 | ] 322 | 323 | [[package]] 324 | name = "crc32fast" 325 | version = "1.4.2" 326 | source = "registry+https://github.com/rust-lang/crates.io-index" 327 | checksum = "a97769d94ddab943e4510d138150169a2758b5ef3eb191a9ee688de3e23ef7b3" 328 | dependencies = [ 329 | "cfg-if", 330 | ] 331 | 332 | [[package]] 333 | name = "crossbeam-deque" 334 | version = "0.8.5" 335 | source = "registry+https://github.com/rust-lang/crates.io-index" 336 | checksum = "613f8cc01fe9cf1a3eb3d7f488fd2fa8388403e97039e2f73692932e291a770d" 337 | dependencies = [ 338 | "crossbeam-epoch", 339 | "crossbeam-utils", 340 | ] 341 | 342 | [[package]] 343 | name = "crossbeam-epoch" 344 | version = "0.9.18" 345 | source = "registry+https://github.com/rust-lang/crates.io-index" 346 | checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" 347 | dependencies = [ 348 | "crossbeam-utils", 349 | ] 350 | 351 | [[package]] 352 | name = "crossbeam-utils" 353 | version = "0.8.20" 354 | source = "registry+https://github.com/rust-lang/crates.io-index" 355 | checksum = "22ec99545bb0ed0ea7bb9b8e1e9122ea386ff8a48c0922e43f36d45ab09e0e80" 356 | 357 | [[package]] 358 | name = "cstr" 359 | version = "0.2.12" 360 | source = "registry+https://github.com/rust-lang/crates.io-index" 361 | checksum = "68523903c8ae5aacfa32a0d9ae60cadeb764e1da14ee0d26b1f3089f13a54636" 362 | dependencies = [ 363 | "proc-macro2", 364 | "quote", 365 | ] 366 | 367 | [[package]] 368 | name = "custom_derive" 369 | version = "0.1.7" 370 | source = "registry+https://github.com/rust-lang/crates.io-index" 371 | checksum = "ef8ae57c4978a2acd8b869ce6b9ca1dfe817bff704c220209fdef2c0b75a01b9" 372 | 373 | [[package]] 374 | name = "deranged" 375 | version = "0.3.11" 376 | source = "registry+https://github.com/rust-lang/crates.io-index" 377 | checksum = "b42b6fa04a440b495c8b04d0e71b707c585f83cb9cb28cf8cd0d976c315e31b4" 378 | dependencies = [ 379 | "powerfmt", 380 | ] 381 | 382 | [[package]] 383 | name = "dirs" 384 | version = "5.0.1" 385 | source = "registry+https://github.com/rust-lang/crates.io-index" 386 | checksum = "44c45a9d03d6676652bcb5e724c7e988de1acad23a711b5217ab9cbecbec2225" 387 | dependencies = [ 388 | "dirs-sys", 389 | ] 390 | 391 | [[package]] 392 | name = "dirs-next" 393 | version = "2.0.0" 394 | source = "registry+https://github.com/rust-lang/crates.io-index" 395 | checksum = "b98cf8ebf19c3d1b223e151f99a4f9f0690dca41414773390fc824184ac833e1" 396 | dependencies = [ 397 | "cfg-if", 398 | "dirs-sys-next", 399 | ] 400 | 401 | [[package]] 402 | name = "dirs-sys" 403 | version = "0.4.1" 404 | source = "registry+https://github.com/rust-lang/crates.io-index" 405 | checksum = "520f05a5cbd335fae5a99ff7a6ab8627577660ee5cfd6a94a6a929b52ff0321c" 406 | dependencies = [ 407 | "libc", 408 | "option-ext", 409 | "redox_users", 410 | "windows-sys 0.48.0", 411 | ] 412 | 413 | [[package]] 414 | name = "dirs-sys-next" 415 | version = "0.1.2" 416 | source = "registry+https://github.com/rust-lang/crates.io-index" 417 | checksum = "4ebda144c4fe02d1f7ea1a7d9641b6fc6b580adcfa024ae48797ecdeb6825b4d" 418 | dependencies = [ 419 | "libc", 420 | "redox_users", 421 | "winapi", 422 | ] 423 | 424 | [[package]] 425 | name = "dlib" 426 | version = "0.5.2" 427 | source = "registry+https://github.com/rust-lang/crates.io-index" 428 | checksum = "330c60081dcc4c72131f8eb70510f1ac07223e5d4163db481a04a0befcffa412" 429 | dependencies = [ 430 | "libloading", 431 | ] 432 | 433 | [[package]] 434 | name = "dwrote" 435 | version = "0.11.1" 436 | source = "registry+https://github.com/rust-lang/crates.io-index" 437 | checksum = "2da3498378ed373237bdef1eddcc64e7be2d3ba4841f4c22a998e81cadeea83c" 438 | dependencies = [ 439 | "lazy_static", 440 | "libc", 441 | "winapi", 442 | "wio", 443 | ] 444 | 445 | [[package]] 446 | name = "either" 447 | version = "1.13.0" 448 | source = "registry+https://github.com/rust-lang/crates.io-index" 449 | checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" 450 | 451 | [[package]] 452 | name = "env_filter" 453 | version = "0.1.2" 454 | source = "registry+https://github.com/rust-lang/crates.io-index" 455 | checksum = "4f2c92ceda6ceec50f43169f9ee8424fe2db276791afde7b2cd8bc084cb376ab" 456 | dependencies = [ 457 | "log", 458 | ] 459 | 460 | [[package]] 461 | name = "env_logger" 462 | version = "0.11.5" 463 | source = "registry+https://github.com/rust-lang/crates.io-index" 464 | checksum = "e13fa619b91fb2381732789fc5de83b45675e882f66623b7d8cb4f643017018d" 465 | dependencies = [ 466 | "anstream", 467 | "anstyle", 468 | "env_filter", 469 | "humantime", 470 | "log", 471 | ] 472 | 473 | [[package]] 474 | name = "equivalent" 475 | version = "1.0.1" 476 | source = "registry+https://github.com/rust-lang/crates.io-index" 477 | checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" 478 | 479 | [[package]] 480 | name = "errno" 481 | version = "0.3.9" 482 | source = "registry+https://github.com/rust-lang/crates.io-index" 483 | checksum = "534c5cf6194dfab3db3242765c03bbe257cf92f22b38f6bc0c58d59108a820ba" 484 | dependencies = [ 485 | "libc", 486 | "windows-sys 0.52.0", 487 | ] 488 | 489 | [[package]] 490 | name = "error-code" 491 | version = "3.3.1" 492 | source = "registry+https://github.com/rust-lang/crates.io-index" 493 | checksum = "a5d9305ccc6942a704f4335694ecd3de2ea531b114ac2d51f5f843750787a92f" 494 | 495 | [[package]] 496 | name = "fastrand" 497 | version = "2.1.1" 498 | source = "registry+https://github.com/rust-lang/crates.io-index" 499 | checksum = "e8c02a5121d4ea3eb16a80748c74f5549a5665e4c21333c6098f283870fbdea6" 500 | 501 | [[package]] 502 | name = "fdeflate" 503 | version = "0.3.5" 504 | source = "registry+https://github.com/rust-lang/crates.io-index" 505 | checksum = "d8090f921a24b04994d9929e204f50b498a33ea6ba559ffaa05e04f7ee7fb5ab" 506 | dependencies = [ 507 | "simd-adler32", 508 | ] 509 | 510 | [[package]] 511 | name = "flate2" 512 | version = "1.0.33" 513 | source = "registry+https://github.com/rust-lang/crates.io-index" 514 | checksum = "324a1be68054ef05ad64b861cc9eaf1d623d2d8cb25b4bf2cb9cdd902b4bf253" 515 | dependencies = [ 516 | "crc32fast", 517 | "miniz_oxide 0.8.0", 518 | ] 519 | 520 | [[package]] 521 | name = "float-ord" 522 | version = "0.3.2" 523 | source = "registry+https://github.com/rust-lang/crates.io-index" 524 | checksum = "8ce81f49ae8a0482e4c55ea62ebbd7e5a686af544c00b9d090bba3ff9be97b3d" 525 | 526 | [[package]] 527 | name = "fnv" 528 | version = "1.0.7" 529 | source = "registry+https://github.com/rust-lang/crates.io-index" 530 | checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" 531 | 532 | [[package]] 533 | name = "font-kit" 534 | version = "0.12.0" 535 | source = "registry+https://github.com/rust-lang/crates.io-index" 536 | checksum = "b0504fc23a34d36352540ae5eedcec2623c86607a4efe25494ca9641845c5a50" 537 | dependencies = [ 538 | "bitflags 2.6.0", 539 | "byteorder", 540 | "core-foundation", 541 | "core-graphics", 542 | "core-text", 543 | "dirs-next", 544 | "dwrote", 545 | "float-ord", 546 | "freetype", 547 | "lazy_static", 548 | "libc", 549 | "log", 550 | "pathfinder_geometry", 551 | "pathfinder_simd", 552 | "walkdir", 553 | "winapi", 554 | "yeslogic-fontconfig-sys", 555 | ] 556 | 557 | [[package]] 558 | name = "foreign-types" 559 | version = "0.3.2" 560 | source = "registry+https://github.com/rust-lang/crates.io-index" 561 | checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" 562 | dependencies = [ 563 | "foreign-types-shared", 564 | ] 565 | 566 | [[package]] 567 | name = "foreign-types-shared" 568 | version = "0.1.1" 569 | source = "registry+https://github.com/rust-lang/crates.io-index" 570 | checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" 571 | 572 | [[package]] 573 | name = "freetype" 574 | version = "0.7.2" 575 | source = "registry+https://github.com/rust-lang/crates.io-index" 576 | checksum = "5a440748e063798e4893ceb877151e84acef9bea9a8c6800645cf3f1b3a7806e" 577 | dependencies = [ 578 | "freetype-sys", 579 | "libc", 580 | ] 581 | 582 | [[package]] 583 | name = "freetype-sys" 584 | version = "0.20.1" 585 | source = "registry+https://github.com/rust-lang/crates.io-index" 586 | checksum = "0e7edc5b9669349acfda99533e9e0bcf26a51862ab43b08ee7745c55d28eb134" 587 | dependencies = [ 588 | "cc", 589 | "libc", 590 | "pkg-config", 591 | ] 592 | 593 | [[package]] 594 | name = "getrandom" 595 | version = "0.1.16" 596 | source = "registry+https://github.com/rust-lang/crates.io-index" 597 | checksum = "8fc3cb4d91f53b50155bdcfd23f6a4c39ae1969c2ae85982b135750cccaf5fce" 598 | dependencies = [ 599 | "cfg-if", 600 | "libc", 601 | "wasi 0.9.0+wasi-snapshot-preview1", 602 | ] 603 | 604 | [[package]] 605 | name = "getrandom" 606 | version = "0.2.15" 607 | source = "registry+https://github.com/rust-lang/crates.io-index" 608 | checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" 609 | dependencies = [ 610 | "cfg-if", 611 | "libc", 612 | "wasi 0.11.0+wasi-snapshot-preview1", 613 | ] 614 | 615 | [[package]] 616 | name = "harfbuzz-sys" 617 | version = "0.5.0" 618 | source = "registry+https://github.com/rust-lang/crates.io-index" 619 | checksum = "bf8c27ca13930dc4ffe474880040fe9e0f03c2121600dc9c95423624cab3e467" 620 | dependencies = [ 621 | "cc", 622 | "core-graphics", 623 | "core-text", 624 | "foreign-types", 625 | "freetype", 626 | "pkg-config", 627 | ] 628 | 629 | [[package]] 630 | name = "hashbrown" 631 | version = "0.14.5" 632 | source = "registry+https://github.com/rust-lang/crates.io-index" 633 | checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" 634 | 635 | [[package]] 636 | name = "heck" 637 | version = "0.3.3" 638 | source = "registry+https://github.com/rust-lang/crates.io-index" 639 | checksum = "6d621efb26863f0e9924c6ac577e8275e5e6b77455db64ffa6c65c904e9e132c" 640 | dependencies = [ 641 | "unicode-segmentation", 642 | ] 643 | 644 | [[package]] 645 | name = "hermit-abi" 646 | version = "0.1.19" 647 | source = "registry+https://github.com/rust-lang/crates.io-index" 648 | checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" 649 | dependencies = [ 650 | "libc", 651 | ] 652 | 653 | [[package]] 654 | name = "humantime" 655 | version = "2.1.0" 656 | source = "registry+https://github.com/rust-lang/crates.io-index" 657 | checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" 658 | 659 | [[package]] 660 | name = "image" 661 | version = "0.24.9" 662 | source = "registry+https://github.com/rust-lang/crates.io-index" 663 | checksum = "5690139d2f55868e080017335e4b94cb7414274c74f1669c84fb5feba2c9f69d" 664 | dependencies = [ 665 | "bytemuck", 666 | "byteorder", 667 | "color_quant", 668 | "jpeg-decoder", 669 | "num-traits", 670 | "png", 671 | ] 672 | 673 | [[package]] 674 | name = "imageproc" 675 | version = "0.23.0" 676 | source = "registry+https://github.com/rust-lang/crates.io-index" 677 | checksum = "b6aee993351d466301a29655d628bfc6f5a35a0d062b6160ca0808f425805fd7" 678 | dependencies = [ 679 | "approx", 680 | "conv", 681 | "image", 682 | "itertools", 683 | "nalgebra", 684 | "num", 685 | "rand", 686 | "rand_distr", 687 | "rayon", 688 | "rusttype", 689 | ] 690 | 691 | [[package]] 692 | name = "indexmap" 693 | version = "2.5.0" 694 | source = "registry+https://github.com/rust-lang/crates.io-index" 695 | checksum = "68b900aa2f7301e21c36462b170ee99994de34dff39a4a6a528e80e7376d07e5" 696 | dependencies = [ 697 | "equivalent", 698 | "hashbrown", 699 | ] 700 | 701 | [[package]] 702 | name = "is_terminal_polyfill" 703 | version = "1.70.1" 704 | source = "registry+https://github.com/rust-lang/crates.io-index" 705 | checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" 706 | 707 | [[package]] 708 | name = "itertools" 709 | version = "0.10.5" 710 | source = "registry+https://github.com/rust-lang/crates.io-index" 711 | checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" 712 | dependencies = [ 713 | "either", 714 | ] 715 | 716 | [[package]] 717 | name = "itoa" 718 | version = "1.0.11" 719 | source = "registry+https://github.com/rust-lang/crates.io-index" 720 | checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" 721 | 722 | [[package]] 723 | name = "jpeg-decoder" 724 | version = "0.3.1" 725 | source = "registry+https://github.com/rust-lang/crates.io-index" 726 | checksum = "f5d4a7da358eff58addd2877a45865158f0d78c911d43a5784ceb7bbf52833b0" 727 | dependencies = [ 728 | "rayon", 729 | ] 730 | 731 | [[package]] 732 | name = "lazy_static" 733 | version = "1.5.0" 734 | source = "registry+https://github.com/rust-lang/crates.io-index" 735 | checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" 736 | 737 | [[package]] 738 | name = "libc" 739 | version = "0.2.159" 740 | source = "registry+https://github.com/rust-lang/crates.io-index" 741 | checksum = "561d97a539a36e26a9a5fad1ea11a3039a67714694aaa379433e580854bc3dc5" 742 | 743 | [[package]] 744 | name = "libloading" 745 | version = "0.8.5" 746 | source = "registry+https://github.com/rust-lang/crates.io-index" 747 | checksum = "4979f22fdb869068da03c9f7528f8297c6fd2606bc3a4affe42e6a823fdb8da4" 748 | dependencies = [ 749 | "cfg-if", 750 | "windows-targets 0.52.6", 751 | ] 752 | 753 | [[package]] 754 | name = "libredox" 755 | version = "0.1.3" 756 | source = "registry+https://github.com/rust-lang/crates.io-index" 757 | checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d" 758 | dependencies = [ 759 | "bitflags 2.6.0", 760 | "libc", 761 | ] 762 | 763 | [[package]] 764 | name = "linked-hash-map" 765 | version = "0.5.6" 766 | source = "registry+https://github.com/rust-lang/crates.io-index" 767 | checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f" 768 | 769 | [[package]] 770 | name = "linux-raw-sys" 771 | version = "0.4.14" 772 | source = "registry+https://github.com/rust-lang/crates.io-index" 773 | checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" 774 | 775 | [[package]] 776 | name = "log" 777 | version = "0.4.22" 778 | source = "registry+https://github.com/rust-lang/crates.io-index" 779 | checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" 780 | 781 | [[package]] 782 | name = "malloc_buf" 783 | version = "0.0.6" 784 | source = "registry+https://github.com/rust-lang/crates.io-index" 785 | checksum = "62bb907fe88d54d8d9ce32a3cceab4218ed2f6b7d35617cafe9adf84e43919cb" 786 | dependencies = [ 787 | "libc", 788 | ] 789 | 790 | [[package]] 791 | name = "matrixmultiply" 792 | version = "0.3.9" 793 | source = "registry+https://github.com/rust-lang/crates.io-index" 794 | checksum = "9380b911e3e96d10c1f415da0876389aaf1b56759054eeb0de7df940c456ba1a" 795 | dependencies = [ 796 | "autocfg", 797 | "rawpointer", 798 | ] 799 | 800 | [[package]] 801 | name = "memchr" 802 | version = "2.7.4" 803 | source = "registry+https://github.com/rust-lang/crates.io-index" 804 | checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" 805 | 806 | [[package]] 807 | name = "miniz_oxide" 808 | version = "0.7.4" 809 | source = "registry+https://github.com/rust-lang/crates.io-index" 810 | checksum = "b8a240ddb74feaf34a79a7add65a741f3167852fba007066dcac1ca548d89c08" 811 | dependencies = [ 812 | "adler", 813 | "simd-adler32", 814 | ] 815 | 816 | [[package]] 817 | name = "miniz_oxide" 818 | version = "0.8.0" 819 | source = "registry+https://github.com/rust-lang/crates.io-index" 820 | checksum = "e2d80299ef12ff69b16a84bb182e3b9df68b5a91574d3d4fa6e41b65deec4df1" 821 | dependencies = [ 822 | "adler2", 823 | ] 824 | 825 | [[package]] 826 | name = "nalgebra" 827 | version = "0.30.1" 828 | source = "registry+https://github.com/rust-lang/crates.io-index" 829 | checksum = "4fb2d0de08694bed883320212c18ee3008576bfe8c306f4c3c4a58b4876998be" 830 | dependencies = [ 831 | "approx", 832 | "matrixmultiply", 833 | "num-complex", 834 | "num-rational", 835 | "num-traits", 836 | "simba", 837 | "typenum", 838 | ] 839 | 840 | [[package]] 841 | name = "num" 842 | version = "0.4.3" 843 | source = "registry+https://github.com/rust-lang/crates.io-index" 844 | checksum = "35bd024e8b2ff75562e5f34e7f4905839deb4b22955ef5e73d2fea1b9813cb23" 845 | dependencies = [ 846 | "num-bigint", 847 | "num-complex", 848 | "num-integer", 849 | "num-iter", 850 | "num-rational", 851 | "num-traits", 852 | ] 853 | 854 | [[package]] 855 | name = "num-bigint" 856 | version = "0.4.6" 857 | source = "registry+https://github.com/rust-lang/crates.io-index" 858 | checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" 859 | dependencies = [ 860 | "num-integer", 861 | "num-traits", 862 | ] 863 | 864 | [[package]] 865 | name = "num-complex" 866 | version = "0.4.6" 867 | source = "registry+https://github.com/rust-lang/crates.io-index" 868 | checksum = "73f88a1307638156682bada9d7604135552957b7818057dcef22705b4d509495" 869 | dependencies = [ 870 | "num-traits", 871 | ] 872 | 873 | [[package]] 874 | name = "num-conv" 875 | version = "0.1.0" 876 | source = "registry+https://github.com/rust-lang/crates.io-index" 877 | checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" 878 | 879 | [[package]] 880 | name = "num-integer" 881 | version = "0.1.46" 882 | source = "registry+https://github.com/rust-lang/crates.io-index" 883 | checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" 884 | dependencies = [ 885 | "num-traits", 886 | ] 887 | 888 | [[package]] 889 | name = "num-iter" 890 | version = "0.1.45" 891 | source = "registry+https://github.com/rust-lang/crates.io-index" 892 | checksum = "1429034a0490724d0075ebb2bc9e875d6503c3cf69e235a8941aa757d83ef5bf" 893 | dependencies = [ 894 | "autocfg", 895 | "num-integer", 896 | "num-traits", 897 | ] 898 | 899 | [[package]] 900 | name = "num-rational" 901 | version = "0.4.2" 902 | source = "registry+https://github.com/rust-lang/crates.io-index" 903 | checksum = "f83d14da390562dca69fc84082e73e548e1ad308d24accdedd2720017cb37824" 904 | dependencies = [ 905 | "num-bigint", 906 | "num-integer", 907 | "num-traits", 908 | ] 909 | 910 | [[package]] 911 | name = "num-traits" 912 | version = "0.2.19" 913 | source = "registry+https://github.com/rust-lang/crates.io-index" 914 | checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" 915 | dependencies = [ 916 | "autocfg", 917 | ] 918 | 919 | [[package]] 920 | name = "objc" 921 | version = "0.2.7" 922 | source = "registry+https://github.com/rust-lang/crates.io-index" 923 | checksum = "915b1b472bc21c53464d6c8461c9d3af805ba1ef837e1cac254428f4a77177b1" 924 | dependencies = [ 925 | "malloc_buf", 926 | ] 927 | 928 | [[package]] 929 | name = "objc-foundation" 930 | version = "0.1.1" 931 | source = "registry+https://github.com/rust-lang/crates.io-index" 932 | checksum = "1add1b659e36c9607c7aab864a76c7a4c2760cd0cd2e120f3fb8b952c7e22bf9" 933 | dependencies = [ 934 | "block", 935 | "objc", 936 | "objc_id", 937 | ] 938 | 939 | [[package]] 940 | name = "objc_id" 941 | version = "0.1.1" 942 | source = "registry+https://github.com/rust-lang/crates.io-index" 943 | checksum = "c92d4ddb4bd7b50d730c215ff871754d0da6b2178849f8a2a2ab69712d0c073b" 944 | dependencies = [ 945 | "objc", 946 | ] 947 | 948 | [[package]] 949 | name = "once_cell" 950 | version = "1.19.0" 951 | source = "registry+https://github.com/rust-lang/crates.io-index" 952 | checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" 953 | 954 | [[package]] 955 | name = "onig" 956 | version = "6.4.0" 957 | source = "registry+https://github.com/rust-lang/crates.io-index" 958 | checksum = "8c4b31c8722ad9171c6d77d3557db078cab2bd50afcc9d09c8b315c59df8ca4f" 959 | dependencies = [ 960 | "bitflags 1.3.2", 961 | "libc", 962 | "once_cell", 963 | "onig_sys", 964 | ] 965 | 966 | [[package]] 967 | name = "onig_sys" 968 | version = "69.8.1" 969 | source = "registry+https://github.com/rust-lang/crates.io-index" 970 | checksum = "7b829e3d7e9cc74c7e315ee8edb185bf4190da5acde74afd7fc59c35b1f086e7" 971 | dependencies = [ 972 | "cc", 973 | "pkg-config", 974 | ] 975 | 976 | [[package]] 977 | name = "option-ext" 978 | version = "0.2.0" 979 | source = "registry+https://github.com/rust-lang/crates.io-index" 980 | checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" 981 | 982 | [[package]] 983 | name = "os_info" 984 | version = "3.8.2" 985 | source = "registry+https://github.com/rust-lang/crates.io-index" 986 | checksum = "ae99c7fa6dd38c7cafe1ec085e804f8f555a2f8659b0dbe03f1f9963a9b51092" 987 | dependencies = [ 988 | "log", 989 | "serde", 990 | "windows-sys 0.52.0", 991 | ] 992 | 993 | [[package]] 994 | name = "owned_ttf_parser" 995 | version = "0.15.2" 996 | source = "registry+https://github.com/rust-lang/crates.io-index" 997 | checksum = "05e6affeb1632d6ff6a23d2cd40ffed138e82f1532571a26f527c8a284bb2fbb" 998 | dependencies = [ 999 | "ttf-parser", 1000 | ] 1001 | 1002 | [[package]] 1003 | name = "paste" 1004 | version = "1.0.15" 1005 | source = "registry+https://github.com/rust-lang/crates.io-index" 1006 | checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" 1007 | 1008 | [[package]] 1009 | name = "pasteboard" 1010 | version = "0.1.3" 1011 | source = "registry+https://github.com/rust-lang/crates.io-index" 1012 | checksum = "639c0cdcf01a242098cf6a9e6f06a2661267dcf3c918d53b5a78c2dd40c05ce6" 1013 | dependencies = [ 1014 | "cocoa", 1015 | "objc", 1016 | "os_info", 1017 | "structopt", 1018 | ] 1019 | 1020 | [[package]] 1021 | name = "pathfinder_geometry" 1022 | version = "0.5.1" 1023 | source = "registry+https://github.com/rust-lang/crates.io-index" 1024 | checksum = "0b7b7e7b4ea703700ce73ebf128e1450eb69c3a8329199ffbfb9b2a0418e5ad3" 1025 | dependencies = [ 1026 | "log", 1027 | "pathfinder_simd", 1028 | ] 1029 | 1030 | [[package]] 1031 | name = "pathfinder_simd" 1032 | version = "0.5.4" 1033 | source = "git+https://github.com/servo/pathfinder#1b7c8bcdaf9da3f045af6a650b5f5c00f0c5a7eb" 1034 | dependencies = [ 1035 | "rustc_version", 1036 | ] 1037 | 1038 | [[package]] 1039 | name = "pkg-config" 1040 | version = "0.3.31" 1041 | source = "registry+https://github.com/rust-lang/crates.io-index" 1042 | checksum = "953ec861398dccce10c670dfeaf3ec4911ca479e9c02154b3a215178c5f566f2" 1043 | 1044 | [[package]] 1045 | name = "plist" 1046 | version = "1.7.0" 1047 | source = "registry+https://github.com/rust-lang/crates.io-index" 1048 | checksum = "42cf17e9a1800f5f396bc67d193dc9411b59012a5876445ef450d449881e1016" 1049 | dependencies = [ 1050 | "base64", 1051 | "indexmap", 1052 | "quick-xml", 1053 | "serde", 1054 | "time", 1055 | ] 1056 | 1057 | [[package]] 1058 | name = "png" 1059 | version = "0.17.13" 1060 | source = "registry+https://github.com/rust-lang/crates.io-index" 1061 | checksum = "06e4b0d3d1312775e782c86c91a111aa1f910cbb65e1337f9975b5f9a554b5e1" 1062 | dependencies = [ 1063 | "bitflags 1.3.2", 1064 | "crc32fast", 1065 | "fdeflate", 1066 | "flate2", 1067 | "miniz_oxide 0.7.4", 1068 | ] 1069 | 1070 | [[package]] 1071 | name = "powerfmt" 1072 | version = "0.2.0" 1073 | source = "registry+https://github.com/rust-lang/crates.io-index" 1074 | checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" 1075 | 1076 | [[package]] 1077 | name = "ppv-lite86" 1078 | version = "0.2.20" 1079 | source = "registry+https://github.com/rust-lang/crates.io-index" 1080 | checksum = "77957b295656769bb8ad2b6a6b09d897d94f05c41b069aede1fcdaa675eaea04" 1081 | dependencies = [ 1082 | "zerocopy", 1083 | ] 1084 | 1085 | [[package]] 1086 | name = "proc-macro-error" 1087 | version = "1.0.4" 1088 | source = "registry+https://github.com/rust-lang/crates.io-index" 1089 | checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" 1090 | dependencies = [ 1091 | "proc-macro-error-attr", 1092 | "proc-macro2", 1093 | "quote", 1094 | "syn 1.0.109", 1095 | "version_check", 1096 | ] 1097 | 1098 | [[package]] 1099 | name = "proc-macro-error-attr" 1100 | version = "1.0.4" 1101 | source = "registry+https://github.com/rust-lang/crates.io-index" 1102 | checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" 1103 | dependencies = [ 1104 | "proc-macro2", 1105 | "quote", 1106 | "version_check", 1107 | ] 1108 | 1109 | [[package]] 1110 | name = "proc-macro2" 1111 | version = "1.0.86" 1112 | source = "registry+https://github.com/rust-lang/crates.io-index" 1113 | checksum = "5e719e8df665df0d1c8fbfd238015744736151d4445ec0836b8e628aae103b77" 1114 | dependencies = [ 1115 | "unicode-ident", 1116 | ] 1117 | 1118 | [[package]] 1119 | name = "quick-xml" 1120 | version = "0.32.0" 1121 | source = "registry+https://github.com/rust-lang/crates.io-index" 1122 | checksum = "1d3a6e5838b60e0e8fa7a43f22ade549a37d61f8bdbe636d0d7816191de969c2" 1123 | dependencies = [ 1124 | "memchr", 1125 | ] 1126 | 1127 | [[package]] 1128 | name = "quote" 1129 | version = "1.0.37" 1130 | source = "registry+https://github.com/rust-lang/crates.io-index" 1131 | checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" 1132 | dependencies = [ 1133 | "proc-macro2", 1134 | ] 1135 | 1136 | [[package]] 1137 | name = "rand" 1138 | version = "0.7.3" 1139 | source = "registry+https://github.com/rust-lang/crates.io-index" 1140 | checksum = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03" 1141 | dependencies = [ 1142 | "getrandom 0.1.16", 1143 | "libc", 1144 | "rand_chacha", 1145 | "rand_core", 1146 | "rand_hc", 1147 | ] 1148 | 1149 | [[package]] 1150 | name = "rand_chacha" 1151 | version = "0.2.2" 1152 | source = "registry+https://github.com/rust-lang/crates.io-index" 1153 | checksum = "f4c8ed856279c9737206bf725bf36935d8666ead7aa69b52be55af369d193402" 1154 | dependencies = [ 1155 | "ppv-lite86", 1156 | "rand_core", 1157 | ] 1158 | 1159 | [[package]] 1160 | name = "rand_core" 1161 | version = "0.5.1" 1162 | source = "registry+https://github.com/rust-lang/crates.io-index" 1163 | checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19" 1164 | dependencies = [ 1165 | "getrandom 0.1.16", 1166 | ] 1167 | 1168 | [[package]] 1169 | name = "rand_distr" 1170 | version = "0.2.2" 1171 | source = "registry+https://github.com/rust-lang/crates.io-index" 1172 | checksum = "96977acbdd3a6576fb1d27391900035bf3863d4a16422973a409b488cf29ffb2" 1173 | dependencies = [ 1174 | "rand", 1175 | ] 1176 | 1177 | [[package]] 1178 | name = "rand_hc" 1179 | version = "0.2.0" 1180 | source = "registry+https://github.com/rust-lang/crates.io-index" 1181 | checksum = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c" 1182 | dependencies = [ 1183 | "rand_core", 1184 | ] 1185 | 1186 | [[package]] 1187 | name = "rawpointer" 1188 | version = "0.2.1" 1189 | source = "registry+https://github.com/rust-lang/crates.io-index" 1190 | checksum = "60a357793950651c4ed0f3f52338f53b2f809f32d83a07f72909fa13e4c6c1e3" 1191 | 1192 | [[package]] 1193 | name = "rayon" 1194 | version = "1.10.0" 1195 | source = "registry+https://github.com/rust-lang/crates.io-index" 1196 | checksum = "b418a60154510ca1a002a752ca9714984e21e4241e804d32555251faf8b78ffa" 1197 | dependencies = [ 1198 | "either", 1199 | "rayon-core", 1200 | ] 1201 | 1202 | [[package]] 1203 | name = "rayon-core" 1204 | version = "1.12.1" 1205 | source = "registry+https://github.com/rust-lang/crates.io-index" 1206 | checksum = "1465873a3dfdaa8ae7cb14b4383657caab0b3e8a0aa9ae8e04b044854c8dfce2" 1207 | dependencies = [ 1208 | "crossbeam-deque", 1209 | "crossbeam-utils", 1210 | ] 1211 | 1212 | [[package]] 1213 | name = "redox_users" 1214 | version = "0.4.6" 1215 | source = "registry+https://github.com/rust-lang/crates.io-index" 1216 | checksum = "ba009ff324d1fc1b900bd1fdb31564febe58a8ccc8a6fdbb93b543d33b13ca43" 1217 | dependencies = [ 1218 | "getrandom 0.2.15", 1219 | "libredox", 1220 | "thiserror", 1221 | ] 1222 | 1223 | [[package]] 1224 | name = "regex-syntax" 1225 | version = "0.8.4" 1226 | source = "registry+https://github.com/rust-lang/crates.io-index" 1227 | checksum = "7a66a03ae7c801facd77a29370b4faec201768915ac14a721ba36f20bc9c209b" 1228 | 1229 | [[package]] 1230 | name = "rustc_version" 1231 | version = "0.4.1" 1232 | source = "registry+https://github.com/rust-lang/crates.io-index" 1233 | checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" 1234 | dependencies = [ 1235 | "semver", 1236 | ] 1237 | 1238 | [[package]] 1239 | name = "rustix" 1240 | version = "0.38.37" 1241 | source = "registry+https://github.com/rust-lang/crates.io-index" 1242 | checksum = "8acb788b847c24f28525660c4d7758620a7210875711f79e7f663cc152726811" 1243 | dependencies = [ 1244 | "bitflags 2.6.0", 1245 | "errno", 1246 | "libc", 1247 | "linux-raw-sys", 1248 | "windows-sys 0.52.0", 1249 | ] 1250 | 1251 | [[package]] 1252 | name = "rusttype" 1253 | version = "0.9.3" 1254 | source = "registry+https://github.com/rust-lang/crates.io-index" 1255 | checksum = "3ff8374aa04134254b7995b63ad3dc41c7f7236f69528b28553da7d72efaa967" 1256 | dependencies = [ 1257 | "ab_glyph_rasterizer", 1258 | "owned_ttf_parser", 1259 | ] 1260 | 1261 | [[package]] 1262 | name = "ryu" 1263 | version = "1.0.18" 1264 | source = "registry+https://github.com/rust-lang/crates.io-index" 1265 | checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" 1266 | 1267 | [[package]] 1268 | name = "safe_arch" 1269 | version = "0.7.2" 1270 | source = "registry+https://github.com/rust-lang/crates.io-index" 1271 | checksum = "c3460605018fdc9612bce72735cba0d27efbcd9904780d44c7e3a9948f96148a" 1272 | dependencies = [ 1273 | "bytemuck", 1274 | ] 1275 | 1276 | [[package]] 1277 | name = "same-file" 1278 | version = "1.0.6" 1279 | source = "registry+https://github.com/rust-lang/crates.io-index" 1280 | checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" 1281 | dependencies = [ 1282 | "winapi-util", 1283 | ] 1284 | 1285 | [[package]] 1286 | name = "semver" 1287 | version = "1.0.23" 1288 | source = "registry+https://github.com/rust-lang/crates.io-index" 1289 | checksum = "61697e0a1c7e512e84a621326239844a24d8207b4669b41bc18b32ea5cbf988b" 1290 | 1291 | [[package]] 1292 | name = "serde" 1293 | version = "1.0.210" 1294 | source = "registry+https://github.com/rust-lang/crates.io-index" 1295 | checksum = "c8e3592472072e6e22e0a54d5904d9febf8508f65fb8552499a1abc7d1078c3a" 1296 | dependencies = [ 1297 | "serde_derive", 1298 | ] 1299 | 1300 | [[package]] 1301 | name = "serde_derive" 1302 | version = "1.0.210" 1303 | source = "registry+https://github.com/rust-lang/crates.io-index" 1304 | checksum = "243902eda00fad750862fc144cea25caca5e20d615af0a81bee94ca738f1df1f" 1305 | dependencies = [ 1306 | "proc-macro2", 1307 | "quote", 1308 | "syn 2.0.77", 1309 | ] 1310 | 1311 | [[package]] 1312 | name = "serde_json" 1313 | version = "1.0.128" 1314 | source = "registry+https://github.com/rust-lang/crates.io-index" 1315 | checksum = "6ff5456707a1de34e7e37f2a6fd3d3f808c318259cbd01ab6377795054b483d8" 1316 | dependencies = [ 1317 | "itoa", 1318 | "memchr", 1319 | "ryu", 1320 | "serde", 1321 | ] 1322 | 1323 | [[package]] 1324 | name = "shell-words" 1325 | version = "1.1.0" 1326 | source = "registry+https://github.com/rust-lang/crates.io-index" 1327 | checksum = "24188a676b6ae68c3b2cb3a01be17fbf7240ce009799bb56d5b1409051e78fde" 1328 | 1329 | [[package]] 1330 | name = "shlex" 1331 | version = "1.3.0" 1332 | source = "registry+https://github.com/rust-lang/crates.io-index" 1333 | checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" 1334 | 1335 | [[package]] 1336 | name = "silicon" 1337 | version = "0.5.2" 1338 | dependencies = [ 1339 | "anyhow", 1340 | "clipboard", 1341 | "clipboard-win 5.4.0", 1342 | "conv", 1343 | "dirs", 1344 | "env_logger", 1345 | "font-kit", 1346 | "harfbuzz-sys", 1347 | "image", 1348 | "imageproc", 1349 | "lazy_static", 1350 | "log", 1351 | "pasteboard", 1352 | "pathfinder_geometry", 1353 | "pathfinder_simd", 1354 | "rayon", 1355 | "shell-words", 1356 | "structopt", 1357 | "syntect", 1358 | "tempfile", 1359 | ] 1360 | 1361 | [[package]] 1362 | name = "simba" 1363 | version = "0.7.3" 1364 | source = "registry+https://github.com/rust-lang/crates.io-index" 1365 | checksum = "2f3fd720c48c53cace224ae62bef1bbff363a70c68c4802a78b5cc6159618176" 1366 | dependencies = [ 1367 | "approx", 1368 | "num-complex", 1369 | "num-traits", 1370 | "paste", 1371 | "wide", 1372 | ] 1373 | 1374 | [[package]] 1375 | name = "simd-adler32" 1376 | version = "0.3.7" 1377 | source = "registry+https://github.com/rust-lang/crates.io-index" 1378 | checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe" 1379 | 1380 | [[package]] 1381 | name = "strsim" 1382 | version = "0.8.0" 1383 | source = "registry+https://github.com/rust-lang/crates.io-index" 1384 | checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a" 1385 | 1386 | [[package]] 1387 | name = "structopt" 1388 | version = "0.3.26" 1389 | source = "registry+https://github.com/rust-lang/crates.io-index" 1390 | checksum = "0c6b5c64445ba8094a6ab0c3cd2ad323e07171012d9c98b0b15651daf1787a10" 1391 | dependencies = [ 1392 | "clap", 1393 | "lazy_static", 1394 | "structopt-derive", 1395 | ] 1396 | 1397 | [[package]] 1398 | name = "structopt-derive" 1399 | version = "0.4.18" 1400 | source = "registry+https://github.com/rust-lang/crates.io-index" 1401 | checksum = "dcb5ae327f9cc13b68763b5749770cb9e048a99bd9dfdfa58d0cf05d5f64afe0" 1402 | dependencies = [ 1403 | "heck", 1404 | "proc-macro-error", 1405 | "proc-macro2", 1406 | "quote", 1407 | "syn 1.0.109", 1408 | ] 1409 | 1410 | [[package]] 1411 | name = "syn" 1412 | version = "1.0.109" 1413 | source = "registry+https://github.com/rust-lang/crates.io-index" 1414 | checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" 1415 | dependencies = [ 1416 | "proc-macro2", 1417 | "quote", 1418 | "unicode-ident", 1419 | ] 1420 | 1421 | [[package]] 1422 | name = "syn" 1423 | version = "2.0.77" 1424 | source = "registry+https://github.com/rust-lang/crates.io-index" 1425 | checksum = "9f35bcdf61fd8e7be6caf75f429fdca8beb3ed76584befb503b1569faee373ed" 1426 | dependencies = [ 1427 | "proc-macro2", 1428 | "quote", 1429 | "unicode-ident", 1430 | ] 1431 | 1432 | [[package]] 1433 | name = "syntect" 1434 | version = "5.2.0" 1435 | source = "registry+https://github.com/rust-lang/crates.io-index" 1436 | checksum = "874dcfa363995604333cf947ae9f751ca3af4522c60886774c4963943b4746b1" 1437 | dependencies = [ 1438 | "bincode", 1439 | "bitflags 1.3.2", 1440 | "flate2", 1441 | "fnv", 1442 | "once_cell", 1443 | "onig", 1444 | "plist", 1445 | "regex-syntax", 1446 | "serde", 1447 | "serde_derive", 1448 | "serde_json", 1449 | "thiserror", 1450 | "walkdir", 1451 | "yaml-rust", 1452 | ] 1453 | 1454 | [[package]] 1455 | name = "tempfile" 1456 | version = "3.12.0" 1457 | source = "registry+https://github.com/rust-lang/crates.io-index" 1458 | checksum = "04cbcdd0c794ebb0d4cf35e88edd2f7d2c4c3e9a5a6dab322839b321c6a87a64" 1459 | dependencies = [ 1460 | "cfg-if", 1461 | "fastrand", 1462 | "once_cell", 1463 | "rustix", 1464 | "windows-sys 0.59.0", 1465 | ] 1466 | 1467 | [[package]] 1468 | name = "term_size" 1469 | version = "0.3.2" 1470 | source = "registry+https://github.com/rust-lang/crates.io-index" 1471 | checksum = "1e4129646ca0ed8f45d09b929036bafad5377103edd06e50bf574b353d2b08d9" 1472 | dependencies = [ 1473 | "libc", 1474 | "winapi", 1475 | ] 1476 | 1477 | [[package]] 1478 | name = "textwrap" 1479 | version = "0.11.0" 1480 | source = "registry+https://github.com/rust-lang/crates.io-index" 1481 | checksum = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060" 1482 | dependencies = [ 1483 | "term_size", 1484 | "unicode-width", 1485 | ] 1486 | 1487 | [[package]] 1488 | name = "thiserror" 1489 | version = "1.0.64" 1490 | source = "registry+https://github.com/rust-lang/crates.io-index" 1491 | checksum = "d50af8abc119fb8bb6dbabcfa89656f46f84aa0ac7688088608076ad2b459a84" 1492 | dependencies = [ 1493 | "thiserror-impl", 1494 | ] 1495 | 1496 | [[package]] 1497 | name = "thiserror-impl" 1498 | version = "1.0.64" 1499 | source = "registry+https://github.com/rust-lang/crates.io-index" 1500 | checksum = "08904e7672f5eb876eaaf87e0ce17857500934f4981c4a0ab2b4aa98baac7fc3" 1501 | dependencies = [ 1502 | "proc-macro2", 1503 | "quote", 1504 | "syn 2.0.77", 1505 | ] 1506 | 1507 | [[package]] 1508 | name = "time" 1509 | version = "0.3.36" 1510 | source = "registry+https://github.com/rust-lang/crates.io-index" 1511 | checksum = "5dfd88e563464686c916c7e46e623e520ddc6d79fa6641390f2e3fa86e83e885" 1512 | dependencies = [ 1513 | "deranged", 1514 | "itoa", 1515 | "num-conv", 1516 | "powerfmt", 1517 | "serde", 1518 | "time-core", 1519 | "time-macros", 1520 | ] 1521 | 1522 | [[package]] 1523 | name = "time-core" 1524 | version = "0.1.2" 1525 | source = "registry+https://github.com/rust-lang/crates.io-index" 1526 | checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3" 1527 | 1528 | [[package]] 1529 | name = "time-macros" 1530 | version = "0.2.18" 1531 | source = "registry+https://github.com/rust-lang/crates.io-index" 1532 | checksum = "3f252a68540fde3a3877aeea552b832b40ab9a69e318efd078774a01ddee1ccf" 1533 | dependencies = [ 1534 | "num-conv", 1535 | "time-core", 1536 | ] 1537 | 1538 | [[package]] 1539 | name = "ttf-parser" 1540 | version = "0.15.2" 1541 | source = "registry+https://github.com/rust-lang/crates.io-index" 1542 | checksum = "7b3e06c9b9d80ed6b745c7159c40b311ad2916abb34a49e9be2653b90db0d8dd" 1543 | 1544 | [[package]] 1545 | name = "typenum" 1546 | version = "1.17.0" 1547 | source = "registry+https://github.com/rust-lang/crates.io-index" 1548 | checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" 1549 | 1550 | [[package]] 1551 | name = "unicode-ident" 1552 | version = "1.0.13" 1553 | source = "registry+https://github.com/rust-lang/crates.io-index" 1554 | checksum = "e91b56cd4cadaeb79bbf1a5645f6b4f8dc5bde8834ad5894a8db35fda9efa1fe" 1555 | 1556 | [[package]] 1557 | name = "unicode-segmentation" 1558 | version = "1.12.0" 1559 | source = "registry+https://github.com/rust-lang/crates.io-index" 1560 | checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" 1561 | 1562 | [[package]] 1563 | name = "unicode-width" 1564 | version = "0.1.14" 1565 | source = "registry+https://github.com/rust-lang/crates.io-index" 1566 | checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af" 1567 | 1568 | [[package]] 1569 | name = "utf8parse" 1570 | version = "0.2.2" 1571 | source = "registry+https://github.com/rust-lang/crates.io-index" 1572 | checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" 1573 | 1574 | [[package]] 1575 | name = "vec_map" 1576 | version = "0.8.2" 1577 | source = "registry+https://github.com/rust-lang/crates.io-index" 1578 | checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191" 1579 | 1580 | [[package]] 1581 | name = "version_check" 1582 | version = "0.9.5" 1583 | source = "registry+https://github.com/rust-lang/crates.io-index" 1584 | checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" 1585 | 1586 | [[package]] 1587 | name = "walkdir" 1588 | version = "2.5.0" 1589 | source = "registry+https://github.com/rust-lang/crates.io-index" 1590 | checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" 1591 | dependencies = [ 1592 | "same-file", 1593 | "winapi-util", 1594 | ] 1595 | 1596 | [[package]] 1597 | name = "wasi" 1598 | version = "0.9.0+wasi-snapshot-preview1" 1599 | source = "registry+https://github.com/rust-lang/crates.io-index" 1600 | checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519" 1601 | 1602 | [[package]] 1603 | name = "wasi" 1604 | version = "0.11.0+wasi-snapshot-preview1" 1605 | source = "registry+https://github.com/rust-lang/crates.io-index" 1606 | checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" 1607 | 1608 | [[package]] 1609 | name = "wide" 1610 | version = "0.7.28" 1611 | source = "registry+https://github.com/rust-lang/crates.io-index" 1612 | checksum = "b828f995bf1e9622031f8009f8481a85406ce1f4d4588ff746d872043e855690" 1613 | dependencies = [ 1614 | "bytemuck", 1615 | "safe_arch", 1616 | ] 1617 | 1618 | [[package]] 1619 | name = "winapi" 1620 | version = "0.3.9" 1621 | source = "registry+https://github.com/rust-lang/crates.io-index" 1622 | checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" 1623 | dependencies = [ 1624 | "winapi-i686-pc-windows-gnu", 1625 | "winapi-x86_64-pc-windows-gnu", 1626 | ] 1627 | 1628 | [[package]] 1629 | name = "winapi-i686-pc-windows-gnu" 1630 | version = "0.4.0" 1631 | source = "registry+https://github.com/rust-lang/crates.io-index" 1632 | checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 1633 | 1634 | [[package]] 1635 | name = "winapi-util" 1636 | version = "0.1.9" 1637 | source = "registry+https://github.com/rust-lang/crates.io-index" 1638 | checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" 1639 | dependencies = [ 1640 | "windows-sys 0.59.0", 1641 | ] 1642 | 1643 | [[package]] 1644 | name = "winapi-x86_64-pc-windows-gnu" 1645 | version = "0.4.0" 1646 | source = "registry+https://github.com/rust-lang/crates.io-index" 1647 | checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 1648 | 1649 | [[package]] 1650 | name = "windows-sys" 1651 | version = "0.48.0" 1652 | source = "registry+https://github.com/rust-lang/crates.io-index" 1653 | checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" 1654 | dependencies = [ 1655 | "windows-targets 0.48.5", 1656 | ] 1657 | 1658 | [[package]] 1659 | name = "windows-sys" 1660 | version = "0.52.0" 1661 | source = "registry+https://github.com/rust-lang/crates.io-index" 1662 | checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" 1663 | dependencies = [ 1664 | "windows-targets 0.52.6", 1665 | ] 1666 | 1667 | [[package]] 1668 | name = "windows-sys" 1669 | version = "0.59.0" 1670 | source = "registry+https://github.com/rust-lang/crates.io-index" 1671 | checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" 1672 | dependencies = [ 1673 | "windows-targets 0.52.6", 1674 | ] 1675 | 1676 | [[package]] 1677 | name = "windows-targets" 1678 | version = "0.48.5" 1679 | source = "registry+https://github.com/rust-lang/crates.io-index" 1680 | checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" 1681 | dependencies = [ 1682 | "windows_aarch64_gnullvm 0.48.5", 1683 | "windows_aarch64_msvc 0.48.5", 1684 | "windows_i686_gnu 0.48.5", 1685 | "windows_i686_msvc 0.48.5", 1686 | "windows_x86_64_gnu 0.48.5", 1687 | "windows_x86_64_gnullvm 0.48.5", 1688 | "windows_x86_64_msvc 0.48.5", 1689 | ] 1690 | 1691 | [[package]] 1692 | name = "windows-targets" 1693 | version = "0.52.6" 1694 | source = "registry+https://github.com/rust-lang/crates.io-index" 1695 | checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" 1696 | dependencies = [ 1697 | "windows_aarch64_gnullvm 0.52.6", 1698 | "windows_aarch64_msvc 0.52.6", 1699 | "windows_i686_gnu 0.52.6", 1700 | "windows_i686_gnullvm", 1701 | "windows_i686_msvc 0.52.6", 1702 | "windows_x86_64_gnu 0.52.6", 1703 | "windows_x86_64_gnullvm 0.52.6", 1704 | "windows_x86_64_msvc 0.52.6", 1705 | ] 1706 | 1707 | [[package]] 1708 | name = "windows_aarch64_gnullvm" 1709 | version = "0.48.5" 1710 | source = "registry+https://github.com/rust-lang/crates.io-index" 1711 | checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" 1712 | 1713 | [[package]] 1714 | name = "windows_aarch64_gnullvm" 1715 | version = "0.52.6" 1716 | source = "registry+https://github.com/rust-lang/crates.io-index" 1717 | checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" 1718 | 1719 | [[package]] 1720 | name = "windows_aarch64_msvc" 1721 | version = "0.48.5" 1722 | source = "registry+https://github.com/rust-lang/crates.io-index" 1723 | checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" 1724 | 1725 | [[package]] 1726 | name = "windows_aarch64_msvc" 1727 | version = "0.52.6" 1728 | source = "registry+https://github.com/rust-lang/crates.io-index" 1729 | checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" 1730 | 1731 | [[package]] 1732 | name = "windows_i686_gnu" 1733 | version = "0.48.5" 1734 | source = "registry+https://github.com/rust-lang/crates.io-index" 1735 | checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" 1736 | 1737 | [[package]] 1738 | name = "windows_i686_gnu" 1739 | version = "0.52.6" 1740 | source = "registry+https://github.com/rust-lang/crates.io-index" 1741 | checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" 1742 | 1743 | [[package]] 1744 | name = "windows_i686_gnullvm" 1745 | version = "0.52.6" 1746 | source = "registry+https://github.com/rust-lang/crates.io-index" 1747 | checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" 1748 | 1749 | [[package]] 1750 | name = "windows_i686_msvc" 1751 | version = "0.48.5" 1752 | source = "registry+https://github.com/rust-lang/crates.io-index" 1753 | checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" 1754 | 1755 | [[package]] 1756 | name = "windows_i686_msvc" 1757 | version = "0.52.6" 1758 | source = "registry+https://github.com/rust-lang/crates.io-index" 1759 | checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" 1760 | 1761 | [[package]] 1762 | name = "windows_x86_64_gnu" 1763 | version = "0.48.5" 1764 | source = "registry+https://github.com/rust-lang/crates.io-index" 1765 | checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" 1766 | 1767 | [[package]] 1768 | name = "windows_x86_64_gnu" 1769 | version = "0.52.6" 1770 | source = "registry+https://github.com/rust-lang/crates.io-index" 1771 | checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" 1772 | 1773 | [[package]] 1774 | name = "windows_x86_64_gnullvm" 1775 | version = "0.48.5" 1776 | source = "registry+https://github.com/rust-lang/crates.io-index" 1777 | checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" 1778 | 1779 | [[package]] 1780 | name = "windows_x86_64_gnullvm" 1781 | version = "0.52.6" 1782 | source = "registry+https://github.com/rust-lang/crates.io-index" 1783 | checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" 1784 | 1785 | [[package]] 1786 | name = "windows_x86_64_msvc" 1787 | version = "0.48.5" 1788 | source = "registry+https://github.com/rust-lang/crates.io-index" 1789 | checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" 1790 | 1791 | [[package]] 1792 | name = "windows_x86_64_msvc" 1793 | version = "0.52.6" 1794 | source = "registry+https://github.com/rust-lang/crates.io-index" 1795 | checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" 1796 | 1797 | [[package]] 1798 | name = "wio" 1799 | version = "0.2.2" 1800 | source = "registry+https://github.com/rust-lang/crates.io-index" 1801 | checksum = "5d129932f4644ac2396cb456385cbf9e63b5b30c6e8dc4820bdca4eb082037a5" 1802 | dependencies = [ 1803 | "winapi", 1804 | ] 1805 | 1806 | [[package]] 1807 | name = "x11-clipboard" 1808 | version = "0.3.3" 1809 | source = "registry+https://github.com/rust-lang/crates.io-index" 1810 | checksum = "89bd49c06c9eb5d98e6ba6536cf64ac9f7ee3a009b2f53996d405b3944f6bcea" 1811 | dependencies = [ 1812 | "xcb", 1813 | ] 1814 | 1815 | [[package]] 1816 | name = "xcb" 1817 | version = "0.8.2" 1818 | source = "registry+https://github.com/rust-lang/crates.io-index" 1819 | checksum = "5e917a3f24142e9ff8be2414e36c649d47d6cc2ba81f16201cdef96e533e02de" 1820 | dependencies = [ 1821 | "libc", 1822 | "log", 1823 | ] 1824 | 1825 | [[package]] 1826 | name = "yaml-rust" 1827 | version = "0.4.5" 1828 | source = "registry+https://github.com/rust-lang/crates.io-index" 1829 | checksum = "56c1936c4cc7a1c9ab21a1ebb602eb942ba868cbd44a99cb7cdc5892335e1c85" 1830 | dependencies = [ 1831 | "linked-hash-map", 1832 | ] 1833 | 1834 | [[package]] 1835 | name = "yeslogic-fontconfig-sys" 1836 | version = "5.0.0" 1837 | source = "registry+https://github.com/rust-lang/crates.io-index" 1838 | checksum = "ffb6b23999a8b1a997bf47c7bb4d19ad4029c3327bb3386ebe0a5ff584b33c7a" 1839 | dependencies = [ 1840 | "cstr", 1841 | "dlib", 1842 | "once_cell", 1843 | "pkg-config", 1844 | ] 1845 | 1846 | [[package]] 1847 | name = "zerocopy" 1848 | version = "0.7.35" 1849 | source = "registry+https://github.com/rust-lang/crates.io-index" 1850 | checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0" 1851 | dependencies = [ 1852 | "byteorder", 1853 | "zerocopy-derive", 1854 | ] 1855 | 1856 | [[package]] 1857 | name = "zerocopy-derive" 1858 | version = "0.7.35" 1859 | source = "registry+https://github.com/rust-lang/crates.io-index" 1860 | checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" 1861 | dependencies = [ 1862 | "proc-macro2", 1863 | "quote", 1864 | "syn 2.0.77", 1865 | ] 1866 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "silicon" 3 | version = "0.5.2" 4 | description = "Create beautiful image of your code" 5 | authors = ["Aloxaf "] 6 | categories = ["command-line-utilities"] 7 | readme = "README.md" 8 | repository = "https://github.com/Aloxaf/silicon" 9 | license = "MIT" 10 | edition = "2018" 11 | 12 | [features] 13 | # bin fearure is required for silicon as a application 14 | # disable it when using as a library 15 | default = ["bin", "harfbuzz"] 16 | bin = ["structopt", "env_logger", "anyhow", "shell-words"] 17 | harfbuzz = ["harfbuzz-sys", "font-kit/loader-freetype-default", "font-kit/source-fontconfig-default"] 18 | 19 | [dependencies] 20 | dirs = "5.0.1" 21 | imageproc = "0.23.0" 22 | clipboard = "0.5.0" 23 | tempfile = "3.10.1" 24 | conv = "0.3.3" 25 | pathfinder_geometry = "0.5.1" 26 | log = "0.4.20" 27 | lazy_static = "1.4.0" 28 | shell-words = { version = "1.1.0", optional = true } 29 | rayon = "1.9.0" 30 | font-kit = "0.12.0" 31 | harfbuzz-sys = { version = "0.5.0", optional = true } 32 | pathfinder_simd = "0.5.3" 33 | 34 | [dependencies.image] 35 | version = "0.24.9" 36 | default-features = false 37 | features = ["jpeg", "png", "jpeg_rayon"] 38 | 39 | [dependencies.syntect] 40 | version = "5.2.0" 41 | default-features = false 42 | features = ["parsing", "dump-load", "regex-onig", "plist-load", "yaml-load"] 43 | 44 | [dependencies.anyhow] 45 | version = "1.0.80" 46 | optional = true 47 | 48 | [dependencies.structopt] 49 | version = "0.3.26" 50 | default-features = false 51 | features = ["color", "wrap_help"] 52 | optional = true 53 | 54 | [dependencies.env_logger] 55 | version = "0.11.2" 56 | default-features = false 57 | features = ["auto-color", "humantime"] 58 | optional = true 59 | 60 | [target.'cfg(target_os = "macos")'.dependencies] 61 | pasteboard = "0.1.3" 62 | 63 | [target.'cfg(target_os = "windows")'.dependencies] 64 | clipboard-win = "5.2.0" 65 | image = { version = "0.24", default-features = false, features = ["jpeg", "bmp", "jpeg_rayon"] } 66 | 67 | [patch.crates-io] 68 | pathfinder_simd = { version = "0.5.4", git = "https://github.com/servo/pathfinder" } 69 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019-2023 Aloxaf 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Silicon 2 | 3 | [![Crates.io](https://img.shields.io/crates/v/silicon.svg)](https://crates.io/crates/silicon) 4 | [![Documentation](https://docs.rs/silicon/badge.svg)](https://docs.rs/silicon) 5 | [![CI](https://github.com/Aloxaf/silicon/workflows/CI/badge.svg)](https://github.com/Aloxaf/silicon/actions?query=workflow%3ACI) 6 | ![License](https://img.shields.io/crates/l/silicon.svg) 7 | 8 | Silicon is an alternative to [Carbon](https://github.com/dawnlabs/carbon) implemented in Rust. 9 | 10 | It can render your source code into a beautiful image. 11 | 12 | 13 | 14 | ## Why Silicon 15 | 16 | Carbon is a wonderful tool to create a beautiful image of your source code. 17 | 18 | But it is a web application, which brings the following disadvantages: 19 | - Cannot work without Internet & browser. 20 | - Doesn't work well with shell. (Although there is _carbon-now-cli_, its experience is not very good, especially when the network is not so good.) 21 | 22 | However, Silicon doesn't have these problems. 23 | It's is implemented in Rust and can work without browser & Internet. 24 | 25 | Silicon can render your source code on the fly while _carbon-now-cli_ takes several seconds on it. 26 | 27 | ## Disadvantages 28 | 29 | It's not as beautiful as Carbon... 30 | 31 | ## Install 32 | 33 | ### Cargo 34 | 35 | ```bash 36 | cargo install silicon 37 | ``` 38 | 39 | NOTE: harfbuzz feature is enabled by default. If you are using Windows, I suggest you disable it to get it build easier. 40 | 41 | ### AUR 42 | 43 | Silicon is available in the official repository: 44 | 45 | ```bash 46 | pacman -S silicon 47 | ``` 48 | 49 | ### Homebrew 50 | 51 | You can install Silicon using [Homebrew](https://brew.sh): 52 | 53 | ```bash 54 | brew install silicon 55 | ``` 56 | 57 | ## Dependencies 58 | 59 | ### Ubuntu 60 | ```bash 61 | sudo apt install expat 62 | sudo apt install libxml2-dev 63 | sudo apt install pkg-config libasound2-dev libssl-dev cmake libfreetype6-dev libexpat1-dev libxcb-composite0-dev libharfbuzz-dev libfontconfig1-dev g++ 64 | ``` 65 | 66 | ### Fedora 67 | ```bash 68 | sudo dnf install \ 69 | cmake \ 70 | expat-devel fontconfig-devel libxcb-devel \ 71 | freetype-devel libxml2-devel \ 72 | harfbuzz 73 | ``` 74 | 75 | ### Arch Linux 76 | 77 | ```bash 78 | sudo pacman -S --needed pkgconf freetype2 fontconfig libxcb xclip harfbuzz 79 | ``` 80 | 81 | ## Examples 82 | 83 | Read code from file 84 | 85 | ```bash 86 | silicon main.rs -o main.png 87 | ``` 88 | 89 | Read code from clipboard, and copy the result image to clipboard 90 | 91 | ```bash 92 | silicon --from-clipboard -l rs --to-clipboard 93 | ``` 94 | 95 | Specify a fallback font list and their size 96 | 97 | ```bash 98 | silicon -o main.png -l bash -f 'Hack; SimSun=31; code2000' < u64 { 2 | match n { 3 | 0 => 1, 4 | _ => n * factorial(n - 1), 5 | } 6 | } 7 | 8 | fn main() { 9 | println!("10! = {}", factorial(10)); 10 | } -------------------------------------------------------------------------------- /example/example.sh: -------------------------------------------------------------------------------- 1 | silicon example.rs -o example.png 2 | -------------------------------------------------------------------------------- /src/assets.rs: -------------------------------------------------------------------------------- 1 | use std::path::Path; 2 | 3 | use crate::directories::PROJECT_DIRS; 4 | use anyhow::Result; 5 | use syntect::dumps; 6 | use syntect::highlighting::ThemeSet; 7 | use syntect::parsing::SyntaxSet; 8 | 9 | const DEFAULT_SYNTAXSET: &[u8] = include_bytes!("../assets/syntaxes.bin"); 10 | const DEFAULT_THEMESET: &[u8] = include_bytes!("../assets/themes.bin"); 11 | 12 | pub struct HighlightingAssets { 13 | pub syntax_set: SyntaxSet, 14 | pub theme_set: ThemeSet, 15 | } 16 | 17 | impl Default for HighlightingAssets { 18 | fn default() -> Self { 19 | Self::new() 20 | } 21 | } 22 | 23 | impl HighlightingAssets { 24 | pub fn new() -> Self { 25 | Self::from_dump_file().unwrap_or_else(|_| Self { 26 | syntax_set: dumps::from_binary(DEFAULT_SYNTAXSET), 27 | theme_set: dumps::from_binary(DEFAULT_THEMESET), 28 | }) 29 | } 30 | 31 | pub fn from_dump_file() -> Result { 32 | let cache_dir = PROJECT_DIRS.cache_dir(); 33 | Ok(Self { 34 | syntax_set: dumps::from_dump_file(cache_dir.join("syntaxes.bin"))?, 35 | theme_set: dumps::from_dump_file(cache_dir.join("themes.bin"))?, 36 | }) 37 | } 38 | 39 | pub fn add_from_folder>(&mut self, path: P) -> Result<()> { 40 | let path = path.as_ref(); 41 | let theme_dir = path.join("themes"); 42 | if theme_dir.is_dir() { 43 | self.theme_set.add_from_folder(theme_dir)?; 44 | } 45 | let mut builder = self.syntax_set.clone().into_builder(); 46 | let syntaxes_dir = path.join("syntaxes"); 47 | if syntaxes_dir.is_dir() { 48 | builder.add_from_folder(syntaxes_dir, true)?; 49 | self.syntax_set = builder.build(); 50 | } 51 | Ok(()) 52 | } 53 | 54 | pub fn dump_to_file>(&self, path: P) -> Result<()> { 55 | dumps::dump_to_file(&self.syntax_set, path.as_ref().join("syntaxes.bin"))?; 56 | dumps::dump_to_file(&self.theme_set, path.as_ref().join("themes.bin"))?; 57 | Ok(()) 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/bin/silicon/config.rs: -------------------------------------------------------------------------------- 1 | use anyhow::{Context, Error}; 2 | use clipboard::{ClipboardContext, ClipboardProvider}; 3 | use image::Rgba; 4 | use silicon::directories::PROJECT_DIRS; 5 | use silicon::font::FontCollection; 6 | use silicon::formatter::{ImageFormatter, ImageFormatterBuilder}; 7 | use silicon::utils::{Background, ShadowAdder, ToRgba}; 8 | use std::ffi::OsString; 9 | use std::fs::File; 10 | use std::io::{stdin, Read}; 11 | use std::num::ParseIntError; 12 | use std::path::PathBuf; 13 | use structopt::clap::AppSettings::ColoredHelp; 14 | use structopt::StructOpt; 15 | use syntect::highlighting::{Theme, ThemeSet}; 16 | use syntect::parsing::{SyntaxReference, SyntaxSet}; 17 | 18 | pub fn config_file() -> PathBuf { 19 | std::env::var("SILICON_CONFIG_PATH") 20 | .ok() 21 | .map(PathBuf::from) 22 | .filter(|config_path| config_path.is_file()) 23 | .unwrap_or_else(|| PROJECT_DIRS.config_dir().join("config")) 24 | } 25 | 26 | pub fn get_args_from_config_file() -> Vec { 27 | let args = std::fs::read_to_string(config_file()) 28 | .ok() 29 | .and_then(|content| { 30 | content 31 | .split('\n') 32 | .map(|line| line.trim()) 33 | .filter(|line| !line.starts_with('#') && !line.is_empty()) 34 | .map(shell_words::split) 35 | .collect::, _>>() 36 | .ok() 37 | }) 38 | .unwrap_or_default(); 39 | args.iter().flatten().map(OsString::from).collect() 40 | } 41 | 42 | fn parse_str_color(s: &str) -> Result, Error> { 43 | s.to_rgba() 44 | .map_err(|_| format_err!("Invalid color: `{}`", s)) 45 | } 46 | 47 | fn parse_font_str(s: &str) -> Vec<(String, f32)> { 48 | let mut result = vec![]; 49 | for font in s.split(';') { 50 | let tmp = font.split('=').collect::>(); 51 | let font_name = tmp[0].to_owned(); 52 | let font_size = tmp 53 | .get(1) 54 | .map(|s| s.parse::().unwrap()) 55 | .unwrap_or(26.0); 56 | result.push((font_name, font_size)); 57 | } 58 | result 59 | } 60 | 61 | fn parse_line_range(s: &str) -> Result, ParseIntError> { 62 | let mut result = vec![]; 63 | for range in s.split(';') { 64 | let range: Vec = range 65 | .split('-') 66 | .map(|s| s.parse::()) 67 | .collect::, _>>()?; 68 | if range.len() == 1 { 69 | result.push(range[0]) 70 | } else { 71 | for i in range[0]..=range[1] { 72 | result.push(i); 73 | } 74 | } 75 | } 76 | Ok(result) 77 | } 78 | 79 | // https://github.com/TeXitoi/structopt/blob/master/CHANGELOG.md#support-optional-vectors-of-arguments-for-distinguishing-between--o-1-2--o-and-no-option-provided-at-all-by-sphynx-180 80 | type FontList = Vec<(String, f32)>; 81 | type Lines = Vec; 82 | 83 | #[derive(StructOpt, Debug)] 84 | #[structopt(name = "silicon")] 85 | #[structopt(global_setting(ColoredHelp))] 86 | pub struct Config { 87 | /// Background image 88 | #[structopt(long, value_name = "IMAGE", conflicts_with = "background")] 89 | pub background_image: Option, 90 | 91 | /// Background color of the image 92 | #[structopt( 93 | long, 94 | short, 95 | value_name = "COLOR", 96 | default_value = "#aaaaff", 97 | parse(try_from_str = parse_str_color) 98 | )] 99 | pub background: Rgba, 100 | 101 | /// Show the path of silicon config file 102 | #[structopt(long)] 103 | pub config_file: bool, 104 | 105 | /// Read input from clipboard. 106 | #[structopt(long)] 107 | pub from_clipboard: bool, 108 | 109 | /// File to read. If not set, stdin will be use. 110 | #[structopt(value_name = "FILE", parse(from_os_str))] 111 | pub file: Option, 112 | 113 | /// The fallback font list. eg. 'Hack; SimSun=31' 114 | #[structopt(long, short, value_name = "FONT", parse(from_str = parse_font_str))] 115 | pub font: Option, 116 | 117 | /// Lines to highlight. eg. '1-3;4' 118 | #[structopt(long, value_name = "LINES", parse(try_from_str = parse_line_range))] 119 | pub highlight_lines: Option, 120 | 121 | /// The language for syntax highlighting. You can use full name ("Rust") or file extension ("rs"). 122 | #[structopt(short, value_name = "LANG", long)] 123 | pub language: Option, 124 | 125 | /// Pad between lines 126 | #[structopt(long, value_name = "PAD", default_value = "2")] 127 | pub line_pad: u32, 128 | 129 | /// Add PAD padding to the right of the code. 130 | #[structopt(long, value_name = "PAD", default_value = "25")] 131 | pub code_pad_right: u32, 132 | 133 | /// Line number offset 134 | #[structopt(long, value_name = "OFFSET", default_value = "1")] 135 | pub line_offset: u32, 136 | 137 | /// List all themes. 138 | #[structopt(long)] 139 | pub list_themes: bool, 140 | 141 | /// List all available fonts in your system 142 | #[structopt(long)] 143 | pub list_fonts: bool, 144 | 145 | /// Write output image to specific location instead of cwd. 146 | #[structopt( 147 | short, 148 | long, 149 | value_name = "PATH", 150 | required_unless_one = &["config-file", "list-fonts", "list-themes", "to-clipboard", "build-cache"] 151 | )] 152 | pub output: Option, 153 | 154 | /// Hide the window controls. 155 | #[structopt(long)] 156 | pub no_window_controls: bool, 157 | 158 | /// Show window title 159 | #[structopt(long, value_name = "WINDOW_TITLE")] 160 | pub window_title: Option, 161 | 162 | /// Hide the line number. 163 | #[structopt(long)] 164 | pub no_line_number: bool, 165 | 166 | /// Don't round the corner 167 | #[structopt(long)] 168 | pub no_round_corner: bool, 169 | 170 | /// Pad horiz 171 | #[structopt(long, value_name = "PAD", default_value = "80")] 172 | pub pad_horiz: u32, 173 | 174 | /// Pad vert 175 | #[structopt(long, value_name = "PAD", default_value = "100")] 176 | pub pad_vert: u32, 177 | 178 | /// Color of shadow 179 | #[structopt( 180 | long, 181 | value_name = "COLOR", 182 | default_value = "#555555", 183 | parse(try_from_str = parse_str_color) 184 | )] 185 | pub shadow_color: Rgba, 186 | 187 | /// Blur radius of the shadow. (set it to 0 to hide shadow) 188 | #[structopt(long, value_name = "R", default_value = "0")] 189 | pub shadow_blur_radius: f32, 190 | 191 | /// Shadow's offset in Y axis 192 | #[structopt(long, value_name = "Y", default_value = "0")] 193 | pub shadow_offset_y: i32, 194 | 195 | /// Shadow's offset in X axis 196 | #[structopt(long, value_name = "X", default_value = "0")] 197 | pub shadow_offset_x: i32, 198 | 199 | /// Tab width 200 | #[structopt(long, value_name = "WIDTH", default_value = "4")] 201 | pub tab_width: u8, 202 | 203 | /// The syntax highlight theme. It can be a theme name or path to a .tmTheme file. 204 | #[structopt(long, value_name = "THEME", default_value = "Dracula")] 205 | pub theme: String, 206 | 207 | /// Copy the output image to clipboard. 208 | #[structopt(short = "c", long)] 209 | pub to_clipboard: bool, 210 | // Draw a custom text on the bottom right corner 211 | // #[structopt(long)] 212 | // watermark: Option, 213 | /// build syntax definition and theme cache 214 | #[structopt(long, value_name = "OUTPUT_DIR")] 215 | pub build_cache: Option>, 216 | } 217 | 218 | impl Config { 219 | pub fn get_source_code<'a>( 220 | &self, 221 | ps: &'a SyntaxSet, 222 | ) -> Result<(&'a SyntaxReference, String), Error> { 223 | let possible_language = self.language.as_ref().map(|language| { 224 | ps.find_syntax_by_token(language) 225 | .ok_or_else(|| format_err!("Unsupported language: {}", language)) 226 | }); 227 | 228 | if self.from_clipboard { 229 | let mut ctx = ClipboardContext::new() 230 | .map_err(|e| format_err!("failed to access clipboard: {}", e))?; 231 | let code = ctx 232 | .get_contents() 233 | .map_err(|e| format_err!("failed to access clipboard: {}", e))?; 234 | 235 | let language = possible_language.unwrap_or_else(|| { 236 | ps.find_syntax_by_first_line(&code) 237 | .ok_or_else(|| format_err!("Failed to detect the language")) 238 | })?; 239 | 240 | return Ok((language, code)); 241 | } 242 | 243 | if let Some(path) = &self.file { 244 | let mut s = String::new(); 245 | let mut file = File::open(path)?; 246 | file.read_to_string(&mut s)?; 247 | 248 | let language = possible_language.unwrap_or_else(|| { 249 | ps.find_syntax_for_file(path)? 250 | .ok_or_else(|| format_err!("Failed to detect the language")) 251 | })?; 252 | 253 | return Ok((language, s)); 254 | } 255 | 256 | let mut stdin = stdin(); 257 | let mut s = String::new(); 258 | stdin.read_to_string(&mut s)?; 259 | 260 | let language = possible_language.unwrap_or_else(|| { 261 | ps.find_syntax_by_first_line(&s) 262 | .ok_or_else(|| format_err!("Failed to detect the language")) 263 | })?; 264 | 265 | Ok((language, s)) 266 | } 267 | 268 | pub fn theme(&self, ts: &ThemeSet) -> Result { 269 | if let Some(theme) = ts.themes.get(&self.theme) { 270 | Ok(theme.clone()) 271 | } else { 272 | ThemeSet::get_theme(&self.theme) 273 | .context(format!("Cannot load the theme: {}", self.theme)) 274 | } 275 | } 276 | 277 | pub fn get_formatter(&self) -> Result, Error> { 278 | let formatter = ImageFormatterBuilder::new() 279 | .line_pad(self.line_pad) 280 | .window_controls(!self.no_window_controls) 281 | .window_title(self.window_title.clone()) 282 | .line_number(!self.no_line_number) 283 | .font(self.font.clone().unwrap_or_default()) 284 | .round_corner(!self.no_round_corner) 285 | .shadow_adder(self.get_shadow_adder()?) 286 | .tab_width(self.tab_width) 287 | .highlight_lines(self.highlight_lines.clone().unwrap_or_default()) 288 | .line_offset(self.line_offset) 289 | .code_pad_right(self.code_pad_right); 290 | 291 | Ok(formatter.build()?) 292 | } 293 | 294 | pub fn get_shadow_adder(&self) -> Result { 295 | Ok(ShadowAdder::new() 296 | .background(match &self.background_image { 297 | Some(path) => Background::Image(image::open(path)?.to_rgba8()), 298 | None => Background::Solid(self.background), 299 | }) 300 | .shadow_color(self.shadow_color) 301 | .blur_radius(self.shadow_blur_radius) 302 | .pad_horiz(self.pad_horiz) 303 | .pad_vert(self.pad_vert) 304 | .offset_x(self.shadow_offset_x) 305 | .offset_y(self.shadow_offset_y)) 306 | } 307 | 308 | pub fn get_expanded_output(&self) -> Option { 309 | let need_expand = self.output.as_ref().map(|p| p.starts_with("~")) == Some(true); 310 | 311 | if let (Ok(home_dir), true) = (std::env::var("HOME"), need_expand) { 312 | self.output 313 | .as_ref() 314 | .map(|p| p.to_string_lossy().replacen('~', &home_dir, 1).into()) 315 | } else { 316 | self.output.clone() 317 | } 318 | } 319 | } 320 | -------------------------------------------------------------------------------- /src/bin/silicon/main.rs: -------------------------------------------------------------------------------- 1 | #[macro_use] 2 | extern crate anyhow; 3 | 4 | use anyhow::Error; 5 | use image::DynamicImage; 6 | use std::env; 7 | use structopt::StructOpt; 8 | use syntect::easy::HighlightLines; 9 | use syntect::util::LinesWithEndings; 10 | #[cfg(target_os = "windows")] 11 | use { 12 | clipboard_win::{formats, Clipboard, Setter}, 13 | image::ImageOutputFormat, 14 | }; 15 | #[cfg(target_os = "macos")] 16 | use {image::ImageOutputFormat, pasteboard::Pasteboard}; 17 | 18 | #[cfg(target_os = "linux")] 19 | use {image::ImageOutputFormat, std::process::Command}; 20 | 21 | mod config; 22 | use crate::config::{config_file, get_args_from_config_file, Config}; 23 | use silicon::assets::HighlightingAssets; 24 | use silicon::directories::PROJECT_DIRS; 25 | 26 | #[cfg(target_os = "linux")] 27 | pub fn dump_image_to_clipboard(image: &DynamicImage) -> Result<(), Error> { 28 | use std::io::{Cursor, Write}; 29 | 30 | match std::env::var(r#"XDG_SESSION_TYPE"#).ok() { 31 | Some(x) if x == "wayland" => { 32 | let mut command = Command::new("wl-copy") 33 | .args(["--type", "image/png"]) 34 | .stdin(std::process::Stdio::piped()) 35 | .spawn()?; 36 | 37 | let mut cursor = Cursor::new(Vec::new()); 38 | image.write_to(&mut cursor, ImageOutputFormat::Png)?; 39 | 40 | { 41 | let stdin = command.stdin.as_mut().unwrap(); 42 | stdin.write_all(cursor.get_ref())?; 43 | } 44 | 45 | command 46 | .wait() 47 | .map_err(|e| format_err!("Failed to copy image to clipboard: {}", e))?; 48 | } 49 | _ => { 50 | let mut temp = tempfile::NamedTempFile::new()?; 51 | image.write_to(&mut temp, ImageOutputFormat::Png)?; 52 | 53 | Command::new(r#"xclip"#) 54 | .args([ 55 | "-sel", 56 | "clip", 57 | "-t", 58 | "image/png", 59 | temp.path().to_str().unwrap(), 60 | ]) 61 | .status() 62 | .map_err(|e| format_err!("Failed to copy image to clipboard: {} (Tip: do you have xclip installed ?)", e))?; 63 | } 64 | }; 65 | Ok(()) 66 | } 67 | 68 | #[cfg(target_os = "macos")] 69 | pub fn dump_image_to_clipboard(image: &DynamicImage) -> Result<(), Error> { 70 | let mut temp = tempfile::NamedTempFile::new()?; 71 | image.write_to(&mut temp, ImageOutputFormat::Png)?; 72 | unsafe { 73 | Pasteboard::Image.copy(temp.path().to_str().unwrap()); 74 | } 75 | Ok(()) 76 | } 77 | 78 | #[cfg(target_os = "windows")] 79 | pub fn dump_image_to_clipboard(image: &DynamicImage) -> Result<(), Error> { 80 | let mut temp = std::io::Cursor::new(Vec::new()); 81 | 82 | // Convert the image to RGB without alpha because the clipboard 83 | // of windows doesn't support it. 84 | let image = DynamicImage::ImageRgb8(image.to_rgb8()); 85 | 86 | image.write_to(&mut temp, ImageOutputFormat::Bmp)?; 87 | 88 | let _clip = 89 | Clipboard::new_attempts(10).map_err(|e| format_err!("Couldn't open clipboard: {}", e))?; 90 | 91 | formats::Bitmap 92 | .write_clipboard(temp.get_ref()) 93 | .map_err(|e| format_err!("Failed copy image: {}", e))?; 94 | Ok(()) 95 | } 96 | 97 | #[cfg(not(any(target_os = "linux", target_os = "macos", target_os = "windows")))] 98 | pub fn dump_image_to_clipboard(_image: &DynamicImage) -> Result<(), Error> { 99 | Err(format_err!( 100 | "This feature hasn't been implemented for your system" 101 | )) 102 | } 103 | 104 | fn run() -> Result<(), Error> { 105 | let mut args = get_args_from_config_file(); 106 | let mut args_cli = std::env::args_os(); 107 | args.insert(0, args_cli.next().unwrap()); 108 | args.extend(args_cli); 109 | let config: Config = Config::from_iter(args); 110 | 111 | let ha = HighlightingAssets::new(); 112 | let (ps, ts) = (ha.syntax_set, ha.theme_set); 113 | 114 | if let Some(path) = config.build_cache { 115 | let mut ha = HighlightingAssets::new(); 116 | ha.add_from_folder(env::current_dir()?)?; 117 | if let Some(path) = path { 118 | ha.dump_to_file(path)?; 119 | } else { 120 | ha.dump_to_file(PROJECT_DIRS.cache_dir())?; 121 | } 122 | return Ok(()); 123 | } else if config.list_themes { 124 | for i in ts.themes.keys() { 125 | println!("{}", i); 126 | } 127 | return Ok(()); 128 | } else if config.list_fonts { 129 | let source = font_kit::source::SystemSource::new(); 130 | for font in source.all_families().unwrap_or_default() { 131 | println!("{}", font); 132 | } 133 | return Ok(()); 134 | } else if config.config_file { 135 | println!("{}", config_file().to_string_lossy()); 136 | return Ok(()); 137 | } 138 | 139 | let (syntax, code) = config.get_source_code(&ps)?; 140 | 141 | let theme = config.theme(&ts)?; 142 | 143 | let mut h = HighlightLines::new(syntax, &theme); 144 | let highlight = LinesWithEndings::from(&code) 145 | .map(|line| h.highlight_line(line, &ps)) 146 | .collect::, _>>()?; 147 | 148 | let mut formatter = config.get_formatter()?; 149 | 150 | let image = formatter.format(&highlight, &theme); 151 | let image = DynamicImage::ImageRgba8(image); 152 | 153 | if config.to_clipboard { 154 | dump_image_to_clipboard(&image)?; 155 | } else { 156 | let path = config.get_expanded_output().unwrap(); 157 | image 158 | .save(&path) 159 | .map_err(|e| format_err!("Failed to save image to {}: {}", path.display(), e))?; 160 | } 161 | 162 | Ok(()) 163 | } 164 | 165 | fn main() { 166 | env_logger::init(); 167 | 168 | if let Err(e) = run() { 169 | eprintln!("[error] {}", e); 170 | } 171 | } 172 | -------------------------------------------------------------------------------- /src/blur.rs: -------------------------------------------------------------------------------- 1 | //! Fast (linear time) implementation of the Gaussian Blur algorithm in Rust 2 | //! 3 | //! This file is originally from https://github.com/fschutt/fastblur 4 | //! Edited by aloxaf to process RgbaImage 5 | 6 | use std::cmp::min; 7 | 8 | use image::RgbaImage; 9 | use rayon::prelude::*; 10 | 11 | #[derive(Copy, Clone)] 12 | struct SharedMutPtr(*mut [[u8; 4]]); 13 | 14 | unsafe impl Sync for SharedMutPtr {} 15 | 16 | impl SharedMutPtr { 17 | #[allow(clippy::mut_from_ref)] 18 | unsafe fn get(&self) -> &mut [[u8; 4]] { 19 | &mut *self.0 20 | } 21 | } 22 | 23 | pub fn gaussian_blur(image: RgbaImage, sigma: f32) -> RgbaImage { 24 | let (width, height) = image.dimensions(); 25 | let mut raw = image.into_raw(); 26 | let len = raw.len(); 27 | 28 | // fastblur::gaussian_blur only accepts Vec<[u8; 4]> 29 | unsafe { 30 | raw.set_len(len / 4); 31 | 32 | let ptr = &mut *(&mut raw as *mut Vec as *mut Vec<[u8; 4]>); 33 | gaussian_blur_impl(ptr, width as usize, height as usize, sigma); 34 | 35 | raw.set_len(len); 36 | } 37 | 38 | RgbaImage::from_raw(width, height, raw).unwrap() 39 | } 40 | 41 | fn gaussian_blur_impl(data: &mut [[u8; 4]], width: usize, height: usize, blur_radius: f32) { 42 | let bxs = create_box_gauss(blur_radius, 3); 43 | let mut backbuf = data.to_vec(); 44 | 45 | box_blur( 46 | &mut backbuf, 47 | data, 48 | width, 49 | height, 50 | ((bxs[0] - 1) / 2) as usize, 51 | ); 52 | box_blur( 53 | &mut backbuf, 54 | data, 55 | width, 56 | height, 57 | ((bxs[1] - 1) / 2) as usize, 58 | ); 59 | box_blur( 60 | &mut backbuf, 61 | data, 62 | width, 63 | height, 64 | ((bxs[2] - 1) / 2) as usize, 65 | ); 66 | } 67 | 68 | #[inline] 69 | fn create_box_gauss(sigma: f32, n: usize) -> Vec { 70 | let n_float = n as f32; 71 | 72 | // Ideal averaging filter width 73 | let w_ideal = (12.0 * sigma * sigma / n_float).sqrt() + 1.0; 74 | let mut wl: i32 = w_ideal.floor() as i32; 75 | 76 | if wl % 2 == 0 { 77 | wl -= 1; 78 | }; 79 | 80 | let wu = wl + 2; 81 | 82 | let wl_float = wl as f32; 83 | let m_ideal = (12.0 * sigma * sigma 84 | - n_float * wl_float * wl_float 85 | - 4.0 * n_float * wl_float 86 | - 3.0 * n_float) 87 | / (-4.0 * wl_float - 4.0); 88 | let m: usize = m_ideal.round() as usize; 89 | 90 | let mut sizes = Vec::::new(); 91 | 92 | for i in 0..n { 93 | if i < m { 94 | sizes.push(wl); 95 | } else { 96 | sizes.push(wu); 97 | } 98 | } 99 | 100 | sizes 101 | } 102 | 103 | /// Needs 2x the same image 104 | #[inline] 105 | fn box_blur( 106 | backbuf: &mut [[u8; 4]], 107 | frontbuf: &mut [[u8; 4]], 108 | width: usize, 109 | height: usize, 110 | blur_radius: usize, 111 | ) { 112 | box_blur_horz(backbuf, frontbuf, width, height, blur_radius); 113 | box_blur_vert(frontbuf, backbuf, width, height, blur_radius); 114 | } 115 | 116 | #[inline] 117 | fn box_blur_vert( 118 | backbuf: &[[u8; 4]], 119 | frontbuf: &mut [[u8; 4]], 120 | width: usize, 121 | height: usize, 122 | blur_radius: usize, 123 | ) { 124 | let iarr = 1.0 / (blur_radius + blur_radius + 1) as f32; 125 | 126 | let frontbuf = SharedMutPtr(frontbuf as *mut [[u8; 4]]); 127 | (0..width).into_par_iter().for_each(|i| { 128 | let col_start = i; //inclusive 129 | let col_end = i + width * (height - 1); //inclusive 130 | let mut ti: usize = i; 131 | let mut li: usize = ti; 132 | let mut ri: usize = ti + blur_radius * width; 133 | 134 | let fv: [u8; 4] = backbuf[col_start]; 135 | let lv: [u8; 4] = backbuf[col_end]; 136 | 137 | let mut val_r: isize = (blur_radius as isize + 1) * isize::from(fv[0]); 138 | let mut val_g: isize = (blur_radius as isize + 1) * isize::from(fv[1]); 139 | let mut val_b: isize = (blur_radius as isize + 1) * isize::from(fv[2]); 140 | let mut val_a: isize = (blur_radius as isize + 1) * isize::from(fv[3]); 141 | 142 | // Get the pixel at the specified index, or the first pixel of the column 143 | // if the index is beyond the top edge of the image 144 | let get_top = |i: usize| { 145 | if i < col_start { 146 | fv 147 | } else { 148 | backbuf[i] 149 | } 150 | }; 151 | 152 | // Get the pixel at the specified index, or the last pixel of the column 153 | // if the index is beyond the bottom edge of the image 154 | let get_bottom = |i: usize| { 155 | if i > col_end { 156 | lv 157 | } else { 158 | backbuf[i] 159 | } 160 | }; 161 | 162 | for j in 0..min(blur_radius, height) { 163 | let bb = backbuf[ti + j * width]; 164 | val_r += isize::from(bb[0]); 165 | val_g += isize::from(bb[1]); 166 | val_b += isize::from(bb[2]); 167 | val_a += isize::from(bb[3]); 168 | } 169 | if blur_radius > height { 170 | val_r += (blur_radius - height) as isize * isize::from(lv[0]); 171 | val_g += (blur_radius - height) as isize * isize::from(lv[1]); 172 | val_b += (blur_radius - height) as isize * isize::from(lv[2]); 173 | val_a += (blur_radius - height) as isize * isize::from(lv[3]); 174 | } 175 | 176 | for _ in 0..min(height, blur_radius + 1) { 177 | let bb = get_bottom(ri); 178 | ri += width; 179 | val_r += isize::from(bb[0]) - isize::from(fv[0]); 180 | val_g += isize::from(bb[1]) - isize::from(fv[1]); 181 | val_b += isize::from(bb[2]) - isize::from(fv[2]); 182 | val_a += isize::from(bb[3]) - isize::from(fv[3]); 183 | 184 | let frontbuf = unsafe { frontbuf.get() }; 185 | frontbuf[ti] = [ 186 | round(val_r as f32 * iarr) as u8, 187 | round(val_g as f32 * iarr) as u8, 188 | round(val_b as f32 * iarr) as u8, 189 | round(val_a as f32 * iarr) as u8, 190 | ]; 191 | ti += width; 192 | } 193 | 194 | if height > blur_radius { 195 | // otherwise `(height - blur_radius)` will underflow 196 | for _ in (blur_radius + 1)..(height - blur_radius) { 197 | let bb1 = backbuf[ri]; 198 | ri += width; 199 | let bb2 = backbuf[li]; 200 | li += width; 201 | 202 | val_r += isize::from(bb1[0]) - isize::from(bb2[0]); 203 | val_g += isize::from(bb1[1]) - isize::from(bb2[1]); 204 | val_b += isize::from(bb1[2]) - isize::from(bb2[2]); 205 | val_a += isize::from(bb1[3]) - isize::from(bb2[3]); 206 | 207 | let frontbuf = unsafe { frontbuf.get() }; 208 | frontbuf[ti] = [ 209 | round(val_r as f32 * iarr) as u8, 210 | round(val_g as f32 * iarr) as u8, 211 | round(val_b as f32 * iarr) as u8, 212 | round(val_a as f32 * iarr) as u8, 213 | ]; 214 | ti += width; 215 | } 216 | 217 | for _ in 0..min(height - blur_radius - 1, blur_radius) { 218 | let bb = get_top(li); 219 | li += width; 220 | 221 | val_r += isize::from(lv[0]) - isize::from(bb[0]); 222 | val_g += isize::from(lv[1]) - isize::from(bb[1]); 223 | val_b += isize::from(lv[2]) - isize::from(bb[2]); 224 | val_a += isize::from(lv[3]) - isize::from(bb[3]); 225 | 226 | let frontbuf = unsafe { frontbuf.get() }; 227 | frontbuf[ti] = [ 228 | round(val_r as f32 * iarr) as u8, 229 | round(val_g as f32 * iarr) as u8, 230 | round(val_b as f32 * iarr) as u8, 231 | round(val_a as f32 * iarr) as u8, 232 | ]; 233 | ti += width; 234 | } 235 | } 236 | }); 237 | } 238 | 239 | #[inline] 240 | fn box_blur_horz( 241 | backbuf: &[[u8; 4]], 242 | frontbuf: &mut [[u8; 4]], 243 | width: usize, 244 | height: usize, 245 | blur_radius: usize, 246 | ) { 247 | let iarr = 1.0 / (blur_radius + blur_radius + 1) as f32; 248 | 249 | let frontbuf = SharedMutPtr(frontbuf as *mut [[u8; 4]]); 250 | (0..height).into_par_iter().for_each(|i| { 251 | let row_start: usize = i * width; // inclusive 252 | let row_end: usize = (i + 1) * width - 1; // inclusive 253 | let mut ti: usize = i * width; // VERTICAL: $i; 254 | let mut li: usize = ti; 255 | let mut ri: usize = ti + blur_radius; 256 | 257 | let fv: [u8; 4] = backbuf[row_start]; 258 | let lv: [u8; 4] = backbuf[row_end]; // VERTICAL: $backbuf[ti + $width - 1]; 259 | 260 | let mut val_r: isize = (blur_radius as isize + 1) * isize::from(fv[0]); 261 | let mut val_g: isize = (blur_radius as isize + 1) * isize::from(fv[1]); 262 | let mut val_b: isize = (blur_radius as isize + 1) * isize::from(fv[2]); 263 | let mut val_a: isize = (blur_radius as isize + 1) * isize::from(fv[3]); 264 | 265 | // Get the pixel at the specified index, or the first pixel of the row 266 | // if the index is beyond the left edge of the image 267 | let get_left = |i: usize| { 268 | if i < row_start { 269 | fv 270 | } else { 271 | backbuf[i] 272 | } 273 | }; 274 | 275 | // Get the pixel at the specified index, or the last pixel of the row 276 | // if the index is beyond the right edge of the image 277 | let get_right = |i: usize| { 278 | if i > row_end { 279 | lv 280 | } else { 281 | backbuf[i] 282 | } 283 | }; 284 | 285 | for j in 0..min(blur_radius, width) { 286 | let bb = backbuf[ti + j]; // VERTICAL: ti + j * width 287 | val_r += isize::from(bb[0]); 288 | val_g += isize::from(bb[1]); 289 | val_b += isize::from(bb[2]); 290 | val_a += isize::from(bb[3]); 291 | } 292 | if blur_radius > width { 293 | val_r += (blur_radius - height) as isize * isize::from(lv[0]); 294 | val_g += (blur_radius - height) as isize * isize::from(lv[1]); 295 | val_b += (blur_radius - height) as isize * isize::from(lv[2]); 296 | val_a += (blur_radius - height) as isize * isize::from(lv[3]); 297 | } 298 | 299 | // Process the left side where we need pixels from beyond the left edge 300 | for _ in 0..min(width, blur_radius + 1) { 301 | let bb = get_right(ri); 302 | ri += 1; 303 | val_r += isize::from(bb[0]) - isize::from(fv[0]); 304 | val_g += isize::from(bb[1]) - isize::from(fv[1]); 305 | val_b += isize::from(bb[2]) - isize::from(fv[2]); 306 | val_a += isize::from(bb[3]) - isize::from(fv[3]); 307 | 308 | let frontbuf = unsafe { frontbuf.get() }; 309 | frontbuf[ti] = [ 310 | round(val_r as f32 * iarr) as u8, 311 | round(val_g as f32 * iarr) as u8, 312 | round(val_b as f32 * iarr) as u8, 313 | round(val_a as f32 * iarr) as u8, 314 | ]; 315 | ti += 1; // VERTICAL : ti += width, same with the other areas 316 | } 317 | 318 | if width > blur_radius { 319 | // otherwise `(width - blur_radius)` will underflow 320 | // Process the middle where we know we won't bump into borders 321 | // without the extra indirection of get_left/get_right. This is faster. 322 | for _ in (blur_radius + 1)..(width - blur_radius) { 323 | let bb1 = backbuf[ri]; 324 | ri += 1; 325 | let bb2 = backbuf[li]; 326 | li += 1; 327 | 328 | val_r += isize::from(bb1[0]) - isize::from(bb2[0]); 329 | val_g += isize::from(bb1[1]) - isize::from(bb2[1]); 330 | val_b += isize::from(bb1[2]) - isize::from(bb2[2]); 331 | val_a += isize::from(bb1[3]) - isize::from(bb2[3]); 332 | 333 | let frontbuf = unsafe { frontbuf.get() }; 334 | frontbuf[ti] = [ 335 | round(val_r as f32 * iarr) as u8, 336 | round(val_g as f32 * iarr) as u8, 337 | round(val_b as f32 * iarr) as u8, 338 | round(val_a as f32 * iarr) as u8, 339 | ]; 340 | ti += 1; 341 | } 342 | 343 | // Process the right side where we need pixels from beyond the right edge 344 | for _ in 0..min(width - blur_radius - 1, blur_radius) { 345 | let bb = get_left(li); 346 | li += 1; 347 | 348 | val_r += isize::from(lv[0]) - isize::from(bb[0]); 349 | val_g += isize::from(lv[1]) - isize::from(bb[1]); 350 | val_b += isize::from(lv[2]) - isize::from(bb[2]); 351 | val_a += isize::from(lv[3]) - isize::from(bb[3]); 352 | 353 | let frontbuf = unsafe { frontbuf.get() }; 354 | frontbuf[ti] = [ 355 | round(val_r as f32 * iarr) as u8, 356 | round(val_g as f32 * iarr) as u8, 357 | round(val_b as f32 * iarr) as u8, 358 | round(val_a as f32 * iarr) as u8, 359 | ]; 360 | ti += 1; 361 | } 362 | } 363 | }); 364 | } 365 | 366 | #[inline] 367 | /// Fast rounding for x <= 2^23. 368 | /// This is orders of magnitude faster than built-in rounding intrinsic. 369 | /// 370 | /// Source: https://stackoverflow.com/a/42386149/585725 371 | fn round(mut x: f32) -> f32 { 372 | x += 12_582_912.0; 373 | x -= 12_582_912.0; 374 | x 375 | } 376 | -------------------------------------------------------------------------------- /src/directories.rs: -------------------------------------------------------------------------------- 1 | use lazy_static::lazy_static; 2 | use std::env; 3 | use std::fs::create_dir_all; 4 | use std::path::{Path, PathBuf}; 5 | 6 | pub struct SiliconProjectDirs { 7 | cache_dir: PathBuf, 8 | config_dir: PathBuf, 9 | } 10 | 11 | impl SiliconProjectDirs { 12 | fn new() -> Option { 13 | let cache_dir = Self::get_cache_dir()?; 14 | 15 | #[cfg(target_os = "macos")] 16 | let config_dir_op = env::var_os("XDG_CONFIG_HOME") 17 | .map(PathBuf::from) 18 | .filter(|p| p.is_absolute()) 19 | .or_else(|| dirs::home_dir().map(|d| d.join(".config"))); 20 | 21 | #[cfg(not(target_os = "macos"))] 22 | let config_dir_op = dirs::config_dir(); 23 | 24 | let config_dir = config_dir_op.map(|d| d.join("silicon"))?; 25 | 26 | create_dir_all(&config_dir).expect("cannot create config dir"); 27 | create_dir_all(&cache_dir).expect("cannot create cache dir"); 28 | 29 | Some(Self { 30 | cache_dir, 31 | config_dir, 32 | }) 33 | } 34 | 35 | fn get_cache_dir() -> Option { 36 | // on all OS prefer SILICON_CACHE_PATH if set 37 | let cache_dir_op = env::var_os("SILICON_CACHE_PATH").map(PathBuf::from); 38 | if cache_dir_op.is_some() { 39 | return cache_dir_op; 40 | } 41 | 42 | #[cfg(target_os = "macos")] 43 | let cache_dir_op = env::var_os("XDG_CACHE_HOME") 44 | .map(PathBuf::from) 45 | .filter(|p| p.is_absolute()) 46 | .or_else(|| dirs::home_dir().map(|d| d.join(".cache"))); 47 | 48 | #[cfg(not(target_os = "macos"))] 49 | let cache_dir_op = dirs::cache_dir(); 50 | 51 | cache_dir_op.map(|d| d.join("silicon")) 52 | } 53 | 54 | pub fn cache_dir(&self) -> &Path { 55 | &self.cache_dir 56 | } 57 | 58 | pub fn config_dir(&self) -> &Path { 59 | &self.config_dir 60 | } 61 | } 62 | 63 | lazy_static! { 64 | pub static ref PROJECT_DIRS: SiliconProjectDirs = 65 | SiliconProjectDirs::new().expect("Could not get home directory"); 66 | } 67 | -------------------------------------------------------------------------------- /src/error.rs: -------------------------------------------------------------------------------- 1 | use font_kit::error::{FontLoadingError, SelectionError}; 2 | use std::error::Error; 3 | use std::fmt::{self, Display}; 4 | use std::num::ParseIntError; 5 | 6 | #[derive(Debug)] 7 | pub enum FontError { 8 | SelectionError(SelectionError), 9 | FontLoadingError(FontLoadingError), 10 | } 11 | 12 | impl Error for FontError {} 13 | 14 | impl Display for FontError { 15 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 16 | match self { 17 | FontError::SelectionError(e) => write!(f, "Font error: {}", e), 18 | FontError::FontLoadingError(e) => write!(f, "Font error: {}", e), 19 | } 20 | } 21 | } 22 | 23 | impl From for FontError { 24 | fn from(e: SelectionError) -> Self { 25 | FontError::SelectionError(e) 26 | } 27 | } 28 | 29 | impl From for FontError { 30 | fn from(e: FontLoadingError) -> Self { 31 | FontError::FontLoadingError(e) 32 | } 33 | } 34 | 35 | #[derive(Debug, Eq, PartialEq)] 36 | pub enum ParseColorError { 37 | InvalidLength, 38 | InvalidDigit, 39 | } 40 | 41 | impl Error for ParseColorError {} 42 | 43 | impl Display for ParseColorError { 44 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 45 | match self { 46 | ParseColorError::InvalidDigit => write!(f, "Invalid digit"), 47 | ParseColorError::InvalidLength => write!(f, "Invalid length"), 48 | } 49 | } 50 | } 51 | 52 | impl From for ParseColorError { 53 | fn from(_e: ParseIntError) -> Self { 54 | ParseColorError::InvalidDigit 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/font.rs: -------------------------------------------------------------------------------- 1 | //! A basic font manager with fallback support 2 | //! 3 | //! # Example 4 | //! 5 | //! ```rust 6 | //! use image::{RgbImage, Rgb}; 7 | //! use silicon::font::{FontCollection, FontStyle}; 8 | //! 9 | //! let mut image = RgbImage::new(250, 100); 10 | //! let font = FontCollection::new(&[("Hack", 27.0), ("FiraCode", 27.0)]).unwrap(); 11 | //! 12 | //! font.draw_text_mut(&mut image, Rgb([255, 0, 0]), 0, 0, FontStyle::REGULAR, "Hello, world"); 13 | //! ``` 14 | use crate::error::FontError; 15 | #[cfg(feature = "harfbuzz")] 16 | use crate::hb_wrapper::{feature_from_tag, HBBuffer, HBFont}; 17 | use anyhow::Result; 18 | use conv::ValueInto; 19 | use font_kit::canvas::{Canvas, Format, RasterizationOptions}; 20 | use font_kit::font::Font; 21 | use font_kit::hinting::HintingOptions; 22 | use font_kit::properties::{Properties, Style, Weight}; 23 | use font_kit::source::SystemSource; 24 | use image::{GenericImage, Pixel, Rgba, RgbaImage}; 25 | use imageproc::definitions::Clamp; 26 | use imageproc::pixelops::weighted_sum; 27 | use pathfinder_geometry::transform2d::Transform2F; 28 | use std::collections::HashMap; 29 | use std::sync::Arc; 30 | use syntect::highlighting; 31 | 32 | /// a single line text drawer 33 | pub trait TextLineDrawer { 34 | /// get the height of the text 35 | fn height(&mut self, text: &str) -> u32; 36 | /// get the width of the text 37 | fn width(&mut self, text: &str) -> u32; 38 | /// draw the text 39 | fn draw_text( 40 | &mut self, 41 | image: &mut RgbaImage, 42 | color: Rgba, 43 | x: u32, 44 | y: u32, 45 | font_style: FontStyle, 46 | text: &str, 47 | ); 48 | } 49 | 50 | impl TextLineDrawer for FontCollection { 51 | fn height(&mut self, _text: &str) -> u32 { 52 | self.get_font_height() 53 | } 54 | 55 | fn width(&mut self, text: &str) -> u32 { 56 | self.layout(text, REGULAR).1 57 | } 58 | 59 | fn draw_text( 60 | &mut self, 61 | image: &mut RgbaImage, 62 | color: Rgba, 63 | x: u32, 64 | y: u32, 65 | font_style: FontStyle, 66 | text: &str, 67 | ) { 68 | self.draw_text_mut(image, color, x, y, font_style, text); 69 | } 70 | } 71 | 72 | /// Font style 73 | #[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)] 74 | pub enum FontStyle { 75 | REGULAR, 76 | ITALIC, 77 | BOLD, 78 | BOLDITALIC, 79 | } 80 | 81 | impl From for FontStyle { 82 | fn from(style: highlighting::FontStyle) -> Self { 83 | if style.contains(highlighting::FontStyle::BOLD) { 84 | if style.contains(highlighting::FontStyle::ITALIC) { 85 | BOLDITALIC 86 | } else { 87 | BOLD 88 | } 89 | } else if style.contains(highlighting::FontStyle::ITALIC) { 90 | ITALIC 91 | } else { 92 | REGULAR 93 | } 94 | } 95 | } 96 | 97 | use pathfinder_geometry::rect::RectI; 98 | use pathfinder_geometry::vector::Vector2I; 99 | use FontStyle::*; 100 | 101 | /// A single font with specific size 102 | #[derive(Debug)] 103 | pub struct ImageFont { 104 | pub fonts: HashMap, 105 | pub size: f32, 106 | } 107 | 108 | impl Default for ImageFont { 109 | /// It will use Hack font (size: 26.0) by default 110 | fn default() -> Self { 111 | let l = vec![ 112 | ( 113 | REGULAR, 114 | include_bytes!("../assets/fonts/Hack-Regular.ttf").to_vec(), 115 | ), 116 | ( 117 | ITALIC, 118 | include_bytes!("../assets/fonts/Hack-Italic.ttf").to_vec(), 119 | ), 120 | ( 121 | BOLD, 122 | include_bytes!("../assets/fonts/Hack-Bold.ttf").to_vec(), 123 | ), 124 | ( 125 | BOLDITALIC, 126 | include_bytes!("../assets/fonts/Hack-BoldItalic.ttf").to_vec(), 127 | ), 128 | ]; 129 | let mut fonts = HashMap::new(); 130 | for (style, bytes) in l { 131 | let font = Font::from_bytes(Arc::new(bytes), 0).unwrap(); 132 | fonts.insert(style, font); 133 | } 134 | 135 | Self { fonts, size: 26.0 } 136 | } 137 | } 138 | 139 | impl ImageFont { 140 | pub fn new(name: &str, size: f32) -> Result { 141 | // Silicon already contains Hack font 142 | if name == "Hack" { 143 | let font = ImageFont { 144 | size, 145 | ..Default::default() 146 | }; 147 | return Ok(font); 148 | } 149 | 150 | let mut fonts = HashMap::new(); 151 | 152 | let family = SystemSource::new().select_family_by_name(name)?; 153 | let handles = family.fonts(); 154 | 155 | debug!("{:?}", handles); 156 | 157 | for handle in handles { 158 | let font = handle.load()?; 159 | 160 | let properties: Properties = font.properties(); 161 | 162 | debug!("{:?} - {:?}", font, properties); 163 | 164 | // cannot use match because `Weight` didn't derive `Eq` 165 | match properties.style { 166 | Style::Normal => { 167 | if properties.weight == Weight::NORMAL { 168 | fonts.insert(REGULAR, font); 169 | } else if properties.weight == Weight::BOLD { 170 | fonts.insert(BOLD, font); 171 | } else if properties.weight == Weight::MEDIUM && !fonts.contains_key(®ULAR) { 172 | fonts.insert(REGULAR, font); 173 | } 174 | } 175 | Style::Italic => { 176 | if properties.weight == Weight::NORMAL { 177 | fonts.insert(ITALIC, font); 178 | } else if properties.weight == Weight::BOLD { 179 | fonts.insert(BOLDITALIC, font); 180 | } else if properties.weight == Weight::MEDIUM && !fonts.contains_key(&ITALIC) { 181 | fonts.insert(ITALIC, font); 182 | } 183 | } 184 | _ => (), 185 | } 186 | } 187 | 188 | Ok(Self { fonts, size }) 189 | } 190 | 191 | /// Get a font by style. If there is no such a font, it will return the REGULAR font. 192 | pub fn get_by_style(&self, style: FontStyle) -> &Font { 193 | self.fonts 194 | .get(&style) 195 | .unwrap_or_else(|| self.fonts.get(®ULAR).unwrap()) 196 | } 197 | 198 | /// Get the regular font 199 | pub fn get_regular(&self) -> &Font { 200 | self.fonts.get(®ULAR).unwrap() 201 | } 202 | 203 | /// Get the height of the font 204 | pub fn get_font_height(&self) -> u32 { 205 | let font = self.get_regular(); 206 | let metrics = font.metrics(); 207 | ((metrics.ascent - metrics.descent) / metrics.units_per_em as f32 * self.size).ceil() as u32 208 | } 209 | } 210 | 211 | /// A collection of font 212 | /// 213 | /// It can be used to draw text on the image. 214 | #[derive(Debug)] 215 | pub struct FontCollection { 216 | fonts: Vec, 217 | } 218 | 219 | impl Default for FontCollection { 220 | fn default() -> Self { 221 | Self { 222 | fonts: vec![ImageFont::default()], 223 | } 224 | } 225 | } 226 | 227 | impl FontCollection { 228 | /// Create a FontCollection with several fonts. 229 | pub fn new>(font_list: &[(S, f32)]) -> Result { 230 | let mut fonts = vec![]; 231 | for (name, size) in font_list { 232 | let name = name.as_ref(); 233 | match ImageFont::new(name, *size) { 234 | Ok(font) => fonts.push(font), 235 | Err(err) => eprintln!("[error] Error occurs when load font `{}`: {}", name, err), 236 | } 237 | } 238 | Ok(Self { fonts }) 239 | } 240 | 241 | fn glyph_for_char(&self, c: char, style: FontStyle) -> Option<(u32, &ImageFont, &Font)> { 242 | for font in &self.fonts { 243 | let result = font.get_by_style(style); 244 | if let Some(id) = result.glyph_for_char(c) { 245 | return Some((id, font, result)); 246 | } 247 | } 248 | eprintln!("[warning] No font found for character `{}`", c); 249 | None 250 | } 251 | 252 | /// get max height of all the fonts 253 | pub fn get_font_height(&self) -> u32 { 254 | self.fonts 255 | .iter() 256 | .map(|font| font.get_font_height()) 257 | .max() 258 | .unwrap() 259 | } 260 | 261 | #[cfg(feature = "harfbuzz")] 262 | fn shape_text(&self, font: &mut HBFont, text: &str) -> Result> { 263 | // feature tags 264 | let features = vec![ 265 | feature_from_tag("kern")?, 266 | feature_from_tag("clig")?, 267 | feature_from_tag("liga")?, 268 | ]; 269 | let mut buf = HBBuffer::new()?; 270 | buf.add_str(text); 271 | buf.guess_segments_properties(); 272 | font.shape(&buf, features.as_slice()); 273 | let hb_infos = buf.get_glyph_infos(); 274 | let mut glyph_ids = Vec::new(); 275 | for info in hb_infos.iter() { 276 | glyph_ids.push(info.codepoint); 277 | } 278 | Ok(glyph_ids) 279 | } 280 | 281 | #[cfg(feature = "harfbuzz")] 282 | fn split_by_font(&self, text: &str, style: FontStyle) -> Vec<(&ImageFont, &Font, String)> { 283 | let mut result: Vec<(&ImageFont, &Font, String)> = vec![]; 284 | for c in text.chars() { 285 | if let Some((_, imfont, font)) = self.glyph_for_char(c, style) { 286 | if result.is_empty() || !std::ptr::eq(result.last().unwrap().0, imfont) { 287 | result.push((imfont, font, String::new())); 288 | } 289 | if std::ptr::eq(result.last().unwrap().0, imfont) { 290 | result.last_mut().unwrap().2.push(c); 291 | } 292 | } 293 | } 294 | log::trace!("{:#?}", &result); 295 | result 296 | } 297 | 298 | #[cfg(feature = "harfbuzz")] 299 | fn layout(&self, text: &str, style: FontStyle) -> (Vec, u32) { 300 | let mut delta_x = 0; 301 | let height = self.get_font_height(); 302 | 303 | let mut glyphs = Vec::with_capacity(text.len()); 304 | for (imfont, font, text) in self.split_by_font(text, style) { 305 | let mut hb_font = HBFont::new(font); 306 | // apply font features especially ligature with a shape engine 307 | let shaped_glyphs = self.shape_text(&mut hb_font, &text).unwrap(); 308 | glyphs.extend(shaped_glyphs.iter().map(|id| { 309 | let raster_rect = font 310 | .raster_bounds( 311 | *id, 312 | imfont.size, 313 | Transform2F::default(), 314 | HintingOptions::None, 315 | RasterizationOptions::GrayscaleAa, 316 | ) 317 | .unwrap(); 318 | let position = Vector2I::new(delta_x as i32, height as i32) + raster_rect.origin(); 319 | delta_x += Self::get_glyph_width(font, *id, imfont.size); 320 | 321 | PositionedGlyph { 322 | id: *id, 323 | font: font.clone(), 324 | size: imfont.size, 325 | raster_rect, 326 | position, 327 | } 328 | })) 329 | } 330 | 331 | (glyphs, delta_x) 332 | } 333 | 334 | #[cfg(not(feature = "harfbuzz"))] 335 | fn layout(&self, text: &str, style: FontStyle) -> (Vec, u32) { 336 | let mut delta_x = 0; 337 | let height = self.get_font_height(); 338 | 339 | let glyphs = text 340 | .chars() 341 | .filter_map(|c| { 342 | self.glyph_for_char(c, style).map(|(id, imfont, font)| { 343 | let raster_rect = font 344 | .raster_bounds( 345 | id, 346 | imfont.size, 347 | Transform2F::default(), 348 | HintingOptions::None, 349 | RasterizationOptions::GrayscaleAa, 350 | ) 351 | .unwrap(); 352 | let position = 353 | Vector2I::new(delta_x as i32, height as i32) + raster_rect.origin(); 354 | delta_x += Self::get_glyph_width(font, id, imfont.size); 355 | 356 | PositionedGlyph { 357 | id, 358 | font: font.clone(), 359 | size: imfont.size, 360 | raster_rect, 361 | position, 362 | } 363 | }) 364 | }) 365 | .collect(); 366 | 367 | (glyphs, delta_x) 368 | } 369 | 370 | /// Get the width of the given glyph 371 | fn get_glyph_width(font: &Font, id: u32, size: f32) -> u32 { 372 | let metrics = font.metrics(); 373 | let advance = font.advance(id).unwrap(); 374 | (advance / metrics.units_per_em as f32 * size).x().ceil() as u32 375 | } 376 | 377 | /// Get the width of the given text 378 | pub fn get_text_len(&self, text: &str) -> u32 { 379 | self.layout(text, REGULAR).1 380 | } 381 | 382 | /// Draw the text to a image 383 | /// return the width of written text 384 | pub fn draw_text_mut( 385 | &self, 386 | image: &mut I, 387 | color: I::Pixel, 388 | x: u32, 389 | y: u32, 390 | style: FontStyle, 391 | text: &str, 392 | ) -> u32 393 | where 394 | I: GenericImage, 395 | ::Subpixel: ValueInto + Clamp, 396 | { 397 | let metrics = self.fonts[0].get_regular().metrics(); 398 | let offset = 399 | (metrics.descent / metrics.units_per_em as f32 * self.fonts[0].size).round() as i32; 400 | 401 | let (glyphs, width) = self.layout(text, style); 402 | 403 | for glyph in glyphs { 404 | glyph.draw(offset, |px, py, v| { 405 | if v <= std::f32::EPSILON { 406 | return; 407 | } 408 | let (x, y) = ((px + x as i32) as u32, (py + y as i32) as u32); 409 | let pixel = image.get_pixel(x, y); 410 | let weighted_color = weighted_sum(pixel, color, 1.0 - v, v); 411 | image.put_pixel(x, y, weighted_color); 412 | }) 413 | } 414 | 415 | width 416 | } 417 | } 418 | 419 | #[derive(Debug)] 420 | struct PositionedGlyph { 421 | id: u32, 422 | font: Font, 423 | size: f32, 424 | position: Vector2I, 425 | raster_rect: RectI, 426 | } 427 | 428 | impl PositionedGlyph { 429 | fn draw(&self, offset: i32, mut o: O) { 430 | let mut canvas = Canvas::new(self.raster_rect.size(), Format::A8); 431 | 432 | // don't rasterize whitespace(https://github.com/pcwalton/font-kit/issues/7) 433 | if canvas.size != Vector2I::new(0, 0) { 434 | self.font 435 | .rasterize_glyph( 436 | &mut canvas, 437 | self.id, 438 | self.size, 439 | Transform2F::from_translation(-self.raster_rect.origin().to_f32()), 440 | HintingOptions::None, 441 | RasterizationOptions::GrayscaleAa, 442 | ) 443 | .unwrap(); 444 | } 445 | 446 | for y in (0..self.raster_rect.height()).rev() { 447 | let (row_start, row_end) = 448 | (y as usize * canvas.stride, (y + 1) as usize * canvas.stride); 449 | let row = &canvas.pixels[row_start..row_end]; 450 | 451 | for x in 0..self.raster_rect.width() { 452 | let val = f32::from(row[x as usize]) / 255.0; 453 | let px = self.position.x() + x; 454 | let py = self.position.y() + y + offset; 455 | 456 | o(px, py, val); 457 | } 458 | } 459 | } 460 | } 461 | -------------------------------------------------------------------------------- /src/formatter.rs: -------------------------------------------------------------------------------- 1 | //! Format the output of syntect into an image 2 | use crate::error::FontError; 3 | use crate::font::{FontCollection, FontStyle, TextLineDrawer}; 4 | use crate::utils::*; 5 | use image::{Rgba, RgbaImage}; 6 | use syntect::highlighting::{Color, Style, Theme}; 7 | 8 | pub struct ImageFormatter { 9 | /// pad between lines 10 | /// Default: 2 11 | line_pad: u32, 12 | /// pad between code and edge of code area. 13 | /// Default: 25 14 | code_pad: u32, 15 | /// pad of top of the code area 16 | /// Default: 50 17 | code_pad_top: u32, 18 | /// pad of right of the code area 19 | /// Default: 25 20 | code_pad_right: u32, 21 | /// Title bar padding 22 | /// Default: 15 23 | title_bar_pad: u32, 24 | /// Whether to show window controls or not 25 | window_controls: bool, 26 | /// Width for window controls 27 | /// Default: 120 28 | window_controls_width: u32, 29 | /// Height for window controls 30 | /// Default: 40 31 | window_controls_height: u32, 32 | /// Window title 33 | window_title: Option, 34 | /// show line number 35 | /// Default: true 36 | line_number: bool, 37 | /// round corner 38 | /// Default: true 39 | round_corner: bool, 40 | /// pad between code and line number 41 | /// Default: 6 42 | line_number_pad: u32, 43 | /// number of columns of line number area 44 | /// Default: Auto detect 45 | line_number_chars: u32, 46 | /// font of english character, should be mono space font 47 | /// Default: Hack (builtin) 48 | font: T, 49 | /// Highlight lines 50 | highlight_lines: Vec, 51 | /// Shadow adder 52 | shadow_adder: Option, 53 | /// Tab width 54 | tab_width: u8, 55 | /// Line Offset 56 | line_offset: u32, 57 | } 58 | 59 | #[derive(Default)] 60 | pub struct ImageFormatterBuilder { 61 | /// Pad between lines 62 | line_pad: u32, 63 | /// Padding to the right of the code 64 | code_pad_right: u32, 65 | /// Show line number 66 | line_number: bool, 67 | /// Font of english character, should be mono space font 68 | font: Vec<(S, f32)>, 69 | /// Highlight lines 70 | highlight_lines: Vec, 71 | /// Whether show the window controls 72 | window_controls: bool, 73 | /// Window title 74 | window_title: Option, 75 | /// Whether round the corner of the image 76 | round_corner: bool, 77 | /// Shadow adder, 78 | shadow_adder: Option, 79 | /// Tab width 80 | tab_width: u8, 81 | /// Line Offset 82 | line_offset: u32, 83 | } 84 | 85 | // FIXME: cannot use `ImageFormatterBuilder::new().build()` bacuse cannot infer type for `S` 86 | impl + Default> ImageFormatterBuilder { 87 | pub fn new() -> Self { 88 | Self { 89 | line_pad: 2, 90 | line_number: true, 91 | window_controls: true, 92 | window_title: None, 93 | round_corner: true, 94 | tab_width: 4, 95 | ..Default::default() 96 | } 97 | } 98 | 99 | /// Whether show the line number 100 | pub fn line_number(mut self, show: bool) -> Self { 101 | self.line_number = show; 102 | self 103 | } 104 | 105 | /// Set Line offset 106 | pub fn line_offset(mut self, offset: u32) -> Self { 107 | self.line_offset = offset; 108 | self 109 | } 110 | 111 | /// Set the pad between lines 112 | pub fn line_pad(mut self, pad: u32) -> Self { 113 | self.line_pad = pad; 114 | self 115 | } 116 | 117 | /// Set the pad on the right of the screen 118 | pub fn code_pad_right(mut self, pad: u32) -> Self { 119 | self.code_pad_right = pad; 120 | self 121 | } 122 | 123 | /// Set the font 124 | pub fn font(mut self, fonts: Vec<(S, f32)>) -> Self { 125 | self.font = fonts; 126 | self 127 | } 128 | 129 | /// Whether show the windows controls 130 | pub fn window_controls(mut self, show: bool) -> Self { 131 | self.window_controls = show; 132 | self 133 | } 134 | 135 | /// Window title 136 | pub fn window_title(mut self, title: Option) -> Self { 137 | self.window_title = title; 138 | self 139 | } 140 | 141 | /// Whether round the corner 142 | pub fn round_corner(mut self, b: bool) -> Self { 143 | self.round_corner = b; 144 | self 145 | } 146 | 147 | /// Add the shadow 148 | pub fn shadow_adder(mut self, adder: ShadowAdder) -> Self { 149 | self.shadow_adder = Some(adder); 150 | self 151 | } 152 | 153 | /// Set the lines to highlight. 154 | pub fn highlight_lines(mut self, lines: Vec) -> Self { 155 | self.highlight_lines = lines; 156 | self 157 | } 158 | 159 | /// Set tab width 160 | pub fn tab_width(mut self, width: u8) -> Self { 161 | self.tab_width = width; 162 | self 163 | } 164 | 165 | pub fn build(self) -> Result, FontError> { 166 | let font = if self.font.is_empty() { 167 | FontCollection::default() 168 | } else { 169 | FontCollection::new(&self.font)? 170 | }; 171 | 172 | let title_bar = self.window_controls || self.window_title.is_some(); 173 | 174 | Ok(ImageFormatter { 175 | line_pad: self.line_pad, 176 | code_pad: 25, 177 | code_pad_top: if title_bar { 50 } else { 0 }, 178 | code_pad_right: self.code_pad_right, 179 | title_bar_pad: 15, 180 | window_controls: self.window_controls, 181 | window_controls_width: 120, 182 | window_controls_height: 40, 183 | window_title: self.window_title, 184 | line_number: self.line_number, 185 | line_number_pad: 6, 186 | line_number_chars: 0, 187 | highlight_lines: self.highlight_lines, 188 | round_corner: self.round_corner, 189 | shadow_adder: self.shadow_adder, 190 | tab_width: self.tab_width, 191 | font, 192 | line_offset: self.line_offset, 193 | }) 194 | } 195 | } 196 | 197 | struct Drawable { 198 | /// max width of the picture 199 | max_width: u32, 200 | /// max number of line of the picture 201 | max_lineno: u32, 202 | /// arguments for draw_text_mut 203 | drawables: Vec<(u32, u32, Option, FontStyle, String)>, 204 | } 205 | 206 | impl ImageFormatter { 207 | /// calculate the height of a line 208 | fn get_line_height(&mut self) -> u32 { 209 | self.font.height(" ") + self.line_pad 210 | } 211 | 212 | /// calculate the Y coordinate of a line 213 | fn get_line_y(&mut self, lineno: u32) -> u32 { 214 | lineno * self.get_line_height() + self.code_pad + self.code_pad_top 215 | } 216 | 217 | /// calculate the size of code area 218 | fn get_image_size(&mut self, max_width: u32, lineno: u32) -> (u32, u32) { 219 | ( 220 | (max_width + self.code_pad_right).max(150), 221 | self.get_line_y(lineno + 1) + self.code_pad, 222 | ) 223 | } 224 | 225 | /// Calculate where code start 226 | fn get_left_pad(&mut self) -> u32 { 227 | self.code_pad 228 | + if self.line_number { 229 | let tmp = format!("{:>width$}", 0, width = self.line_number_chars as usize); 230 | 2 * self.line_number_pad + self.font.width(&tmp) 231 | } else { 232 | 0 233 | } 234 | } 235 | 236 | /// create 237 | fn create_drawables(&mut self, v: &[Vec<(Style, &str)>]) -> Drawable { 238 | // tab should be replaced to whitespace so that it can be rendered correctly 239 | let tab = " ".repeat(self.tab_width as usize); 240 | let mut drawables = vec![]; 241 | let (mut max_width, mut max_lineno) = (0, 0); 242 | 243 | for (i, tokens) in v.iter().enumerate() { 244 | let height = self.get_line_y(i as u32); 245 | let mut width = self.get_left_pad(); 246 | 247 | for (style, text) in tokens { 248 | let text = text.trim_end_matches('\n').replace('\t', &tab); 249 | if text.is_empty() { 250 | continue; 251 | } 252 | 253 | drawables.push(( 254 | width, 255 | height, 256 | Some(style.foreground), 257 | style.font_style.into(), 258 | text.to_owned(), 259 | )); 260 | 261 | width += self.font.width(&text); 262 | 263 | max_width = max_width.max(width); 264 | } 265 | max_lineno = i as u32; 266 | } 267 | 268 | if self.window_title.is_some() { 269 | let title = self.window_title.as_ref().unwrap(); 270 | let title_width = self.font.width(title); 271 | 272 | let ctrls_offset = if self.window_controls { 273 | self.window_controls_width + self.title_bar_pad 274 | } else { 275 | 0 276 | }; 277 | let ctrls_center = self.window_controls_height / 2; 278 | 279 | drawables.push(( 280 | ctrls_offset + self.title_bar_pad, 281 | self.title_bar_pad + ctrls_center - self.font.height(" ") / 2, 282 | None, 283 | FontStyle::BOLD, 284 | title.to_string(), 285 | )); 286 | 287 | let title_bar_width = ctrls_offset + title_width + self.title_bar_pad * 2; 288 | max_width = max_width.max(title_bar_width); 289 | } 290 | 291 | Drawable { 292 | max_width, 293 | max_lineno, 294 | drawables, 295 | } 296 | } 297 | 298 | fn draw_line_number(&mut self, image: &mut RgbaImage, lineno: u32, mut color: Rgba) { 299 | for i in color.0.iter_mut() { 300 | *i = (*i).saturating_sub(20); 301 | } 302 | for i in 0..=lineno { 303 | let line_number = format!( 304 | "{:>width$}", 305 | i + self.line_offset, 306 | width = self.line_number_chars as usize 307 | ); 308 | let y = self.get_line_y(i); 309 | self.font.draw_text( 310 | image, 311 | color, 312 | self.code_pad, 313 | y, 314 | FontStyle::REGULAR, 315 | &line_number, 316 | ); 317 | } 318 | } 319 | 320 | fn highlight_lines>(&mut self, image: &mut RgbaImage, lines: I) { 321 | let width = image.width(); 322 | let height = self.get_line_height(); 323 | let color = image.get_pixel_mut(20, 20); 324 | 325 | for i in color.0.iter_mut() { 326 | *i = (*i).saturating_add(40); 327 | } 328 | 329 | let shadow = RgbaImage::from_pixel(width, height, *color); 330 | 331 | for i in lines { 332 | let y = self.get_line_y(i - 1); 333 | copy_alpha(&shadow, image, 0, y); 334 | } 335 | } 336 | 337 | // TODO: use &T instead of &mut T ? 338 | pub fn format(&mut self, v: &[Vec<(Style, &str)>], theme: &Theme) -> RgbaImage { 339 | if self.line_number { 340 | self.line_number_chars = 341 | (((v.len() + self.line_offset as usize) as f32).log10() + 1.0).floor() as u32; 342 | } else { 343 | self.line_number_chars = 0; 344 | self.line_number_pad = 0; 345 | } 346 | 347 | let drawables = self.create_drawables(v); 348 | 349 | let size = self.get_image_size(drawables.max_width, drawables.max_lineno); 350 | 351 | let foreground = theme.settings.foreground.unwrap(); 352 | let background = theme.settings.background.unwrap(); 353 | 354 | let mut image = RgbaImage::from_pixel(size.0, size.1, background.to_rgba()); 355 | 356 | if !self.highlight_lines.is_empty() { 357 | let highlight_lines = self 358 | .highlight_lines 359 | .iter() 360 | .cloned() 361 | .filter(|&n| n >= 1 && n <= drawables.max_lineno + 1) 362 | .collect::>(); 363 | self.highlight_lines(&mut image, highlight_lines); 364 | } 365 | if self.line_number { 366 | self.draw_line_number(&mut image, drawables.max_lineno, foreground.to_rgba()); 367 | } 368 | 369 | for (x, y, color, style, text) in drawables.drawables { 370 | let color = color.unwrap_or(foreground).to_rgba(); 371 | self.font.draw_text(&mut image, color, x, y, style, &text); 372 | } 373 | 374 | if self.window_controls { 375 | let params = WindowControlsParams { 376 | width: self.window_controls_width, 377 | height: self.window_controls_height, 378 | padding: self.title_bar_pad, 379 | radius: self.window_controls_width / 3 / 4, 380 | }; 381 | add_window_controls(&mut image, ¶ms); 382 | } 383 | 384 | if self.round_corner { 385 | round_corner(&mut image, 12); 386 | } 387 | 388 | if let Some(adder) = &self.shadow_adder { 389 | adder.apply_to(&image) 390 | } else { 391 | image 392 | } 393 | } 394 | } 395 | -------------------------------------------------------------------------------- /src/hb_wrapper.rs: -------------------------------------------------------------------------------- 1 | use anyhow::{ensure, Result}; 2 | use core::slice; 3 | // font_kit already has a wrapper around freetype called Font so use it directly 4 | use font_kit::font::Font; 5 | use font_kit::loaders::freetype::NativeFont; 6 | // use harfbuzz for shaping ligatures 7 | pub use harfbuzz::*; 8 | use harfbuzz_sys as harfbuzz; 9 | use std::mem; 10 | 11 | /// font feature tag 12 | pub fn feature_from_tag(tag: &str) -> Result { 13 | unsafe { 14 | let mut feature = mem::zeroed(); 15 | ensure!( 16 | hb_feature_from_string( 17 | tag.as_ptr() as *const ::std::os::raw::c_char, 18 | tag.len() as i32, 19 | &mut feature as *mut _ 20 | ) != 0, 21 | "hb_feature_from_string failed for {}", 22 | tag 23 | ); 24 | Ok(feature) 25 | } 26 | } 27 | 28 | /// Harfbuzz font 29 | pub struct HBFont { 30 | font: *mut hb_font_t, 31 | } 32 | 33 | // harfbuzz freetype integration 34 | extern "C" { 35 | pub fn hb_ft_font_create_referenced(face: NativeFont) -> *mut hb_font_t; // the same as hb_face_t 36 | } 37 | 38 | impl Drop for HBFont { 39 | fn drop(&mut self) { 40 | unsafe { hb_font_destroy(self.font) } 41 | } 42 | } 43 | 44 | impl HBFont { 45 | pub fn new(face: &Font) -> HBFont { 46 | HBFont { 47 | font: unsafe { hb_ft_font_create_referenced(face.native_font() as _) }, 48 | } 49 | } 50 | pub fn shape(&mut self, buffer: &HBBuffer, features: &[hb_feature_t]) { 51 | unsafe { 52 | hb_shape( 53 | self.font, 54 | buffer.buffer, 55 | features.as_ptr(), 56 | features.len() as u32, 57 | ); 58 | } 59 | } 60 | } 61 | 62 | /// Harfbuzz buffer 63 | pub struct HBBuffer { 64 | buffer: *mut hb_buffer_t, 65 | } 66 | 67 | impl Drop for HBBuffer { 68 | fn drop(&mut self) { 69 | unsafe { hb_buffer_destroy(self.buffer) } 70 | } 71 | } 72 | 73 | impl HBBuffer { 74 | pub fn new() -> Result { 75 | let hb_buf = unsafe { hb_buffer_create() }; 76 | ensure!( 77 | unsafe { hb_buffer_allocation_successful(hb_buf) } != 0, 78 | "hb_buffer_create failed!" 79 | ); 80 | Ok(HBBuffer { buffer: hb_buf }) 81 | } 82 | 83 | pub fn guess_segments_properties(&mut self) { 84 | unsafe { hb_buffer_guess_segment_properties(self.buffer) }; 85 | } 86 | 87 | pub fn add_utf8(&mut self, s: &[u8]) { 88 | unsafe { 89 | hb_buffer_add_utf8( 90 | self.buffer, 91 | s.as_ptr() as *const ::std::os::raw::c_char, 92 | s.len() as i32, 93 | 0, 94 | s.len() as i32, 95 | ); 96 | } 97 | } 98 | pub fn add_str(&mut self, s: &str) { 99 | self.add_utf8(s.as_bytes()); 100 | } 101 | 102 | pub fn get_glyph_infos(&mut self) -> &[hb_glyph_info_t] { 103 | unsafe { 104 | let mut len: u32 = 0; 105 | let info = hb_buffer_get_glyph_infos(self.buffer, &mut len as *mut u32); 106 | slice::from_raw_parts(info, len as usize) 107 | } 108 | } 109 | 110 | pub fn get_glyph_positions(&mut self) -> &[hb_glyph_position_t] { 111 | unsafe { 112 | let mut len: u32 = 0; 113 | let info = hb_buffer_get_glyph_positions(self.buffer, &mut len as *mut u32); 114 | slice::from_raw_parts(info, len as usize) 115 | } 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | //! `silicon` is a tool to create beautiful image of your source code. 2 | //! 3 | //! # Example 4 | //! 5 | //! ``` 6 | //! use syntect::easy::HighlightLines; 7 | //! use syntect::util::LinesWithEndings; 8 | //! use silicon::utils::ShadowAdder; 9 | //! use silicon::formatter::ImageFormatterBuilder; 10 | //! use silicon::assets::HighlightingAssets; 11 | //! 12 | //! let ha = HighlightingAssets::new(); 13 | //! let (ps, ts) = (ha.syntax_set, ha.theme_set); 14 | //! let code = r#"fn main() { 15 | //! println!("Hello, world!"); 16 | //! } 17 | //! "#; 18 | //! 19 | //! let syntax = ps.find_syntax_by_token("rs").unwrap(); 20 | //! let theme = &ts.themes["Dracula"]; 21 | //! 22 | //! let mut h = HighlightLines::new(syntax, theme); 23 | //! let highlight = LinesWithEndings::from(&code) 24 | //! .map(|line| h.highlight_line(line, &ps)) 25 | //! .collect::, _>>() 26 | //! .unwrap(); 27 | //! 28 | //! let mut formatter = ImageFormatterBuilder::new() 29 | //! .font(vec![("Hack", 26.0)]) 30 | //! .shadow_adder(ShadowAdder::default()) 31 | //! .build() 32 | //! .unwrap(); 33 | //! let image = formatter.format(&highlight, theme); 34 | //! 35 | //! image.save("hello.png").unwrap(); 36 | //! ``` 37 | #[macro_use] 38 | extern crate log; 39 | 40 | pub mod assets; 41 | pub mod blur; 42 | pub mod directories; 43 | pub mod error; 44 | pub mod font; 45 | pub mod formatter; 46 | #[cfg(feature = "harfbuzz")] 47 | pub mod hb_wrapper; 48 | pub mod utils; 49 | -------------------------------------------------------------------------------- /src/utils.rs: -------------------------------------------------------------------------------- 1 | use crate::error::ParseColorError; 2 | use image::imageops::{crop_imm, resize, FilterType}; 3 | use image::Pixel; 4 | use image::{GenericImage, GenericImageView, Rgba, RgbaImage}; 5 | use imageproc::drawing::{draw_filled_rect_mut, draw_line_segment_mut}; 6 | use imageproc::rect::Rect; 7 | 8 | pub trait ToRgba { 9 | type Target; 10 | fn to_rgba(&self) -> Self::Target; 11 | } 12 | 13 | /// Parse hex color (#RRGGBB or #RRGGBBAA) 14 | impl ToRgba for str { 15 | type Target = Result, ParseColorError>; 16 | 17 | fn to_rgba(&self) -> Self::Target { 18 | if self.as_bytes()[0] != b'#' { 19 | return Err(ParseColorError::InvalidDigit); 20 | } 21 | let mut color = u32::from_str_radix(&self[1..], 16)?; 22 | 23 | match self.len() { 24 | // RGB or RGBA 25 | 4 | 5 => { 26 | let a = if self.len() == 5 { 27 | let alpha = (color & 0xf) as u8; 28 | color >>= 4; 29 | alpha 30 | } else { 31 | 0xff 32 | }; 33 | 34 | let r = ((color >> 8) & 0xf) as u8; 35 | let g = ((color >> 4) & 0xf) as u8; 36 | let b = (color & 0xf) as u8; 37 | 38 | Ok(Rgba([r << 4 | r, g << 4 | g, b << 4 | b, a << 4 | a])) 39 | } 40 | // RRGGBB or RRGGBBAA 41 | 7 | 9 => { 42 | let alpha = if self.len() == 9 { 43 | let alpha = (color & 0xff) as u8; 44 | color >>= 8; 45 | alpha 46 | } else { 47 | 0xff 48 | }; 49 | 50 | Ok(Rgba([ 51 | (color >> 16) as u8, 52 | (color >> 8) as u8, 53 | color as u8, 54 | alpha, 55 | ])) 56 | } 57 | _ => Err(ParseColorError::InvalidLength), 58 | } 59 | } 60 | } 61 | 62 | impl ToRgba for syntect::highlighting::Color { 63 | type Target = Rgba; 64 | fn to_rgba(&self) -> Self::Target { 65 | Rgba([self.r, self.g, self.b, self.a]) 66 | } 67 | } 68 | 69 | pub struct WindowControlsParams { 70 | pub width: u32, 71 | pub height: u32, 72 | pub padding: u32, 73 | pub radius: u32, 74 | } 75 | 76 | /// Add the window controls for image 77 | pub(crate) fn add_window_controls(image: &mut RgbaImage, params: &WindowControlsParams) { 78 | let color = [ 79 | ("#FF5F56", "#E0443E"), 80 | ("#FFBD2E", "#DEA123"), 81 | ("#27C93F", "#1AAB29"), 82 | ]; 83 | 84 | let background = image.get_pixel_mut(37, 37); 85 | background.0[3] = 0; 86 | 87 | let mut title_bar = RgbaImage::from_pixel(params.width * 3, params.height * 3, *background); 88 | let step = (params.radius * 2) as i32; 89 | let spacer = step * 2; 90 | let center_y = (params.height / 2) as i32; 91 | 92 | for (i, (fill, outline)) in color.iter().enumerate() { 93 | draw_filled_circle_mut( 94 | &mut title_bar, 95 | ((i as i32 * spacer + step) * 3, center_y * 3), 96 | (params.radius + 1) as i32 * 3, 97 | outline.to_rgba().unwrap(), 98 | ); 99 | draw_filled_circle_mut( 100 | &mut title_bar, 101 | ((i as i32 * spacer + step) * 3, center_y * 3), 102 | params.radius as i32 * 3, 103 | fill.to_rgba().unwrap(), 104 | ); 105 | } 106 | // create a big image and resize it to blur the edge 107 | // it looks better than `blur()` 108 | let title_bar = resize( 109 | &title_bar, 110 | params.width, 111 | params.height, 112 | FilterType::Triangle, 113 | ); 114 | 115 | copy_alpha(&title_bar, image, params.padding, params.padding); 116 | } 117 | 118 | #[derive(Clone, Debug)] 119 | pub enum Background { 120 | Solid(Rgba), 121 | Image(RgbaImage), 122 | } 123 | 124 | impl Default for Background { 125 | fn default() -> Self { 126 | Self::Solid("#abb8c3".to_rgba().unwrap()) 127 | } 128 | } 129 | 130 | impl Background { 131 | fn to_image(&self, width: u32, height: u32) -> RgbaImage { 132 | match self { 133 | Background::Solid(color) => RgbaImage::from_pixel(width, height, color.to_owned()), 134 | Background::Image(image) => resize(image, width, height, FilterType::Triangle), 135 | } 136 | } 137 | } 138 | 139 | /// Add the shadow for image 140 | #[derive(Debug)] 141 | pub struct ShadowAdder { 142 | background: Background, 143 | shadow_color: Rgba, 144 | blur_radius: f32, 145 | pad_horiz: u32, 146 | pad_vert: u32, 147 | offset_x: i32, 148 | offset_y: i32, 149 | } 150 | 151 | impl ShadowAdder { 152 | pub fn new() -> Self { 153 | Self { 154 | background: Background::default(), 155 | shadow_color: "#707070".to_rgba().unwrap(), 156 | blur_radius: 50.0, 157 | pad_horiz: 80, 158 | pad_vert: 100, 159 | offset_x: 0, 160 | offset_y: 0, 161 | } 162 | } 163 | 164 | /// Set the background color 165 | pub fn background(mut self, bg: Background) -> Self { 166 | self.background = bg; 167 | self 168 | } 169 | 170 | /// Set the shadow color 171 | pub fn shadow_color(mut self, color: Rgba) -> Self { 172 | self.shadow_color = color; 173 | self 174 | } 175 | 176 | /// Set the shadow size 177 | pub fn blur_radius(mut self, sigma: f32) -> Self { 178 | self.blur_radius = sigma; 179 | self 180 | } 181 | 182 | pub fn pad_horiz(mut self, pad: u32) -> Self { 183 | self.pad_horiz = pad; 184 | self 185 | } 186 | 187 | pub fn pad_vert(mut self, pad: u32) -> Self { 188 | self.pad_vert = pad; 189 | self 190 | } 191 | 192 | pub fn offset_x(mut self, offset: i32) -> Self { 193 | self.offset_x = offset; 194 | self 195 | } 196 | 197 | pub fn offset_y(mut self, offset: i32) -> Self { 198 | self.offset_y = offset; 199 | self 200 | } 201 | 202 | pub fn apply_to(&self, image: &RgbaImage) -> RgbaImage { 203 | // the size of the final image 204 | let width = image.width() + self.pad_horiz * 2; 205 | let height = image.height() + self.pad_vert * 2; 206 | 207 | // create the shadow 208 | let mut shadow = self.background.to_image(width, height); 209 | if self.blur_radius > 0.0 { 210 | let rect = Rect::at( 211 | self.pad_horiz as i32 + self.offset_x, 212 | self.pad_vert as i32 + self.offset_y, 213 | ) 214 | .of_size(image.width(), image.height()); 215 | 216 | draw_filled_rect_mut(&mut shadow, rect, self.shadow_color); 217 | 218 | shadow = crate::blur::gaussian_blur(shadow, self.blur_radius); 219 | } 220 | // it's to slow! 221 | // shadow = blur(&shadow, self.blur_radius); 222 | 223 | // copy the original image to the top of it 224 | copy_alpha(image, &mut shadow, self.pad_horiz, self.pad_vert); 225 | 226 | shadow 227 | } 228 | } 229 | 230 | impl Default for ShadowAdder { 231 | fn default() -> Self { 232 | ShadowAdder::new() 233 | } 234 | } 235 | 236 | /// copy from src to dst, taking into account alpha channels 237 | pub(crate) fn copy_alpha(src: &RgbaImage, dst: &mut RgbaImage, x: u32, y: u32) { 238 | assert!(src.width() + x <= dst.width()); 239 | assert!(src.height() + y <= dst.height()); 240 | for j in 0..src.height() { 241 | for i in 0..src.width() { 242 | // NOTE: Undeprecate in https://github.com/image-rs/image/pull/1008 243 | #[allow(deprecated)] 244 | unsafe { 245 | let s = src.unsafe_get_pixel(i, j); 246 | let mut d = dst.unsafe_get_pixel(i + x, j + y); 247 | match s.0[3] { 248 | 255 => d = s, 249 | 0 => (/* do nothing */), 250 | _ => d.blend(&s), 251 | } 252 | dst.unsafe_put_pixel(i + x, j + y, d); 253 | } 254 | } 255 | } 256 | } 257 | 258 | /// Round the corner of the image 259 | pub(crate) fn round_corner(image: &mut RgbaImage, radius: u32) { 260 | // draw a circle with given foreground on given background 261 | // then split it into four pieces and paste them to the four corner of the image 262 | // 263 | // the circle is drawn on a bigger image to avoid the aliasing 264 | // later it will be scaled to the correct size 265 | // we add +1 (to the radius) to make sure that there is also space for the border to mitigate artefacts when scaling 266 | // note that the +1 isn't added to the radius when drawing the circle 267 | let mut circle = 268 | RgbaImage::from_pixel((radius + 1) * 4, (radius + 1) * 4, Rgba([255, 255, 255, 0])); 269 | 270 | let width = image.width(); 271 | let height = image.height(); 272 | 273 | // use the bottom right pixel to get the color of the foreground 274 | let foreground = image.get_pixel(width - 1, height - 1); 275 | 276 | draw_filled_circle_mut( 277 | &mut circle, 278 | (((radius + 1) * 2) as i32, ((radius + 1) * 2) as i32), 279 | radius as i32 * 2, 280 | *foreground, 281 | ); 282 | 283 | // scale down the circle to the correct size 284 | let circle = resize( 285 | &circle, 286 | (radius + 1) * 2, 287 | (radius + 1) * 2, 288 | FilterType::Triangle, 289 | ); 290 | 291 | // top left 292 | let part = crop_imm(&circle, 1, 1, radius, radius); 293 | image.copy_from(&*part, 0, 0).unwrap(); 294 | 295 | // top right 296 | let part = crop_imm(&circle, radius + 1, 1, radius, radius - 1); 297 | image.copy_from(&*part, width - radius, 0).unwrap(); 298 | 299 | // bottom left 300 | let part = crop_imm(&circle, 1, radius + 1, radius, radius); 301 | image.copy_from(&*part, 0, height - radius).unwrap(); 302 | 303 | // bottom right 304 | let part = crop_imm(&circle, radius + 1, radius + 1, radius, radius); 305 | image 306 | .copy_from(&*part, width - radius, height - radius) 307 | .unwrap(); 308 | } 309 | 310 | // `draw_filled_circle_mut` doesn't work well with small radius in imageproc v0.18.0 311 | // it has been fixed but still have to wait for releasing 312 | // issue: https://github.com/image-rs/imageproc/issues/328 313 | // PR: https://github.com/image-rs/imageproc/pull/330 314 | /// Draw as much of a circle, including its contents, as lies inside the image bounds. 315 | pub(crate) fn draw_filled_circle_mut( 316 | image: &mut I, 317 | center: (i32, i32), 318 | radius: i32, 319 | color: I::Pixel, 320 | ) where 321 | I: GenericImage, 322 | I::Pixel: 'static, 323 | { 324 | let mut x = 0i32; 325 | let mut y = radius; 326 | let mut p = 1 - radius; 327 | let x0 = center.0; 328 | let y0 = center.1; 329 | 330 | while x <= y { 331 | draw_line_segment_mut( 332 | image, 333 | ((x0 - x) as f32, (y0 + y) as f32), 334 | ((x0 + x) as f32, (y0 + y) as f32), 335 | color, 336 | ); 337 | draw_line_segment_mut( 338 | image, 339 | ((x0 - y) as f32, (y0 + x) as f32), 340 | ((x0 + y) as f32, (y0 + x) as f32), 341 | color, 342 | ); 343 | draw_line_segment_mut( 344 | image, 345 | ((x0 - x) as f32, (y0 - y) as f32), 346 | ((x0 + x) as f32, (y0 - y) as f32), 347 | color, 348 | ); 349 | draw_line_segment_mut( 350 | image, 351 | ((x0 - y) as f32, (y0 - x) as f32), 352 | ((x0 + y) as f32, (y0 - x) as f32), 353 | color, 354 | ); 355 | 356 | x += 1; 357 | if p < 0 { 358 | p += 2 * x + 1; 359 | } else { 360 | y -= 1; 361 | p += 2 * (x - y) + 1; 362 | } 363 | } 364 | } 365 | 366 | #[cfg(test)] 367 | mod tests { 368 | use crate::utils::ToRgba; 369 | use image::Rgba; 370 | 371 | #[test] 372 | fn to_rgba() { 373 | assert_eq!("#abcdef".to_rgba(), Ok(Rgba([0xab, 0xcd, 0xef, 0xff]))); 374 | assert_eq!("#abcdef00".to_rgba(), Ok(Rgba([0xab, 0xcd, 0xef, 0x00]))); 375 | assert_eq!("#abc".to_rgba(), Ok(Rgba([0xaa, 0xbb, 0xcc, 0xff]))); 376 | assert_eq!("#abcd".to_rgba(), Ok(Rgba([0xaa, 0xbb, 0xcc, 0xdd]))); 377 | } 378 | } 379 | --------------------------------------------------------------------------------