From 2c7eefbb757871db78be93004bdc565c74311776 Mon Sep 17 00:00:00 2001 From: golovin Date: Sun, 17 Dec 2023 07:57:42 +0300 Subject: [PATCH] 2023-06-30 --- .gitattributes | 5 + .gitignore | 3 + DIRECTORY-TREE.md | 64 ++++ README.en.md | 16 + README.md | 16 + build.sh | 54 ++++ jekyll_site/Gemfile_color | 3 + jekyll_site/Gemfile_older | 3 + jekyll_site/_config_color.yml | 20 ++ jekyll_site/_config_older.yml | 20 ++ jekyll_site/_includes/counters_body.html | 2 + jekyll_site/_includes/counters_head.html | 16 + jekyll_site/css/pomodoro5.css | 8 + .../en/2023/01/04/drawing-simple-captcha.md | 224 +++++++++++++ .../2023/02/06/function-graph-in-console.md | 147 +++++++++ .../en/2023/03/08/drawing-heart-in-console.md | 304 ++++++++++++++++++ .../en/2023/06/20/password-generator.md | 90 ++++++ jekyll_site/en/index.md | 50 +++ jekyll_site/img/captcha.png | Bin 0 -> 6789 bytes jekyll_site/img/circumference-equation.svg | 38 +++ jekyll_site/img/heart-graph.png | Bin 0 -> 22659 bytes jekyll_site/img/heart-monospaced-plain-22.bmp | Bin 0 -> 170 bytes jekyll_site/img/rhombus-equation.svg | 31 ++ jekyll_site/img/square-equations.svg | 76 +++++ jekyll_site/js/password-generator.js | 29 ++ jekyll_site/robots.txt | 7 + .../ru/2023/01/03/drawing-simple-captcha.md | 223 +++++++++++++ .../2023/02/05/function-graph-in-console.md | 145 +++++++++ .../ru/2023/03/08/drawing-heart-in-console.md | 300 +++++++++++++++++ .../ru/2023/06/19/password-generator.md | 87 +++++ jekyll_site/ru/index.md | 48 +++ package.sh | 5 + serve.sh | 4 + 33 files changed, 2038 insertions(+) create mode 100644 DIRECTORY-TREE.md create mode 100644 README.en.md create mode 100644 README.md create mode 100755 build.sh create mode 100644 jekyll_site/Gemfile_color create mode 100644 jekyll_site/Gemfile_older create mode 100644 jekyll_site/_config_color.yml create mode 100644 jekyll_site/_config_older.yml create mode 100644 jekyll_site/_includes/counters_body.html create mode 100644 jekyll_site/_includes/counters_head.html create mode 100644 jekyll_site/css/pomodoro5.css create mode 100644 jekyll_site/en/2023/01/04/drawing-simple-captcha.md create mode 100644 jekyll_site/en/2023/02/06/function-graph-in-console.md create mode 100644 jekyll_site/en/2023/03/08/drawing-heart-in-console.md create mode 100644 jekyll_site/en/2023/06/20/password-generator.md create mode 100644 jekyll_site/en/index.md create mode 100644 jekyll_site/img/captcha.png create mode 100644 jekyll_site/img/circumference-equation.svg create mode 100644 jekyll_site/img/heart-graph.png create mode 100644 jekyll_site/img/heart-monospaced-plain-22.bmp create mode 100644 jekyll_site/img/rhombus-equation.svg create mode 100644 jekyll_site/img/square-equations.svg create mode 100644 jekyll_site/js/password-generator.js create mode 100644 jekyll_site/robots.txt create mode 100644 jekyll_site/ru/2023/01/03/drawing-simple-captcha.md create mode 100644 jekyll_site/ru/2023/02/05/function-graph-in-console.md create mode 100644 jekyll_site/ru/2023/03/08/drawing-heart-in-console.md create mode 100644 jekyll_site/ru/2023/06/19/password-generator.md create mode 100644 jekyll_site/ru/index.md create mode 100755 package.sh create mode 100755 serve.sh diff --git a/.gitattributes b/.gitattributes index e69de29..2136e80 100644 --- a/.gitattributes +++ b/.gitattributes @@ -0,0 +1,5 @@ +jekyll_site/ru/** linguist-language=Java +jekyll_site/en/** linguist-language=Java + +jekyll_site/ru/2023/06/** linguist-language=JavaScript +jekyll_site/en/2023/06/** linguist-language=JavaScript diff --git a/.gitignore b/.gitignore index c38fa4e..42e9bb7 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,5 @@ .idea *.iml +*.zip +_site* +.repo_*.sh diff --git a/DIRECTORY-TREE.md b/DIRECTORY-TREE.md new file mode 100644 index 0000000..94941b1 --- /dev/null +++ b/DIRECTORY-TREE.md @@ -0,0 +1,64 @@ +## Дерево каталогов + +
+.
+├─ jekyll_site
+│  ├─ _includes
+│  │  ├─ counters_body.html
+│  │  └─ counters_head.html
+│  ├─ css
+│  │  └─ pomodoro5.css
+│  ├─ en
+│  │  ├─ 2023
+│  │  │  ├─ 01
+│  │  │  │  └─ 04
+│  │  │  │     └─ drawing-simple-captcha.md
+│  │  │  ├─ 02
+│  │  │  │  └─ 06
+│  │  │  │     └─ function-graph-in-console.md
+│  │  │  ├─ 03
+│  │  │  │  └─ 08
+│  │  │  │     └─ drawing-heart-in-console.md
+│  │  │  └─ 06
+│  │  │     └─ 20
+│  │  │        └─ password-generator.md
+│  │  └─ index.md
+│  ├─ img
+│  │  ├─ captcha.png
+│  │  ├─ circumference-equation.svg
+│  │  ├─ heart-graph.png
+│  │  ├─ heart-monospaced-plain-22.bmp
+│  │  ├─ rhombus-equation.svg
+│  │  └─ square-equations.svg
+│  ├─ js
+│  │  └─ password-generator.js
+│  ├─ ru
+│  │  ├─ 2023
+│  │  │  ├─ 01
+│  │  │  │  └─ 03
+│  │  │  │     └─ drawing-simple-captcha.md
+│  │  │  ├─ 02
+│  │  │  │  └─ 05
+│  │  │  │     └─ function-graph-in-console.md
+│  │  │  ├─ 03
+│  │  │  │  └─ 08
+│  │  │  │     └─ drawing-heart-in-console.md
+│  │  │  └─ 06
+│  │  │     └─ 19
+│  │  │        └─ password-generator.md
+│  │  └─ index.md
+│  ├─ Gemfile_color
+│  ├─ Gemfile_older
+│  ├─ _config_color.yml
+│  ├─ _config_older.yml
+│  └─ robots.txt
+├─ CONTRIBUTING.md
+├─ DIRECTORY-TREE.md
+├─ LICENSE.md
+├─ OPEN_LICENSE.txt
+├─ README.en.md
+├─ README.md
+├─ build.sh
+├─ package.sh
+└─ serve.sh
+
diff --git a/README.en.md b/README.en.md new file mode 100644 index 0000000..8d6d7ab --- /dev/null +++ b/README.en.md @@ -0,0 +1,16 @@ +## Website pages + +- [Password generator](https://pomodoro5.mircloud.ru/en/2023/06/20/password-generator.html) — 20.06.2023. +- [Drawing heart in console](https://pomodoro5.mircloud.ru/en/2023/03/08/drawing-heart-in-console.html) — 08.03.2023. +- [Function graph in console](https://pomodoro5.mircloud.ru/en/2023/02/06/function-graph-in-console.html) — 06.02.2023. +- [Drawing simple captcha](https://pomodoro5.mircloud.ru/en/2023/01/04/drawing-simple-captcha.html) — 04.01.2023. + +## [Source texts](README.md) + +- Series of the static websites [«Pomodori»](https://hub.mos.ru/golovin.gg/pomodoro/blob/master/README.en.md). +- Used formats — Markdown, Liquid, YAML. +- Build tool — Jekyll with tomato design themes. +- Automation of processes — Bash scripts for command line. +- [build.sh](build.sh) — Building a site in two tomato themes and optimizing the results. +- [serve.sh](serve.sh) — Local deployment to verify the correctness of the build. +- [package.sh](package.sh) — Preparing an archive for subsequent deployment. diff --git a/README.md b/README.md new file mode 100644 index 0000000..eb3b44a --- /dev/null +++ b/README.md @@ -0,0 +1,16 @@ +## Страницы вёб-сайта + +- [Генератор паролей](https://pomodoro5.mircloud.ru/ru/2023/06/19/password-generator.html) — 19.06.2023. +- [Рисуем сердечко в консоли](https://pomodoro5.mircloud.ru/ru/2023/03/08/drawing-heart-in-console.html) — 08.03.2023. +- [График функции в консоли](https://pomodoro5.mircloud.ru/ru/2023/02/05/function-graph-in-console.html) — 05.02.2023. +- [Рисуем простую капчу](https://pomodoro5.mircloud.ru/ru/2023/01/03/drawing-simple-captcha.html) — 03.01.2023. + +## [Исходные тексты](README.en.md) + +- Серия статических вёб-сайтов [«Помидоры»](https://hub.mos.ru/golovin.gg/pomodoro/blob/master/README.md). +- Используемые форматы — Markdown, Liquid, YAML. +- Инструмент сборки — Jekyll с помидорными темами оформления. +- Автоматизация процессов — Bash скрипты для командной строки. +- [build.sh](build.sh) — Сборка сайта в двух помидорных темах и оптимизация результатов. +- [serve.sh](serve.sh) — Локальное развёртывание для проверки корректности сборки. +- [package.sh](package.sh) — Подготовка архива для последующего развёртывания. diff --git a/build.sh b/build.sh new file mode 100755 index 0000000..0a5e572 --- /dev/null +++ b/build.sh @@ -0,0 +1,54 @@ +#!/bin/bash +echo "Сборка сайта в двух помидорных темах и оптимизация результатов." +milliseconds=$(date '+%s%3N') +rm -rf _site +rm -rf _site_older +rm -rf _site_color +echo "Сборка старого помидора." +mkdir -p _site_older +cp -r jekyll_site/_includes _site_older +cp -r jekyll_site/ru _site_older +cp -r jekyll_site/en _site_older +cp -r jekyll_site/ru/index.md _site_older +cp -r jekyll_site/_config_older.yml _site_older/_config.yml +cp -r jekyll_site/Gemfile_older _site_older/Gemfile +cd _site_older || exit +jekyll build +cp -r _site .. +cd .. +echo "Сборка цветного помидора." +mkdir -p _site_color +cp -r jekyll_site/_includes _site_color +cp -r jekyll_site/ru _site_color +cp -r jekyll_site/en _site_color +cp -r jekyll_site/ru/index.md _site_color +cp -r jekyll_site/_config_color.yml _site_color/_config.yml +cp -r jekyll_site/Gemfile_color _site_color/Gemfile +cd _site_color || exit +jekyll build +cp -r _site ../_site/color +cd .. +echo "Копирование без сборки." +cp -r jekyll_site/css _site +cp -r jekyll_site/img _site +cp -r jekyll_site/js _site +cp -r jekyll_site/robots.txt _site +echo "Оптимизация собранного контента." +cd _site || exit +cp -r assets/* . +rm -r assets +rm -r color/assets/favicon.ico +cp -r color/assets/* . +rm -r color/assets +rm -r color/404.html +find . -type f -name '*.html' | sort -r | while read -r file; do + sed -i 's/layout-padding=""/layout-padding/g' "$file" + sed -i 's/ class="language-plaintext highlighter-rouge"//g' "$file" + sed -i 's/ class="language-java highlighter-rouge"//g' "$file" + sed -i 's/ class="language-js highlighter-rouge"//g' "$file" + sed -i 's/
/
/g' "$file"
+  sed -i 's/<\/code><\/pre><\/div><\/div>/<\/code><\/pre><\/div>/g' "$file"
+  sed -i 's/
/
/g' "$file" + sed -i -r 's///g' "$file" +done +echo "Время выполнения сборки: $(("$(date '+%s%3N')" - "$milliseconds")) мс." diff --git a/jekyll_site/Gemfile_color b/jekyll_site/Gemfile_color new file mode 100644 index 0000000..136ccb7 --- /dev/null +++ b/jekyll_site/Gemfile_color @@ -0,0 +1,3 @@ +source "https://rubygems.org" +gem "jekyll" +gem "color-tomato-theme" diff --git a/jekyll_site/Gemfile_older b/jekyll_site/Gemfile_older new file mode 100644 index 0000000..5e19e6f --- /dev/null +++ b/jekyll_site/Gemfile_older @@ -0,0 +1,3 @@ +source "https://rubygems.org" +gem "jekyll" +gem "older-tomato-theme" diff --git a/jekyll_site/_config_color.yml b/jekyll_site/_config_color.yml new file mode 100644 index 0000000..89e32cc --- /dev/null +++ b/jekyll_site/_config_color.yml @@ -0,0 +1,20 @@ +# site parameters +name: "Код с комментариями" +name_translated: "Code with comments" +url: "https://pomodoro5.mircloud.ru" +baseurl: "/color" +homepage_url: "https://git.org.ru/pomodoro/5" +homepage_name: "GIT.ORG.RU" +older_tomato_baseurl: "" +timezone: "Europe/Moscow" +author: "Головин Г.Г." +author_translated: "Golovin G.G." +translation_caption: "translation from Russian" +# build parameters +disable_disk_cache: true +theme: color-tomato-theme +defaults: + - scope: + path: "" + values: + layout: default diff --git a/jekyll_site/_config_older.yml b/jekyll_site/_config_older.yml new file mode 100644 index 0000000..8358119 --- /dev/null +++ b/jekyll_site/_config_older.yml @@ -0,0 +1,20 @@ +# site parameters +name: "Код с комментариями" +name_translated: "Code with comments" +url: "https://pomodoro5.mircloud.ru" +baseurl: "" +homepage_url: "https://git.org.ru/pomodoro/5" +homepage_name: "GIT.ORG.RU" +color_tomato_baseurl: "/color" +timezone: "Europe/Moscow" +author: "Головин Г.Г." +author_translated: "Golovin G.G." +translation_caption: "translation from Russian" +# build parameters +disable_disk_cache: true +theme: older-tomato-theme +defaults: + - scope: + path: "" + values: + layout: default diff --git a/jekyll_site/_includes/counters_body.html b/jekyll_site/_includes/counters_body.html new file mode 100644 index 0000000..a4d3268 --- /dev/null +++ b/jekyll_site/_includes/counters_body.html @@ -0,0 +1,2 @@ + + diff --git a/jekyll_site/_includes/counters_head.html b/jekyll_site/_includes/counters_head.html new file mode 100644 index 0000000..eeee38e --- /dev/null +++ b/jekyll_site/_includes/counters_head.html @@ -0,0 +1,16 @@ + + + + + diff --git a/jekyll_site/css/pomodoro5.css b/jekyll_site/css/pomodoro5.css new file mode 100644 index 0000000..f2522c4 --- /dev/null +++ b/jekyll_site/css/pomodoro5.css @@ -0,0 +1,8 @@ +input { + font-size: 100%; + cursor: pointer; +} + +md-content input { + color: #1b5e20; +} diff --git a/jekyll_site/en/2023/01/04/drawing-simple-captcha.md b/jekyll_site/en/2023/01/04/drawing-simple-captcha.md new file mode 100644 index 0000000..a254ec0 --- /dev/null +++ b/jekyll_site/en/2023/01/04/drawing-simple-captcha.md @@ -0,0 +1,224 @@ +--- +title: Drawing simple captcha +description: Let's write an algorithm for displaying text as an image using the Java AWT library. Symbols and font can be any, but for this example we will use a... +sections: [Cryptography,Font rendering,Image rotation] +tags: [java,awt,graphics,image,picture,captcha] +canonical_url: /en/2023/01/04/drawing-simple-captcha.html +url_translated: /ru/2023/01/03/drawing-simple-captcha.html +title_translated: Рисуем простую капчу +date: 2023.01.04 +lang: en +--- + +Let's write an algorithm for displaying text as an image using the Java AWT library. Symbols +and font can be any, but for this example we will use a combination of uppercase latin letters +and digits with the *Comic Sans* font — we will draw a simple captcha for a website or blog. + +{% include picture.html id="captcha.png" src="/img/captcha.png" alt="Drawing simple captcha" %} + +We'll consider special characters, but we won't use them, because it will be difficult for the +user to guess them with such a text decoration. For example, the plus `+` is still possible +to guess, but the minus `-` or the underscore `_` is already with difficulty, and even if you +guess right, then to find these buttons with difficulty, especially on the phone. Therefore, +for captcha we'll use a combination of only capital latin letters and digits. + +Rendering special characters in a monospaced font: [Drawing heart in console]({{ '/en/2023/03/08/drawing-heart-in-console.html#text-as-picture-and-picture-as-text' | relative_url }}). + +## Algorithm description {#algorithm-description} + +We prepare an array of symbols consisting of uppercase latin letters and numbers. Then bypass +this array and draw each symbol separately — we get a picture. Then rotate the pictures +alternately by ±35 degrees — we get an array of pictures with symbols. The second time we +bypass the array with pictures and collect a common image — we attach pictures from left +to right, so that the next picture runs over the previous one by 40% of its width. + +Why 35 degrees? If we take a larger angle, then it will be difficult for the user to solve +such a captcha. For example, the letters `N` and `Z` will be similar to each other. If we +take a smaller angle, then such a captcha will be easy to solve using machine text recognition. + +The imposition of the next picture on the previous one by 40% of its width is necessary, so +that the symbols are located very close or slightly touch each other — it also complicates +machine text recognition. + +## Font rendering {#font-rendering} + +When rendering the font, we will use *anti-aliasing*, otherwise the letters will have jagged +edges. Set the image with transparency support, color black, font *Comic Sans*. + +```java +// converting a string with text into a picture with text +private static BufferedImage stringToImage(String str, Font font) { + // font rendering context + FontRenderContext ctx = new FontRenderContext(font.getTransform(), true, true); + // get the dimensions of the picture with text when rendering + Rectangle bnd = font.getStringBounds(str, ctx).getBounds(); + // create a new image with transparency support + BufferedImage image = new BufferedImage(bnd.width, bnd.height, BufferedImage.TYPE_INT_ARGB); + // turn on the editing mode of the new image + Graphics2D graphics = image.createGraphics(); + // font for rendering + graphics.setFont(font); + // color, that we'll draw with + graphics.setColor(Color.BLACK); + // apply font smoothing when rendering text + graphics.setRenderingHint( // anti-aliasing pixels along the shape border + RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); + // draw a picture with text + graphics.drawString(str, bnd.x, -bnd.y); + // disable the editing mode + graphics.dispose(); + // return a picture with text + return image; +} +``` + +## Image rotation {#image-rotation} + +When rotating the image for smoothing, we will use *bilinear interpolation*, otherwise there +will be a lot of unnecessary artifacts along the image borders. On the way, we recalculate +the dimensions for the new image. + +```java +// rotate the picture by a given angle and change its dimensions +private static BufferedImage rotateImage(BufferedImage image, double angle) { + // converting degrees to radians + double radian = Math.toRadians(angle); + double sin = Math.abs(Math.sin(radian)); + double cos = Math.abs(Math.cos(radian)); + // get the dimensions of the current image + int width = image.getWidth(); + int height = image.getHeight(); + // calculate the dimensions of the new image + int nWidth = (int) Math.floor(width * cos + height * sin); + int nHeight = (int) Math.floor(height * cos + width * sin); + // create a new image with transparency support + BufferedImage rotated = new BufferedImage(nWidth, nHeight, BufferedImage.TYPE_INT_ARGB); + // turn on the editing mode of the new image + Graphics2D graphics = rotated.createGraphics(); + // apply picture smoothing when rotating + graphics.setRenderingHint( // bilinear interpolation + RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR); + // shift the origin of the new image to its center + graphics.translate(nWidth / 2, nHeight / 2); + // rotate the new image with its coordinate system + graphics.rotate(radian); + // put the current image in the new one, so that their centers coincide + graphics.drawImage(image, -width / 2, -height / 2, null); + // disable the editing mode + graphics.dispose(); + // return a new image + return rotated; +} +``` + +## Drawing simple captcha {#drawing-simple-captcha} + +We bypass the array of symbols, draw and rotate each symbol separately, on the way calculate +the dimensions for the common image. Create a common image and after that once again bypass +the array of images and add them one by one from left to right to the common image. We return +a pair of objects: the text value of the captcha and the picture with symbols. + +```java +// draw an array of symbols, rotate them and merge the results +private static Map.Entry drawSimpleCaptcha(String[] symbols) + throws IOException, FontFormatException { + Font font = Font // set the font file + .createFont(Font.TRUETYPE_FONT, new File("ComicSansMS.ttf")) + // set the font style and size + .deriveFont(Font.BOLD, 32); + // dimensions of the final image + int width = 0, height = 0; + // prepare an image array + BufferedImage[] images = new BufferedImage[symbols.length]; + // bypass the array of symbols, get pictures and + // calculate the dimensions of the final image + for (int i = 0; i < symbols.length; i++) { + if (i % 2 == 0) // draw the symbols and rotate the images + images[i] = rotateImage(stringToImage(symbols[i], font), 35); + else + images[i] = rotateImage(stringToImage(symbols[i], font), -35); + // dimensions of the picture with the current symbol + int h = images[i].getHeight(), w = images[i].getWidth(); + // height of the largest symbol + height = Math.max(height, h); + // we'll shift the next symbol by 40% of the previous one's width + if (i < symbols.length - 1) + width += w * 6 / 10; // take 60% of the current symbol's width + else // take the last symbol's width entirely + width += w; + } + // create a new image with transparency support + BufferedImage captcha = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB); + // turn on the editing mode of the new image + Graphics2D graphics = captcha.createGraphics(); + // bypass the array of images and add them to the common image from left to right + for (BufferedImage image : images) { + // draw the current symbol at the origin + graphics.drawImage(image, 0, 0, null); + // shift the origin by 60% of the current symbol's width + graphics.translate(image.getWidth() * 6 / 10, 0); + } + // disable the editing mode + graphics.dispose(); + // pair of objects: the text value of the captcha and the picture with symbols + return Map.entry(String.join("", symbols), captcha); +} +``` + +{% capture collapsed_md %} +```java +// method for outer calls, returns a random combination of 5 symbols +public static Map.Entry drawSimpleCaptcha() + throws IOException, FontFormatException { + return drawSimpleCaptcha(5); +} +``` +```java +// method for outer calls, string length required +public static Map.Entry drawSimpleCaptcha(int length) + throws IOException, FontFormatException { + // получаем случайную комбинацию символов указанной длины + String[] symbols = getRandomString(length); + return drawSimpleCaptcha(symbols); +} +``` +```java +// get a random combination of uppercase latin letters and numbers +private static String[] getRandomString(int length) { + String[] symbols = new String[length]; + Random random = new Random(); + for (int i = 0; i < length; i++) { + // 26 capital letters and 10 digits + int rnd = random.nextInt(36); + if (rnd < 26) // letters [A..Z] + symbols[i] = Character.toString('A' + rnd); + else // digits [0..9] + symbols[i] = Character.toString('0' + rnd - 26); + } + return symbols; +} +``` +{% endcapture %} +{%- include collapsed_block.html summary="Additional methods" content=collapsed_md -%} + +## Testing and launching {#testing-n-launching} + +The algorithm turned out to be universal — it can render almost any string and in almost any +font, but with a long list of exceptions, related to unicode symbol ranges and font types. There +are many variants, testing takes a long time, and the simplified model suits me quite well. + +To complete this example for visualization, let's draw a line: `SIMPLE+CAPTCHA+1+1`. + +```java +// start the program and output the result +public static void main(String[] args) throws IOException, FontFormatException { + String[] symbols = "simple+captcha+1+1".toUpperCase().split(""); + // Map.Entry captcha = drawSimpleCaptcha(18); + Map.Entry captcha = drawSimpleCaptcha(symbols); + // save image to file, text to console + ImageIO.write(captcha.getValue(), "png", new File("captcha.png")); + // System.out.println(captcha.getKey()); +} +``` + +See the picture from this code above: [captcha.png](#captcha.png). diff --git a/jekyll_site/en/2023/02/06/function-graph-in-console.md b/jekyll_site/en/2023/02/06/function-graph-in-console.md new file mode 100644 index 0000000..44b2129 --- /dev/null +++ b/jekyll_site/en/2023/02/06/function-graph-in-console.md @@ -0,0 +1,147 @@ +--- +title: Function graph in console +description: Let's write an algorithm to output a graph of a function or a system of equations to the console in the form of text. We will use Java tools. For calculations. +sections: [Geometric figures,Text image] +tags: [java,math,fdlibm,console,rhombus,square,circumference,circle,text,image] +canonical_url: /en/2023/02/06/function-graph-in-console.html +url_translated: /ru/2023/02/05/function-graph-in-console.html +title_translated: График функции в консоли +date: 2023.02.06 +lang: en +--- + +Let's write an algorithm to output a graph of a function or a system of equations to the console +in the form of text. We will use Java tools. For calculations, we will use the `Math` class, and +to bypass the range of coordinates, we will use two nested `for` loops. We draw in the console a +graph of a circle and graphs of a rhombus and a square inscribed in it. + +Graph of a function with filling: [Drawing heart in console]({{ '/en/2023/03/08/drawing-heart-in-console.html' | relative_url }}). + +### Equations of functions {#equations-of-functions} + +We will check each point `(x,y)` from the output range of coordinates for belonging to function +graphs and output in accordance with this. We define the parameters of the functions and the +output range in advance before the start of bypass. + +*Rhombus*. + +{% include image_svg.html src="/img/rhombus-equation.svg" +style="width: 129.297pt; height: 14.944pt;" +alt="|x-a|+|y-b|=r." %} + +*Square*. + +{% include image_svg.html src="/img/square-equations.svg" +style="width: 167.599pt; height: 62.7502pt;" +alt="\begin{cases}|x-a|=c\land|y-b|\leqslant c,\\|y-b|=c\land|x-a|\leqslant c,\\c=r\times1/\sqrt2.\end{cases}" %} + +*Circle*. + +{% include image_svg.html src="/img/circumference-equation.svg" +style="width: 162.53pt; height: 17.9328pt;" +alt="\sqrt{(x-a)^2+(y-b)^2}=r." %} + +*Parameters*. + +`r` — circle radius; + +`(a,b)` — figure center; + +`c` — half a side of a square. + +### Mathematical operations {#mathematical-operations} + +To perform *basic* mathematical operations, Java uses the `FdLibm` library — *Freely Distributable +Math Library*. Most of the methods are implemented at the platform level to increase performance. +We will refer to them through the `Math` class. + +For floating point calculations and rounding of results, we will use the methods of the `Math` class. + +`abs(a)` — absolute value of the argument `a`; + +`pow(a,b)` — raising the argument `a` to the power of the argument `b`; + +`sqrt(a)` — square root of the argument `a`; + +`ceil(a)` — rounding up the argument `a`; + +`floor(a)` — rounding down the argument `a`. + +### Algorithm description {#algorithm-description} + +We take a range of coordinates on the plane in such a way that the displayed figure completely fits +in the printing area. We bypass the selected range with two nested `for` loops: first along the `y` +axis and then along the `x` axis. We check each point for belonging to the graphs of functions and +output. For clarity, we also output the coordinate axes, the origin of coordinates and the center +of the figure. + +```java +// radius, figure center, offset +int r=12, a=5, b=-1, gap=2; +// boundaries of the text image +int xMin=a-r-gap, xMax=a+r+gap; +int yMin=b-r-gap, yMax=b+r+gap; +// half a side of an inscribed square +double c = Math.ceil(r/Math.sqrt(2)); +// output the title +System.out.println("Radius: "+r+"; center: 0("+a+","+b+")."); +// output to the console line by line from left to right from top to bottom +for (int y = yMax; y >= yMin; y--) { + for (int x = xMin; x <= xMax; x++) { + // check each point for belonging to the graphs and output + if (Math.abs(x-a) + Math.abs(y-b) == r) + System.out.print("z "); // rhombus + else if (Math.abs(x-a) == c && Math.abs(y-b) <= c + || Math.abs(y-b) == c && Math.abs(x-a) <= c) + System.out.print("* "); // square + else if (Math.floor(Math.sqrt(Math.pow(x-a,2)+Math.pow(y-b,2))) == r) + System.out.print("o "); // circle + else if (y == b && x == a) + System.out.print("0 "); // figure center + else if (y == 0 && x == 0) + System.out.print("+-"); // origin of coordinates + else if (y == 0) // abscissa axis (x) + System.out.print(x == xMax ? ">x" : "--"); + else if (x == 0) // ordinate axis (y) + System.out.print(y == yMax ? "↑y" : "¦ "); + else // empty place + System.out.print(" "); + } // transition to a new line + System.out.println(); +} +``` + +Text image in console. + +``` +Radius: 12; center: 0(5,-1). + ↑y + ¦ + ¦ o o o o z o o o o + o o z z o o + o o ¦ z z o o + * * * * * * z * * * * * z * * * * * * + o * ¦ z z * o + o * z z * o + o * z ¦ z * o + o * z ¦ z * o + o * z ¦ z * o + o z ¦ z o + o z * ¦ * z o +----o z --* ------+---------------------------* --z o -->x + z * ¦ 0 * z + o z * ¦ * z o + o z * ¦ * z o + o z ¦ z o + o * z ¦ z * o + o * z ¦ z * o + o * z ¦ z * o + o * z z * o + o * ¦ z z * o + * * * * * * z * * * * * z * * * * * * + o o ¦ z z o o + o o z z o o + ¦ o o o o z o o o o + ¦ + ¦ +``` diff --git a/jekyll_site/en/2023/03/08/drawing-heart-in-console.md b/jekyll_site/en/2023/03/08/drawing-heart-in-console.md new file mode 100644 index 0000000..0b17a70 --- /dev/null +++ b/jekyll_site/en/2023/03/08/drawing-heart-in-console.md @@ -0,0 +1,304 @@ +--- +title: Drawing heart in console +description: Let's write two versions of the algorithm in Java to output a heart to the console in the form of a text image — let's congratulate women on the eighth of... +sections: [Geometric figures,Font rendering,Text image] +tags: [java,awt,console,rhombus,circumference,circle,text,image,font] +canonical_url: /en/2023/03/08/drawing-heart-in-console.html +url_translated: /ru/2023/03/08/drawing-heart-in-console.html +title_translated: Рисуем сердечко в консоли +date: 2023.03.08 +lang: en +--- + +Let's write two versions of the algorithm in Java to output a heart to the console in the form +of a text image — let's congratulate women on the eighth of March. Let's draw a graph of the +function in the form of a heart and, in addition, draw the symbol *heart* in the form of a +picture, and output the picture as text — console congratulations on the eighth of March. + +## Heart shaped graph {#heart-shaped-graph} + +Let's draw two half-circles and one half-rhombus, filled inside and outside. In the previous example, +we output a [function graph to console]({{ '/en/2023/02/06/function-graph-in-console.html' | relative_url }}) +— we take the formulas for the circle and for the rhombus from it, and in this example, to fill the +figure inside, in the formula instead of the *equals* sign we substitute the *less* sign, and to +fill the outside, on the contrary, — the *greater* sign. There will be many conditions, unlike the +previous example. + +Let's draw a picture for clarity. + +{% include picture.html src="/img/heart-graph.png" background=true +alt="Heart shaped graph — is two half-circles and one half-rhombus" +caption="Two half-circles and one half-rhombus" %} + +We output the upper part of the figure, the lower part of the figure, paint over in a +checkerboard pattern and output the coordinate axes. We get several text images that +look like this. + +``` +Radius: 5, in/out/axes: true/true/true. + · · · · · ↑y · · · · · +· · o o o o o · ¦ · o o o o o · · + · o * * o · o * * o · +· o * * * o ¦ o * * * o · + o * * * * o * * * * o +· o * * * * * o * * * * * o · +--o --* --* --* --* --+---* --* --* --* --o >x +· o * * * * ¦ * * * * o · + · o * * * * * * * o · +· · o * * * ¦ * * * o · · + · · o * * * * * o · · +· · · o * * ¦ * * o · · · + · · · o * * * o · · · +· · · · o * ¦ * o · · · · + · · · · o * o · · · · +· · · · · o ¦ o · · · · · + · · · · · o · · · · · +· · · · · · ¦ · · · · · · +``` +{% capture collapsed_md %} +``` +Radius: 4, in/out/axes: false/true/false. +· · · · · · · · · · + · o o o o o · o o o o o · +· o o o o o o o o · + o o o o o o o +· o o o · + o o +· o o · + · o o · +· · o o · · + · · o o · · +· · · o o · · · + · · · o o · · · +· · · · o o · · · · + · · · · o · · · · +· · · · · · · · · · +``` +``` +Radius: 3, in/out/axes: true/false/false. + o o o o o o + o * * o o * * o +o * * * o * * * o +o * * * * * o + o * * * * o + o * * * o + o * * o + o * o + o o + o +``` +``` +Radius: 2, in/out/axes: false/false/false. + o o o o o o +o o o +o o + o o + o o + o o + o +``` +{% endcapture %} +{%- include collapsed_block.html summary="Full output" content=collapsed_md -%} + +We bypass the coordinate range with two nested `for` loops: first along the `y` axis and then along +the `x` axis. We check each point for compliance with the conditions and output it. In the upper part +we draw two half-circles and optionally paint over them inside/outside. In the lower part we draw a +half-rhombus and also optionally paint over inside/outside. + +```java +/** + * @param r radius + * @param gap offset + * @param in filling inside + * @param out filling outside + * @param axes coordinate axes + */ +public static void printHeartGraph( + int r, int gap, boolean in, boolean out, boolean axes) { + // boundaries of the text image + int xMax = 2*r+gap, xMin = -xMax; + int yMax = r+gap, yMin = -r-yMax; + System.out.println( // header with parameters + "Radius: "+r+", in/out/axes: "+in+"/"+out+"/"+axes+"."); + // output to the console line by line from left to right from top to bottom + for (int y = yMax; y >= yMin; y--) { + for (int x = xMin; x <= xMax; x++) { + double[] circle = { // two circles left/right + Math.round(Math.sqrt(Math.pow(x+r,2)+Math.pow(y,2))), // left + Math.round(Math.sqrt(Math.pow(x-r,2)+Math.pow(y,2)))}; // right + int rhombus = Math.abs(x)+Math.abs(y); // rhombus + boolean inCh = in && (x+y)%2 == 0; // checkerboard pattern inside + boolean outCh = out && (x+y)%2 == 0; // checkerboard pattern outside + // check each point for compliance with the conditions and output it + if (axes && y == 0 && x == 0) + System.out.print("+-"); // origin of coordinates + else if (axes && y == 0 && x == xMax) + System.out.print(">x"); // maximum of abscissa axis (x) + else if (axes && x == 0 && y == yMax) + System.out.print("↑y"); // maximum of ordinate axis (y) + else if (y > 0 && (circle[0] == r || circle[1] == r)) + System.out.print("o "); // two half circles, top + else if (y > 0 && inCh && (circle[0] < r || circle[1] < r)) + System.out.print("* "); // top inside + else if (y > 0 && outCh && (circle[0] > r && circle[1] > r)) + System.out.print("· "); // top outside + else if (y <= 0 && rhombus == 2*r) + System.out.print("o "); // half rhombus, bottom + else if (y <= 0 && inCh && rhombus < 2*r) + System.out.print("* "); // bottom inside + else if (y <= 0 && outCh && rhombus > 2*r) + System.out.print("· "); // bottom outside + else if (axes && y == 0) + System.out.print("--"); // abscissa axis (x) + else if (axes && x == 0) + System.out.print("¦ "); // ordinate axis (y) + else + System.out.print(" "); // empty space + } // transition to a new line + System.out.println(); + } +} +``` +```java +// execute the program and output the result +public static void main(String[] args) { + printHeartGraph(5, 1, true, true, true); + printHeartGraph(4, 1, false, true, false); + printHeartGraph(3, 0, true, false, false); + printHeartGraph(2, 0, false, false, false); +} +``` + +## Text as picture and picture as text {#text-as-picture-and-picture-as-text} + +In the previous example we [drew a simple captcha]({{ '/en/2023/01/04/drawing-simple-captcha.html' | relative_url }}) +— we take the font rendering algorithm from it, only this time we draw a binary black-and-white +image in a monospaced font, we do not use anti-aliasing. The symbol *heart* in the form of a +picture looks like this. + +{% include picture.html src="/img/heart-monospaced-plain-22.bmp" +alt="Symbol heart, monospaced font, plain, 22" +title="Symbol heart, monospaced font, plain, 22" %} + +Since the character is in the middle of the line, half of the pixels in the resulting image are empty. +We bypass the pixels line by line and output only non-empty lines, that is the central part of the image. + +``` +Monospaced.plain, 22, symbols: ♡ + o o o + o o o o o o o +o o o o +o o o +o o o +o o +o o + o o + o o + o o + o o + o o + o o + o +``` +{% capture collapsed_md %} +``` +Monospaced.plain, 28, symbols: ♡ + o o + o o o o o o o o + o o o o +o o o o +o o o +o o o +o o o +o o + o o + o o + o o + o o + o o + o o + o o + o o + o +``` +``` +Monospaced.plain, 36, symbols: ♡ + o o o o o o o + o o o o o o o + o o o o + o o o o +o o o o +o o o +o o o +o o + o o + o o + o o + o o + o o + o o + o o + o o + o o + o o + o o + o o + o o +``` +{% endcapture %} +{%- include collapsed_block.html summary="Full output" content=collapsed_md -%} + +We draw a line of text as a black-and-white image, then iterate over the pixels of this image +and print them as text to the console line by line. We draw only the central part of the image +with text, do not display empty lines of pixels. + +```java +// draw a text in the form of a picture and a picture in the form of a text +public static void printTextImage(String str, Font font) { + FontRenderContext ctx = // font rendering context + new FontRenderContext(font.getTransform(), false, false); + // get the dimensions of the picture with text when rendering + Rectangle bnd = font.getStringBounds(str, ctx).getBounds(); + // create a new binary black-and-white image + BufferedImage image = new BufferedImage( + bnd.width, bnd.height, BufferedImage.TYPE_BYTE_BINARY); + // turn on the editing mode of the new image + Graphics2D graphics = image.createGraphics(); + // font for rendering, do not use anti-aliasing + graphics.setFont(font); + // draw a picture with text + graphics.drawString(str, bnd.x, -bnd.y); + // disable the editing mode + graphics.dispose(); + // output the header + System.out.println( + font.getFontName()+", "+font.getSize()+", symbols: "+str); + // bypass the pixels line by line and output non-empty lines + for (int y = 0; y < bnd.height; y++) { + StringBuilder line = new StringBuilder(); + for (int x = 0; x < bnd.width; x++) + line.append(image.getRGB(x, y) == -1 ? "o " : " "); + // draw only non-empty lines + if (line.indexOf("o") != -1) System.out.println(line); + } +} +``` +```java +// execute the program and output the result +public static void main(String[] args) { + printTextImage("♡", new Font(Font.MONOSPACED, Font.PLAIN, 22)); + printTextImage("♡", new Font(Font.MONOSPACED, Font.PLAIN, 28)); + printTextImage("♡", new Font(Font.MONOSPACED, Font.PLAIN, 36)); +} +``` + +The last example uses the Java AWT library. + +{% capture collapsed_md %} +```java +import java.awt.*; +import java.awt.font.FontRenderContext; +import java.awt.image.BufferedImage; +``` +{% endcapture %} +{%- include collapsed_block.html summary="Required imports" content=collapsed_md -%} diff --git a/jekyll_site/en/2023/06/20/password-generator.md b/jekyll_site/en/2023/06/20/password-generator.md new file mode 100644 index 0000000..98e1b44 --- /dev/null +++ b/jekyll_site/en/2023/06/20/password-generator.md @@ -0,0 +1,90 @@ +--- +title: Password generator +description: We write a program in JavaScript for the formation of random 20-symbol combinations of latin letters, numbers and special characters. There are 60 variants... +sections: [Cryptography,Random combinations] +tags: [javascript,text,symbols,letters,digits,characters,combinations] +scripts: [/js/password-generator.js] +styles: [/css/pomodoro5.css] +canonical_url: /en/2023/06/20/password-generator.html +url_translated: /ru/2023/06/19/password-generator.html +title_translated: Генератор паролей +date: 2023.06.20 +lang: en +--- + +We write a program in JavaScript for the formation of random 20-symbol combinations of latin letters, +numbers and special characters. There are 60 variants to choose from — 4 columns of 15 rows. First +character — is always a letter, all characters in each combination go without repetitions. We will +generate passwords for web-sites in the browser. + +
+ +
+ +
+
+
+
+
+ +We use symbols in the range from `!` to `~` and their decimal codes from `33` to `126`. + +{% capture collapsed_md %} +``` +! " # $ % & ' ( ) * + , - . / 0 1 2 3 4 5 6 7 8 9 : +33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 + +; < = > ? @ A B C D E F G H I J K L M N O P Q R S T +59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 + +U V W X Y Z [ \ ] ^ _ ` a b c d e f g h i j k +85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 + +l m n o p q r s t u v w x y z { | } ~ +108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 +``` +{% endcapture %} +{%- include collapsed_block.html summary="Table of symbols" content=collapsed_md -%} + +Three nested loops: by rows, by columns and by combinations. We get a random number — character code +in the range `[33-126]`, then we check for compliance with the conditions and add to the combination. +First character — is a letter, all characters in each combination go without repetitions. We escape +characters `&`, `>` and `<` for correct display in browser. We gather 15 lines, 4 combinations in +each line. + +```js +// gathering combinations for the web-site +const generate = function() { + let text = ""; + for (let row = 0; row < 15; row++) { + let line = ""; + for (let col = 0; col < 4; col++) { + let combo = ""; + for (let len = 0; len < 20; len = combo.length) { + const co = random("!".charCodeAt(0), "~".charCodeAt(0)); + const ch = String.fromCharCode(co); + if (len == 0 && (ch >= "A" && ch <= "Z" || ch >= "a" && ch <= "z") + || len > 0 && combo.indexOf(ch) < 0) + combo += ch; + } + line += escapeHTML(combo) + " "; + } + text += line + (row < 14 ? "\n" : ""); + } + return text; +} +``` +```js +// get a random number in a given range +const random = (min, max) => Math.round(min + (max-min) * Math.random()); +// escape HTML special characters for correct display in the browser +const escapeHTML = (str) => str.replace("&", "&").replace(">", ">").replace("<", "<"); +// button on the page — refresh combinations +const refresh = () => document.getElementById("combinations").innerHTML = generate(); +// after loading all parts of the page — refresh combinations +document.addEventListener("DOMContentLoaded", refresh); +``` + +I have been using this algorithm for a long time — I have all passwords for web-sites generated by +this scheme, so I recommend it. The first version was written in Java, but for web-sites it turns +out easier in JavaScript, so as not to go far. diff --git a/jekyll_site/en/index.md b/jekyll_site/en/index.md new file mode 100644 index 0000000..f92e51c --- /dev/null +++ b/jekyll_site/en/index.md @@ -0,0 +1,50 @@ +--- +title: Code with comments +description: Notes about programming with code snippets and comments. Problem solutions and solution descriptions. +sections: [Problem solutions and solution descriptions] +tags: [java,javascript,algorithms,implementation,text,fonts,combinations,images,pictures,cryptography] +canonical_url: /en/ +url_translated: /ru/ +title_translated: Код с комментариями +lang: en +--- + +{%- assign articles = "" | split: "" %} +{%- assign articles = articles | push: "Password generator" %} +{%- capture article_brief %} +We write a program in JavaScript for the formation of random 20-symbol combinations of latin letters, +numbers and special characters. There are 60 variants to choose from — 4 columns of 15 rows. First +character — is always a letter, all characters in each combination go without repetitions. We will +generate passwords for web-sites in the browser. +{%- endcapture %} +{%- assign articles = articles | push: article_brief %} +{%- assign articles = articles | push: "Drawing heart in console" %} +{%- capture article_brief %} +Let's write two versions of the algorithm in Java to output a heart to the console in the form +of a text image — let's congratulate women on the eighth of March. Let's draw a graph of the +function in the form of a heart and, in addition, draw the symbol *heart* in the form of a +picture, and output the picture as text — console congratulations on the eighth of March. +{%- endcapture %} +{%- assign articles = articles | push: article_brief %} +{%- assign articles = articles | push: "Function graph in console" %} +{%- capture article_brief %} +Let's write an algorithm to output a graph of a function or a system of equations to the console +in the form of text. We will use Java tools. For calculations, we will use the `Math` class, and +to bypass the range of coordinates, we will use two nested `for` loops. We draw in the console a +graph of a circle and graphs of a rhombus and a square inscribed in it. +{%- endcapture %} +{%- assign articles = articles | push: article_brief %} +{%- assign articles = articles | push: "Drawing simple captcha" %} +{%- capture article_brief %} +Let's write an algorithm for displaying text as an image using the Java AWT library. Symbols +and font can be any, but for this example we will use a combination of uppercase latin letters +and digits with the *Comic Sans* font — we will draw a simple captcha for a website or blog. + +We'll consider special characters, but we won't use them, because it will be difficult for the +user to guess them with such a text decoration. For example, the plus `+` is still possible +to guess, but the minus `-` or the underscore `_` is already with difficulty, and even if you +guess right, then to find these buttons with difficulty, especially on the phone. Therefore, +for captcha we'll use a combination of only capital latin letters and digits. +{%- endcapture %} +{%- assign articles = articles | push: article_brief %} +{%- include main_page.html articles = articles -%} diff --git a/jekyll_site/img/captcha.png b/jekyll_site/img/captcha.png new file mode 100644 index 0000000000000000000000000000000000000000..051f9c300bd80554c599362b52fb096ddc10d9f1 GIT binary patch literal 6789 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 literal 0 HcmV?d00001 diff --git a/jekyll_site/img/circumference-equation.svg b/jekyll_site/img/circumference-equation.svg new file mode 100644 index 0000000..7eb554d --- /dev/null +++ b/jekyll_site/img/circumference-equation.svg @@ -0,0 +1,38 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/jekyll_site/img/heart-graph.png b/jekyll_site/img/heart-graph.png new file mode 100644 index 0000000000000000000000000000000000000000..cdae562bb5e4b5b1dacc2ee6523baa2a30b4df59 GIT binary patch literal 22659 zcmb?hV|OMzw5@G>YPVB&+D>ghwQaXk+qP}Ho!WN$)V6K=&U^1)xF52ToRzcB4%SMt zlW>5XI3gS#90&*qqNId~A_xem?tcje;-AHgv?m?}gj~l%S=~v|z?InkkDaN7wF$A4 zyS)jqiJOHf2#DKSQA2T1v(_Y_*d|lt&{I6c07(PDR-ru4J zi*8NRKRrG=L+q}7y?58Ga6XcJTLItgo@xBwhF;!3oAnqUoWbYA9`3)|XI|RlhNgcC zy=ADpw)@{+*@-E8RPxK(cyG3Qdscql_AK~(-SK~P{H@?Qmzy0jYCo+vzhz~a|DJ@Z zP5*|Pz?*yL((t@K4-C~9Uh0lsdd>ZEx<3Emx^cb^Z4nfl!Gjr+BZxD||J?FLR+(xatbd+=sp@O*sk^%VQOx$J!Xu1na{W71fnRk!lzpP~__xM@3f2(7?>)3mxI3Dg*qnq;4hW46@PvVoxQ;+VW`s3%lqqi@Q zFMhmc(xQtGC|08H1Im~`zT%2O5I)dT$AEbdL%ynWxV50`Ib(5U-y=NnVL2GT(s`ZS zw^6hl$8O}>2===BbJI3h4DPa*%4o+>(B$#5_Wkz9cWTeKeZHrPKFQ0IcQuaMLIf*< zsNDCV5(N21EjvZQ6@$Y|_YD8xJ>AscNaJKQED}F|Y8ux^yp~Yaf!o-JtX?wV*_ZBm zxThHudKAnHSWXZa+@QL!J|X@sZq#qh2-a3iA)o{6c-kafABjUN=XUw7H^&hL2uKYkzioq~Vp}Hi)s=TRr?c97xaiY3$ zP4nF5VP&dMpel6(Eak$%psI9W}@xoL5E#UXRu z?~1YGs`;IWia_zSC{dp4v2lL7wP>v=)3*C?Y2CJG-*ch6;LUNtCwe1SoWV#X^@v>k*ni3_v$YyIbf-5@R)yFqJrhQm5e@<0JOmn+PKZJhj-4frBk*wzNCz5*~hcD zJarch;+i$ec3ep%1|GhtKeH47j>9+V4zjH!Igh5|W)m>@Uw~aa!308)m3Cl!aV4%y z*#qJ=eT~(sFV0kR{jKD<$rHu26JZ@mE@AtW$`p-{qa8pWdzgisY^Zf(CN`ZG#oX~I zRUD)IqK;{0MqmLL?IX(l`Mi2t$ck1?>6^(XWy%GJlNn_=;#h{3){lcFN~4J-^(W#! zUiJ97ysLT0#J!N)W710{t%C$;MD?n|^v1Yz7n&1Vz6LlrY*s?iWiX2w49A7J*y>qn zkXB?|@CYhZQc2i0j{_--;*@*|{jDJJhJ- zy&`!s9>0xOK%62k-Kiqw`kcsU$yE?r>@r8RHOe3&`i29-Qj$w>umzEFt^;JY*4m)~ zWBn9>Zy;Nc!cf(eN89hR9d4#5Jw1@t2NBacFHZ9^= z5(ZWEQg@!PhE`jSoh}p8xr{>b45QIjdGyrVn{&U}tvrib@Gn2XqxR}6u0KNfweHB$ zn#0|p*V}Y$LoiFPW>JSUpE-kR?Eda7pUwb;+aR0zc$5MW}IQWBUFZ zW2hNyD_ZLrLief7~)+!nkD7 z!)+WZMcM6GD$ROuu>cD0*!0kEpQjP^{8V6imD;`F6EP2!4QyUesgdrevO7>B!5cGb zm44UtF~=(YQnX0sbwM)yFsR!E5L-7y3gGEANl(AY<%O!r;qr15jk0n*k41N*)Z#0J zAa{@*Y+*?h%48_GlMIfDFwi3A zl}AS%B%;6~M0Do636!>R7Wsn-HfAkRUQyGpldOxy7BGaW#Ni9BgQJq%8_o@mfyI&| z4NE;}NzzpUAm&1b{Ek1JP-roV{0hXzw6OF+)pS!Nxw8q*GY{LrMnz18|B8f~kk^y9 z_eS(Np=g>n{Jyh9f#vhBbb!B$9u+c|Sba2ii%ULlM6^y#hI+z%h|i7U>T6kN*MLW! z2=%U+i;fRojE79#=+7t!OyAsskEvw9Jac2x8f_fXf}@w=tpPUypWaBk25SjvjMSL} zDXp^&^O_n*2qd{S@_J8TrxrPoehQp~4N^P`u&35QV+#bdCpFkAv5uCm|&M znTF0r&o4kXWl8w4$}*}`(GK1uTqi-S#R(#u-U2*F12}gY)8=|M2WErVvz(8Z;pic2 z6*-@6qG0u^QVx0nzZ2v84~XR6LOP3O;=IUkTr!DfNJFQ%*Co`&n*;g;mQ>X3(s zXr~vNA7cJ7OQUscg45nM!xC!;-3tPEAU8+4DDtzhj3%>i;{{ppT zuvqUmhg3p+{&i+*p17alU_z^T;(uhoNO)bvinA|wtxymY#kTXb$Rxd)`q9j_^(=># zixqp5_JfI4Px6x3$U(xV8Ol!`kr|9^;Gr^fhltL*S9IxdOakdFu_ebJ;5A`yCzqFQ zW>9zFGaQ?8z)JK)wQK@yguS9d(;`>5!#IW@JOA90`6G}4e9H9rKz$I*ex*XNWmgF5 z?~CP12^d?L$V$0OtP&#!;jcjJG&fO?!|GVR2_yakwZbpdMWx6`Lk}omGAI?4Ky!dP zZ!^9n==F1eH4BnmRP3CzMq>*@kUWsc5Udw$F{Us%>7${~vro7tJ)41zRssFnJR%8I zj(--o`#@Kd-Zq_4D=gF$Xxa^}s~b#CoI^A(xfI~z?rtm*R??k!mn|x0IO^?@gi|v{ zT+Kqa@uPS^!VhVQL?FlufQ<&xooAUtGJu*VM68L*qPnE4q7Aj!C)$hu+XfR>i2W=1 z$vTG#OvF`E5CbjDovC3}fdlLl$>DdMumEqlQ+c2VVMe?5DzR0eB-OFtUqhhr$QJfr zsDQs1mvtwkf*au(KX-SzNvT(yyw|KZj3hA{ky`h+dH-7C5Y)JlxDG2k}!N#;gj0EZNlp|uc7LVP>aKCg!Om^1WeU_Z4+H*H{Rb(2*3A6d5m*xhz( zxsUhO8?5;38Ybl(vwSH=Qu?^{6?i4OiDM;?<$`!!9clp*=0X1mVZS>41;jc|DE&Qh z5p^@MpcDZGkd`yO28}TypTj7RuQ*48?D4j!?JP)AVRV53wtMlfVjyI3*4ZAJ^J|l(CjH{nbug zSOcWK!B?O#EN8rCnq9>|mzd4tm)S)3-49teOG;6old!i)ln@z?)DoJ5`^4%7&)&`V zYSgKeYjhJD%DTWqE{=ui+j$B3+F8edigHCBDoyz#;YI*pjSqaZRPi&0?CyAjL9fms zl~U(}3l6a+4@WIVVZ~!4Oasvij}7_6Qpib&6QxWwAs8j>-R8Xd`)9~NJiH^)8w*|c zau)3O0uHmsUVY5WAtRfOoc6R7d5E#7_Aia_Zm}JC;IH} zs0uCKx~R7Becb!#0i9tcy{o=o`_f5Cy1-^VdOwP_<^tR#84lkHDi$;%CVf?U!5h@mX8 zbjOZgou!Ood0^l)B7&E+sQiWX-4wt#jevMo`fO7U@epr42F2Iwb;Ghvz123qBA$Z- zH8FK7#Ss_R4c!IfAdj=HPR{rpf!+3tIZ$`YM3Ow2{==hP=9Hi)D{jP1KiWp{A&`q3 zt1Mr5A#-TTG>a{U$VZ{a2n3$qMmaIfGIk=iJMM93TFz36bP4A%*~EU7#pxZTB0m3E zlsGsK_Q7ZIE*veQ7j?^_B{W25NTxX6IBkj|9g9%N!srU%$Lfx1K!CXp5?E~_KT0I2 zpTrOr8wI@{i%SGTWR1Jq6yzm za}GsEWDvyXLNF?XlDTOn9gitRJp~+al|oj6%q%lY#`b#!nv3-&Xf9QM;N9FE$+0ciT53iRrjp{U*=a|eEb$=_-_pr zcSSX$GGCD~xgs<=;KiMzM=(M}UmCf6B<=8=&~Q=cSDwGzl627v@t>q+0we_E@eN7`8GrQ`RN7t|OZd_9lIsZt3f@4fGyNd$beTq@2g%w^j3Fk}&aU1NwSTE3%qODM9G$ED%n+D8C`JtDP zs-tU^Iu6^+FROYYcY09paL+5A_+MNR=rZtHhs;oX$O_vCC{xxrB_#&=4uAbr8|1h4 zCW;O<6aGy!Q|hF_N<3G&@6pAb2qUC~T+BTO@@%RuZCP}J60E>;dyCc0{Hj4TM zmvxeUDPHwZ_P33sld@&6c+6A^J7k^8fp0$|xA8koG7{$1 zDlqsgvPu>BM(-PE+}NZ=9I8-c%b1qRzu31c>d>7^5azWmI0hAmjoZR-$t6lWYqE38 zXuW|CnJZR+P)~USrp16XFTy9X-<<9&k}k99iFJHyS_$M-udoFAe+@_yTL(mKZ0b9fUPw$)Fir9z6MKID{SaaEK6BHFzyIo*_;> zzZ2$hULsk!9Q%+%l^`TOgq<7mWMPYF@q9cj?K$MG`e4(oDNN7X)MptDo&Yl!`~=Q>4dy!N>lg@3O|O+ZCA6XUsw0#S-|NG= zik$qRwI?kV%_*=Q035aq33-6GOdtt|@JTc7ZObE>C5opYua)T|Csn!~LhGv}It@-2 zvC=)uHzUM!>{Wg2MTsEBwtuinK{&Na$>p*PA+|k9evj_U*eA1NA~3y=VrYw$lra#C z?-p;f0nu@USP6XNiuy1-r)jQG%mCAH135&rEz|_0_79(8nbSCNuknRgV@}==3i%b* z#YPFb?Jhz;eZ*?FDPEG?=mISAS|r4x-AgW%89}z!mEAkeSFFGzIs6meA)(qi0JAq;f0qb#2>flW3GWVUjL3M@ z3j--@y`prn@)oJ&T1k_mEG?$uV#V^R5(!Rhzm@uNwDrnwjWyZqy)WyO4E=+a5 z%6$$T=Q(5)ks}%dvVXFNiV$OBO+?wqXhjpz|3ou(8!1vYF9#Qg@IolETKv-I(V_BM zNLQ^UK?UFHk&ae#-oz`xr3_nhoiONtp;M8h4Pq`vB$0>^Z1=X)EvJMOZ6_lhR!aN;BuoOVP^>#Fw;Q}fP&f8klt#4eIHPkvtk>w=4)K?u zNaEux_f3C+I)eqEui`E0kN?3?gcN}W^PMue%xemjBdTvbTek{f&E?YdIx&)`Bx08* z^iLD7hE=Q&TP}xkmf6*WLAHhz$mBquq?QPp#P$15SQXqUldMmNl3-p4q}bigQZ2}% z_*xJ-IcpPK=_vR_sBdKrRF|*c)J2Oj|93(vs4kujFo&l9Q%9pLt2zBUMUL49 zHdZmxBLpE&q|u@SAG|P5~>Ji;I8Y=N!r=gef7LV4G|Miu!_gJQk?NR7NKPs>pe0 zn0}%5V0ASoF6tA8V0%az=M@jlZ7(g6b;1L)+$oWQ+;bFTqfgMzcuewyQZp zL3RUL6sXWkfI7F57NtSMC93K}w2xYwY19(QvPPACO&NRxLZAd78y3eY$|VI%~T;e=sZ;Lu)RBolS%S@&S<|$dIvV?PRDT z3l*jPql($X+m?iAs}&6zYjbt9p7R@$*Q0jiAsP$==A(XLQ(fhqM7pdN$O&?L${kc6 zMGI}-q?dJ3<7pwmd5uj<0$5TZ6sVK~J$o*bW=p6Xgud(`URC~y5NNK7iHl_92duk! zd)b)6&dx#3G-(=&!Zy(HD0q0J#Q8B#R6uL)j<@-xUR-W`D9@p;Y!_pdD~%CAUH;+G z(zYPzkH8pSG+~x8GhFo#I6mYQVWv}vA5TY*EpMHp86Lgr?0oVxhx)`Na?vWQvU#$D zFNP!=v4*dT>22JI*Ajy~9N}h;ia(XbVNN`a2Z!$q#PqkM4o^U2Gsk~gP8PP!FKXzk zB2&S9VPdfCLFUlLNQ@^KPAlZBjc>W>JC7qP^hxb*dG>8GP_!@4digDp*V<>V5p5DN zbwEYEEaR8G#?2Lz-*x>=d^Zmwru7lsLS-?59h z<)jspd+%#vFp|N6rrBGlmji9wUM}hv2GlA6;S2LI+x=a_3~LaKhVhJ)bghX9oY?&` z!{zn8mmNs(=YHFQv;;4WsIb{3%J)ls?oWjVF z%*H}MkbFw zvGd#o!0Ji|f5!kck^p76v%(hfwySws(tbMdk0QYN*F z*2M2?cUW2BH(ylgP1;{;q=n-JbFGmjr%bW@1c3zGlyzPJgrfNvIK^RIBPGb3fxV!x zL)!dcXAiv=8z55%-GI3N0lLas-`QPEN^yjJ(XaB9yRLhIYB{`bTx7M6CTcI=7b()BWb zlW+ke1)Nm3HeQ0!LapFdBb%d=d!~+(iPMQ2T&T4B77kF3E=N#n({NABV8MeXi$wM0 zno)R?7Xk!W^kCoAa$lD)lEErBT?)kei`JZk>4^}=bW3HgjV7)e1c*4E(1$FGB>Iu%tn zq-_m2LR}zEA}FFZ!SQ8vJ-UTGojFIz%R4wlqg+HPv!yUtae^j?=;wOzoxrpJBk?oh zSpw~W>kM?@b)Eqglu+>Ui74XG9tb7Lsf|8`c!M$8Nnp^BG&(ODvx?Q6sv+Yv7-2tu z@OU?1U>f?Tx`TvLX1hw*t=qyDHa8ESi#0&1%Z$i8 zwvLA>*@z8C8_v7KwZ5G-N#+0WGX@>OeJq&rZDX=I4=@GL;FYJMjsCb~b4tnv@*xgV zo3u3`B^4BPNofO4iWeID#*KS8iqmtq<;kG4`;7c=nFQbINc#RDt>^L9m^Ci!93er| zdoZlg`o~7lx`sD)JkdGjf%x<>iu7`kR^tzIlBYU}i44a)TJMRreJV74Iv32zl`7GI zU5b+~GYldbc75W9K{vw(A*njx*V%F#;w@;V5X|rYkj&iqMlQ|&f%499KW8PRP*&P) zzvTgwylT@K2A54>wil;&VaQU!YB>=s105|XYfqxkU$J+ih#Wa-E^jyq9y6Qn^!(8?cNugG2 z6Kk4e;y;0#WcedN1Z@$q!uibtJ=@WNR9f3M|3@N<3oacpa>HXAy{v;;fP1i12{nJT z)*c(g+&CMI6mZ<{DtgSY5ESx3xFnqURKfIES+6{f%4_PNVSI!BlVvMG}O!ROr#SC(^aygK?L=v&5KI#jrDRa%WxM-?`;3y!I=Ni-QY*eCUF{n*hEH zbn_5_p;c#_O=-uxHdKwoZM2z!?7kPd7KwBO=ObaN5A)tC#{=E$CpSNsY?y%x0^UU| zTYnqBTg(t74PtmUn|@eQQJX(u9|jDezvl|eKYgBzM6kuOErs9e=tWCT6!RsCkhX+Q z$dyo2D+fTsDKhU)W9;A;G=kJp)&-z0R~QMpfr6*;RFVXQ+-3CLLS&kzZH;}1VG=Gy z7ey%{urv42J#jEE>>E8f*o$QBfB{KqUGZa_TURytj)-5of`p5ONToStJi=5#i>B7x zkJ$jAu%|ZsDbD#huKOdf;2`eO_ioVdohL`DXg z8BTKI%`liF5a7cbn7ZdqwuB3ptk*zK#Xo$1@mPkU%BKhfE>KsRHfbLRXN#)1>KQgn6`s! zx0NrgCrGV1IO*BPLAl@AeUb)#J;UJnf?M|n4H@b>6rur28lr21Q%qugfaFfplO~Ol zLTQZc$wzn6iwdhA)jlES5vLx?r(i>!#a)uTf1y*+Ul4MaL-uU+?J7|Hct~v zyLZ*SR>#aVe=JHJAN>%PM$BZC)%ZpE8RkLxuQy{|oepV2~0Hsc~bypY(A*c?8 zW-@bBJ&ptHX6J=7#Nq$eu7P8=dBr%IKK>5wCqMICs+46dy|4KYJ{GK>Ice&7`G|Xu zL?+n%wLRv>>oO4K<;?BZ#x7*snz{^PA-KxtTqXtmHRdm@;ht@GBkjpM{hUDaZyT`r zRWnnEB8ZP!0{kh|DJd>aSw@6=BnBG&*9Ju5ubEz1{LFxrS_E7_Ph?JPpnR#11U-cp z=mQ3!fJxV5D=FPD=JOm>DBgesz-Gtgk1UU(TB#l+mU{bcA0?U zl5a)&xIZRu{Lz8$6Uc6#_}J}tbI-gP=nmL?-Z|!dduQP($&alND5l)FvI3NvFK(_) zvh9*?^S1PC;n?obZOdlMI5lo&-!pW+Z)abN&<|2O9GEFA-9$+98gBEbq!tA13#h## z6ug&8p=JAQ@RW|BTR1lCq-E$B@WiOdyd9A0!|k#2}I) zg34}dm)V|P%E#`s0?S1$o0KwgDJ%BDTGa*yzf63h0p;ATsy<_kt0O-+|g=d-Hw zkhx+;=P>DTX?4%?azZ5~o@V%?KtxcPx;|`#Zom4r!0_G;R4@_>HjYy3C#~Xs+c>_9~Hfmn-e8eE|)Yk zk252cfR{BpM^$4^ee18wz3gEKcP z44)XyKDV)l(geZ-lxPo!ZCqi3KZINbf~hEpRof5KUMpDQgGR{&~K} z^pNlNdX1o?)I(02L|7HvEaxVbynAeY3LRaq3GVY8f2?}4h$Nb&9cmf@8w$A~zMhDX zPQX(xUM82Ex*A>Qm8_6jq$%-~u?^WRz0zqXm5ey&5^WSuXq2;)N~n1-B{om@B1C>A zMF=s-w=Hk}MYbD9i!m!GIVZ22Kc7(4kg>7Ps={5GcEx#$8fbaRn+$V3G^I zS<4~Wt@?OZK6KYK?^J8>tDHyFENjbKtvD~tvkOjOJ&VH2E_OQkG^rrN1dR%}6NSr|A| zJpbA4s(4$kjn`C=ISx{n)?s6na_#Arx(I(VdDmW4Ao>+y*GkJMXKLZUbr5&Hxt=aWgkr)eAM!==!%zBZktKD= zK1B@_&2qhYejFmQ5!anHc6$$x7>M;1+QRZnbMNaqA*-``V?r=5Y&MU!By=(qYK>Yy zAe@D;uIlf>qb---@cwV;mt0-hlOGYdpuLFMuf|(sYZGu%t%9;2T*qC!`L3}7HPoZ} z8JtFGy^w3}S^Uy@j50+tpYM;^p`FzqnD?F4UfJ9BSPsy`aq^j!wM=P8Qy87tjhVH# zma$29w?HmcdFhkQ3D6e|BQ8vjYv%)p9CS)qGP{yft%*Bbq#p(8^*d3aB3@Cyk3B&! zcKMZhi7LyO8c4KmEX;5)<`(w1#rAJc{n~v;Y#I%!#St_b;wxQF<3~JUVYZ7+vuDAQ zD`lh?s>*UW!g2qGc4As4k$*O}R6+6jpAt!DN8r?kIcj0^w&Qrb}iQ(^n}9 z@*-$j+c%nt4;WNY6sVUR_|MOkqqdkE&)pGdZ4Q+e6r0ZW4BlVxnpHq2p}Ozb-IDIdRxESUFq% z3lw)0&6ByT>5T90h!E^QHcyAQJcs{UXf*LeY@ny>Vu?5x&ujJ#AK;HGghf@#G*JRk-%sq z&RRXD?QE9~f<}4B*8q;JgV5H1f8tdEP3@bnULVSvumhyC)J+PNHcgO%3?$YnZBp$q zcFT~=?nv@Y8iO?fzx-ItmOK`Xk|_nhE!^<8*j*&Z2T665zQ~S9#9Us>N(KfjRDjjy z9FgqQbR})~)K5p*fL9XW9TxwcDy4gZH^H$Pi20F*oz+fMnMrZ8;*j1AJO;P?AV2jL zuc4LmQJ7Xa=31qyC}wKy3vn}x0^!VjJFoXW8Jm)V&CQhJ_E`PDLhds<1htMc{f)&q*VD>sj$5uEB(eo*DTvs^90 z6X-nJt4AerN2_)w_(ZD?oejbyKw597#Ur;(2IB68FoJE$&`wxzbG71B)UNrgPSrf8f~PTL z-1)blC$D4j;j026z^cVDdwwp7Z4=>`R*SPvTrsjkqfPArU8Q7b(M9r_{I>g)8?Q=n zBFf^F(l$7ytPOv0uRU*y_foJLR-sch@m-z8>`bI(q;J__3bzV0-y)_HoY=3Tu{0AG zV=?1luv@u2k~{7|g#fN|G&0KWwCKu<@LKF|E0Ve3b&I+R_wH7MY|HaI;CI<(1v1w| zF^3I7)@3P6b}IR&F-Yaus_oOO0dHJQU3G<%0(7D$2D{|Q2D|ng{l2z`ZLd?;oQ->| zWe?FgT}bh(WkN0D*l?S238V-AgWF5RY`>sQ`;&CSmV_p2&b28RZ#vKkcM4GXM8y#7 zscjnKB$F8BPGdLIs3;5D(xOp&e)VNu9ELRdBdtZHSTidR(r7K82E2Pu93wU9fJ53b zD}C0&VOqN3!<06q>IhQw0Z<3l^(+X3hfL*6=E2vPHR7z{$<81N-zL|sirg>e#n7kW zTyKZCP3Qh;JXVQYfq44HTOrp0VW-v+L*5fWT~OWkc=5S&kY$q7KK~$A7bo7N@@%Cz z$6X{tg)yJE_n1*LkdLUWEe>S}dS)RxclA}Pe8`RFA>)D=9IB$UP_hs1{-I3xV(C@Q zgoUJfp(E|qL4=p~SvAA$5b$2!YeL%@D}+RX(k@zbPZUSUD}PQ zxSLnHbdwo4NGJ1U zP2mg8va2^@DHjX<*?%m?6_ZtHGI-ea#`>t}N4|S3y!@(@6*IEe`thT&=dq}uJRTck zmW<@SZ`tZoE7n6XVPT;qSE%J&+kVL`v?fJ{b)jS+{9qA-VHw^-OvT%)X5t)RCD(8; zj>G(KjY?E5sDrb@CauQLr{HBhd0q$zhU`M>Wn%JntA=c4f! zFh2KB7t{BIN4h4o`_073vu5&Ag{9Isk-0TXZ(C^H!r7h*GTU26F%`@@-eGwq_a@KB z?+DItlZ<%OXXgF|Foie2`uN+?iof%s_jKo_L2FY?`a}wI59!3V_++85ZXNq3J|`t< zDjRXaN`4BjY5||yc67Z`xV$@?a()4JDn>lsv$q|BEDl3yOwXTuH`%qJY8;$4B2kVv z_{DC+^>oCx_#66@6JrcB;#+=th5XIiU;>_RJtYA-{Dt-!OX8v6+qNwVet${-U~Zv= zzz8B56!A*Ub@uHVNkJHG!iB_pfPp&;-%Z5$Q#{SwFb&wg-G7x2-1ha_8LNbOX3D>Z zw7Z+8$+1V6Sy8wmd`pq6kn8i%8Y5a(GzRmnpVVAJ*mUUSw zbm$Uxs;LyOp@?kCJza&(69N-wDpeO*`;J{rNL`8*vlo(qS*BAnm^#YIq!}ffmdo8= z59Jlk%r}uT%sEf(D)Yb~<-sH^`wFR%2dUDO#q0_dW#%Dr9Vrt~<#JWXt9PZYMRW56 zJn~n~?R8ZK`*=}SrRuye%Z0g9XN4w4x{`hFa;)s7hOrmNM!uV<-=?O*c9a8IF~yU( zh#hWBgkrR00X>!Jqk>-{b`Bx&8yRy@CqGxNbvFk2AO5_bZ?wF3d3{LtZTb9|HWg_t zyuAhc0Yqg?oR$@#HYHuAVt+8{z@Iq0U0=;2<`VuRA1EOu&5vW*$&X`=Sz^uY8LReP*nV%+N4Ray8IXFB z-LQ7%lmlTRKy;5$>}pe31xVbQwg^levTnU1i$ARma`Q=My0+Pz@wWgMFv%*6^6Tzq z=ZI5?x?K!oh5qv0WNBbBo1IkPc5kK%`!R&RuH-YG~lYkp+IU zFS3z2pYH8=(QW>Ph^nl#D|Y)0B0E}I?1sT=c-ecOEL+}ewT0HXuF?zeQ|eBK*>g=1 z1#=cYzjypipV>#T8)L~XuSxLh>UPrBqfxfLM!$L7S+_go;f3PUW(^BMZ2I+r9$~!m zs4E(OQ?Kdw&XnAK`Z^8FX{L+eN17SeM5;zoja-;6!#Aq2JjNfo?34=&s;LCV@f*=& z^2mCQ0i~H?fze~H0I@y65bYc3w@m>)>>`Tj{qqu+fy}0-nkkhGpiWJo2d&#<%~}(V zaz2%_WOs9^(L_<$maMs8)6n4&UOT;(b=;un_XAZwt@9SxXW<@3k~;qfcnbLo}*ljUk+T%rn44-{~%=*ah@wbF;K z$kmFal9>9!WpSeG8sQ9&15)+2`ZUwHigq(GhdWv(v4uRWlD*W9Y>^^$X1m%gd!5q` zb$b`A#g>6wXH}Ig=OGOd~-9)w8KlaHmujGIQnh3vYTm zC_Av+&02lXHGSFlz2<`K8ap7ItG;*B>)F#Y)L z2PGg7ISP?zYTJ3KRz20j&ln}_4=rCZi=o9%RSS z5(aDDPz>qtpNg@hpBnQ`t%Ms5jnQ5=n3_FXm+OlYqJf-iK^M(fDccO3?jR%)Qs({jq`|-7U zq6UB90JT_rs+b-S?^y4)m4a6n?3$+JuK9jUZ~B|?LW8y3o$du(d`pu9vF~-dw85oS z&l)D#V@ss%o@3Cj$DTD*cULcDh&vMPyi&-mR~^4B$jY;J4j;n1$Q!UDW|wPQSwJcqJ@%XKSW^P;{gT2wj3cp>{8k^P4(f;meNN9T|VCQ zx4}R!C7OxUy$`d*2MJj~-}?zoGUBwaV)^nbmm(3hdgS#o z&jHSjP!KLJdum|M0t82zPxcg{+f%D4db$<6r`ORVa_XZ@8j{P{P0S98Ary!d zKNP7}NjVG{9!EesgrO2TxD;YN$6>@s7%}G)##`bP^_$&5*$W?e{9x=E^G!jHDhM{D zjFac1z);AGfdV0NpNYM_yR9VXiSIem{8ajWn2I{;O2-fAi7j1#{cG2lu>3rF(#X%m zZh3K;(-6HsAhzYPr!CPfB#J8L2>zqkFZK^FvnH-|5Zds+D?LtM7=T-48C;Q1KuHh%|7)DfBX1k4@oG_@4YZlg)5kL-NGIm;(ELHKS@r_XP3JvC8Zq(y`{wZjcG z?XyhbWp**t429ar7rMLbfq^A&z(ushHxR1RRZ& zq2%=s8#c#u)eH*|F~z897|rnc2>B>YxjO|pMR7g{+y2rVv)@>+_cpX!=ZN55aiDC%s7(QN}+(F zjsC&Q_k=aqjGQTcR(nEZSWzGf%uocCkI7%r7U}Bpy0nbF);mg)~CrX-SOC|BRp zbEp;{gI}#!;s6in6|R*ARQ-(Yn^i;IG1?e>+);ea%NEREnt}LF7J&BMs}G z6ell^FeN=Ox7Fk&-X@fUBkCr=3td*6yv%iK11amO8z?8@-rqrmxbXtlGxCFpc{ere zz7y!NM7fVjFdE>5-@+6T%bZY;0T9TEEIi)**9%#U1Z1~=f{YBP&Jwpp<(M7h5d z_BIA3GFEo0ksvhsrj>@p(P4xwB?aZ$tcg6fF z2sZ`J2xJN2(Hw{w4*r`$!P_$kIr1uWkL*`2iBQuXN}ewSe1Zk#o4&syz6y`7T^2wD7OZ0oMShEb7a~8%E#kaTjIOir4ET zr8N6$W?)?uCirR0Hov=G5zFQ7@RxJlaihIm`xJ7`ro6_p37E|w4btTSW)&;*T^P?D zrpptL@Jm)n%r8Df4CV+r?Gngb(>;t`1+Yf@aQK7Wd!-C&eFD*_L+OuY{i$E$J?_uN zZgeI0v5XwFmhK^oS@Kh{1*bBHmHExAgm}-?{73vy+)Mesna{dNw-kIMFGNWBF}2ZX z1EtAWYkoLRHz_lldB zhRrt4^xpPO9Ro18?J*bB<^(o*S|=1ZftepF5v;-h$cEQNdo>a-xjVet`wM;5;IA>y z6um?E14vF;pi)a_qV^GW$1h19#GOSy(s1m2MZPKTuKx+k7c}TNv|IQQTWKW5jr=Mf z0SajZCNPdd@(>;q8RzWH7?4lC+u3{gf%(!K&EuRRN;AGAcRnIU8{pB#c}~#4IgSuz z0wcMO60YS6FLHv8aaf~>@_D{VA?JvZ&lMVZpLc2JZ0K7$-Z_S1;0FFC2*bn>gr>m?!1r}-p})Nz3G_(UmWJmlsZu^~5Nkm1`JBJNb;`Itm#gjBUXsihgJN{?xWE+%xEk$mk<-?$D%v_yy1pc*Yr{%SQzbF@^i>N!1_YQdr_ zDmKPiO~C`@>qKhpp!B+{BrZ$T$ZEU# zxYR_AsT%p(s47+H1xXF*YyLXW1zxWPebIgGO?@hbBATylic9i!L94sB9n>Vv?fz?3 zt~J`L<>{(|PF*NZhdT3iw#a{exLOx$zI;XXmX_%q`HJ^99=o5jt&^niv~{=YH1|D| zIC$qwwaC{p0IeRr&WtM4TLE?BdNpm0xvTY4UC_#&WK={oiiNvas!S_Yt3~~(qRbU6 z;%(R6H1kIZfyO+(&V9OTSgO5l!lRa{UXA8*3(*uSk2|dTqJ+IPOi0>KeJzEwy77J& z0iREk@#G$rvWRkb#c|g0*Xatgd#kmN63+E_2^}25uu4A91vnp4q-cd}Cr?@R$=^n(} z6OKNhcyiXOR!WNLJ-w)%imNew4Y3EXuz(rD9yVW93Cq3!GX z%<6q@kgo%()U%4JZ6F?_;h^uGdaoOOuVnUysZ7Tt9n#CXtQO5rRa}~-sHCREeqLH7 zX>T&;)-&?eqBnI?zI+|%eai8WNuyE+L!0RLGVSTmB-P5-MMZV3ABsFG%UpGnmycC-IF2%Jnlh2%9bl=;gC8;lxx>GSp_1(;d^^#ss z>htR*?dkaanydE|molRPRj&6!Lt<7B$HYsOYK?qJhjXUssYt8D zX=t+cg$#yu!((k~o-Qe>RoW(BP3fC{k%?XIkp%j-r|PsyTJs)~bX4V_sX8qws?~#E zv@}z3N%i3fl1g=^V|;fKSF=shmV{%s>qR{{#4F#H5Or4%8+Fess#Pl2B<+)QF>~v0 zI_P^i!FXuOgx|sWd4J-p%qiL-U%rkHwo|tjDlX}Ac*Ca2x|D1PMhmoOXsO!Ep5v9k zV31A^PcDV-RGoai+o3izOGhOgOQm0WrhmW;UFa6Hcxb5oj4IV$#Wg?l`ZdKRy)roc zAYX51NkUFiKVnh0q`CH? zjR_OvSY)E2+C8MBPhTJS0&x3Qx7$LpAQMN!+4mVJJZHhr6x&r;aj1nB%RDQ&8t$aS9?gx!;%7{ zDQc9jpZ1n6s)!b-PEt$m(05vom|Jt^WBe&--g-$F63DwzQcZS|R}rlWg@zi3OTwzq zPHnt;CZ8tjsG9XGIYWRcs_91G%emt9Q<-WdH6(Z&UY4{on^q4+^lZpHYt4*Xa*)?r zu1UGG@^<(gPIb%nJ(3#%LzU^gq?Rtnm?GLGU$12qbyp>rW(RVL963OO1v;NV-Zm}E z1re)MCnUwg%dqxHdNGTr`%K8zl}@H-s+u7zCQ z$?_|%a1Y$eAzfS}X>C?q-Tk^0il)7rL*dt)r0*=9O`z}5>|W+n?Iyi%?q>5oHSAXw z(KBib#V(&3u8t5N9`Z(XcQ?;NT{bUunxe+Unp;J5SiT+|b~!#CG8lZVGIJp_f}f$o z3G{8zifk3Vrs!g#&8sq~38i+VyO9(pPt?RSMTW zs9bfKsDSk8_)3qZZ>NZSiB>i6J9lFx7EC)wQbzCtm9mZ>#?^c5j z>b{gS^O5*+Lo zv#+CyCaXn~uN5Kkz9(Og4Vw*qH1xQf&+w4^f@Y zCU?7LIh%Jy=-5k=4rF?VuZW%qd3Kxhq#;BWJ^qr@M zgbrt$v%&SQJJsG12nZePrIJFGTB~!4%U4UH$2uJ}bES)&l6fUlt@;ef-=ez`;%?Dw z2zu&H=PG+XbZoVx9SIGfs;?oLlr}| z*}l_tD8cp}_Mow*I#*ekyf1cjehiF@J=&%R712~(l;0J?&|1ai%a^aXE@|7q8~>?H z?*{I%ELo2Xz2BH(5zL%scSUJY2I)R+lKC%H{lZ-By9<(yi(FxO?DN& zzS;&h6GHQYDR-@U^uu8=QTy-E*JGe>j z0LSntq!_$f7ST*0zc!GK8h5y1tL^-lzt6HEP)6Qf=)0N+LetYq_znJb*yuaXO)o16 z9cuhBPB3%+9sV+1WB&}9ur}}3xdgxO z`r**Ny2piN!{OnTl6LoQf2v&d;q9F!>AYf^2P)MrDcQlqR^}dkXC~0M zUE4CReceD6>6q>dHes*Hzgmk^-L7`EcSYG0(NXzYuZSK}yV{d$bEPW16r|wnv}E(# zq;Iuq66VRjo$-uFHEeLeS)7$9HR*jw+^?bVzYYzMctvVtrz- zFh#UOQcN*PHAzvoRO^EWrq+_pbDO?X9Z5Q)ZW${UQmNibC{$|eq>|Q1iYq25uKfwY z;Ce~hL+2>d$>8&+66a`Mr@Pz~v94xHliVik zba3REgogjq)v9=SPLS?Yvm{@wic4zJmcDufb?ZEKz$zs^2=p!0+E60)?#yi5G%ukK zn6F3NjG+aJt3f}};)Isd5k)jpF|~&2sWNSpRI8bqs}u4i?Yi2-r-&X4ePb?-elqk! z-`7IZ%eQADWbDe`y8_jYbXqRyVB!^MmUifj8YMMpdN(t`*9BF9CTW{u^7VG_8db|f z1yPrBY50MmKPpYpJAv^K*GofBUzKTFZ~0t$MSfTIg4@`=qgMCc?sJNtkkrzV(V$Xo zRa}0!^I4#E9}gJ}b(&+;{b12|QfLF!o{8qvnxyyLw*^C?6wzVD!wKh#XtgeBvz93; zDVkUouN9ISJGHSY^pcu-hAQ-MsA=9=bB}#6w{Bgztr?lTpbgGRRuQ7=QANW(4^7rt zozN7`b}K_mxbLuLt1X;^riiM#yUesWWS}%^uHESmCVeMsb6^=p``){!<@;WdBpph4 zfEv^s){R;tU+*fS74j8VyQB+UcCR8@+IJ6lJs6}tO7+RO(+s&v--sRyS%;^(qFF=o zZO=Q1w}`eCB)YoXw6bFwz( zpve1X0#)CX^lGvipr!c9m9 zdG1L;la=P|Ywc%DZRORC&8az2&d=iMiTTb>T$yp>uPw6|)|{T59# z1#T8A>*93vc}lA2#|{yAW;%ebbyq=(bd$QXECJRle4z zaJ~D2$1SQ&1Mi_@_#n}Dal%Bhchebl(>6&;=$8}GUisQOXsvrFv{JlmE%VHDSQB!a z#dR=!+RTx6ZNj(h>n$%V$t!(5w8gw^l@f;T$?9>vogS~RBCdvQN7wlj(JuMnLD~a2 z+bf}hrgp8gjhX>anYM(qZ!A4AHuS=Wj$~Vy%S}jnnGK* zO{qAeRN>-a>w1v)RPs&wb&oED9;g?xXHb_R`{+9-q4c+C$k&Nv z9}3+Snsel9oAqKa^r!1Ch6pq0{*t=iHFR3qgS-cmGaED|MBSKYrfTjPjHRi?&NI8bhqob#*~5zH zfxyRb)>`Hn2130@Lwo$Vo*TI5K@YfM_9qjLzAEX>B>W!7gQ(x-aFvCjZ{(Q>fWPP8 zX$~Y($sGfxuTMJ==VZdsi->qX)Sby6^D#G! z>%?GY!{G7%_(3ojtB6UueE^@$cg91=NKh%)0j*c1ZWTY^Q|?6*4`96*3@K^fX|9k` zlFlXS(LO)-oZIY$f)py#DMhc=z)6*QC&<`s)_jw_Y2Fm!J;4Glx);~+CrP($jv~Or z4;?F}0H4NQsT*M)Uvgsy+jxZ~CVL}q>hxX2bF{e^B~&H3aT|pP_@RM@P=rU~P{A(h zKExLtb>pn!+cx;m$eSL_=21sqk1vy4o!c(tG;kNLV>Be_;!a!l391>{@q*X+fl+sG zC@>N9(7m<#A^H3%B|r(^Qi=Diem``r`~TR`u@)dkOE>D4@lX7`)4JjS4|&^7=B9bm zN8df(9n5#uYB7JBM1!dDj*elf7838Kivj+ETiko$2=lzxg!~?^(cl)rB^~-FWX(yUv6+e5y=v z#NB5SHVh?wrv@{P&v(q}xyt)qaDLtep<@p!rURi4I^}vZs0zNU`%Surp{DPopmR+h z3F2DW3wfVbylc9!O0_PaZcOVeW7m)w`W|qkJFdxBdCy~xy!VEVEmT}J!C@8Ar=d|6DClT!&TZO6|KkMbuc!y!60aWl6wW%_A;l=aYQ< zDsA;U!o$B9G6xFr5QV@G_y!}~(I`)Q>rAGGOlsdLt{a)JojqGqYk{+84}}J*JS|^u zs9ei~kuJVYYK65CF=U>;cRLcs^>+7#Uw61Npir%_UXXN3FN8Ya957qgFg)aq=n-eR z`r4MLeKo~VG~6ZfHA(SMqtXW5ZQULX!$;r^&OGq7J4|0qb?r-bh0KPPA>v9GG~Wyc z+prBM7P~@CkDK{nC}>)hTI`mKkn?vZBJNyX~gpoJ*(1Z}RCI};MZki2IGaHP&xrhshe8!j{ zjJ&yrMnqAl-IKw{n=`l(4|<<9Y#0jI1kCiI5Yg+bVS{PjY{&KFm)g^@ScaEW}N&@y_BytymUmcjy&Kj`%Dx@-dsbJ7NCtICJG~Ou3;rl(@cqrv>ApR)@2}V@M^S+ x<80@I$s$W(T?zV>PQ+wjFc=I5gTat>`2V&xOL0X*U6=p>002ovPDHLkV1ga556%Dp literal 0 HcmV?d00001 diff --git a/jekyll_site/img/heart-monospaced-plain-22.bmp b/jekyll_site/img/heart-monospaced-plain-22.bmp new file mode 100644 index 0000000000000000000000000000000000000000..e2a1af5d52f0aaa71819914aac524823b50886dc GIT binary patch literal 170 zcmZ?rUBv(ac0fu4h + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/jekyll_site/img/square-equations.svg b/jekyll_site/img/square-equations.svg new file mode 100644 index 0000000..5827257 --- /dev/null +++ b/jekyll_site/img/square-equations.svg @@ -0,0 +1,76 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/jekyll_site/js/password-generator.js b/jekyll_site/js/password-generator.js new file mode 100644 index 0000000..8552dfe --- /dev/null +++ b/jekyll_site/js/password-generator.js @@ -0,0 +1,29 @@ +// собираем комбинации для вёб-сайта +const generate = function() { + let text = ""; + for (let row = 0; row < 15; row++) { + let line = ""; + for (let col = 0; col < 4; col++) { + let combo = ""; + for (let len = 0; len < 20; len = combo.length) { + const co = random("!".charCodeAt(0), "~".charCodeAt(0)); + const ch = String.fromCharCode(co); + if (len == 0 && (ch >= "A" && ch <= "Z" || ch >= "a" && ch <= "z") + || len > 0 && combo.indexOf(ch) < 0) + combo += ch; + } + line += escapeHTML(combo) + " "; + } + text += line + (row < 14 ? "\n" : ""); + } + return text; +} + +// получаем случайное число в заданном диапазоне +const random = (min, max) => Math.round(min + (max-min) * Math.random()); +// экранируем спецсимволы HTML для корректного отображения в браузере +const escapeHTML = (str) => str.replace("&", "&").replace(">", ">").replace("<", "<"); +// кнопка обновить на странице — обновляем комбинации +const refresh = () => document.getElementById("combinations").innerHTML = generate(); +// после загрузки всех частей страницы — обновляем комбинации +document.addEventListener("DOMContentLoaded", refresh); diff --git a/jekyll_site/robots.txt b/jekyll_site/robots.txt new file mode 100644 index 0000000..7e13783 --- /dev/null +++ b/jekyll_site/robots.txt @@ -0,0 +1,7 @@ +User-agent: * +Disallow: *404* + +Sitemap: https://pomodoro5.mircloud.ru/pagesmap.xml +Sitemap: https://pomodoro5.mircloud.ru/color/pagesmap.xml + +Host: https://pomodoro5.mircloud.ru diff --git a/jekyll_site/ru/2023/01/03/drawing-simple-captcha.md b/jekyll_site/ru/2023/01/03/drawing-simple-captcha.md new file mode 100644 index 0000000..5939c47 --- /dev/null +++ b/jekyll_site/ru/2023/01/03/drawing-simple-captcha.md @@ -0,0 +1,223 @@ +--- +title: Рисуем простую капчу +description: Напишем алгоритм для отображения текста в виде картинки с использованием библиотеки Java AWT. Символы и шрифт могут быть любыми, но для этого примера будем... +sections: [Криптография,Отрисовка шрифта,Поворот изображения] +tags: [java,awt,графика,изображение,картинка,капча] +canonical_url: /ru/2023/01/03/drawing-simple-captcha.html +url_translated: /en/2023/01/04/drawing-simple-captcha.html +title_translated: Drawing simple captcha +date: 2023.01.03 +--- + +Напишем алгоритм для отображения текста в виде картинки с использованием библиотеки Java AWT. +Символы и шрифт могут быть любыми, но для этого примера будем использовать комбинацию заглавных +латинских букв и цифр со шрифтом *Comic Sans* — будем рисовать простую капчу для сайта или блога. + +{% include picture.html id="captcha.png" src="/img/captcha.png" alt="Рисуем простую капчу" %} + +Спецсимволы рассмотрим, но пользоваться ими не будем, потому что угадать их с таким оформлением +текста пользователю будет сложно. Например, плюс `+` угадать ещё можно, а вот минус `-` или +нижнее подчёркивание `_` уже с трудом, и даже если угадаешь, тогда найти эти кнопки с трудом, +особенно на телефоне. Поэтому для капчи будем использовать комбинацию только из заглавных +латинских букв и цифр. + +Отрисовка спецсимволов моноширинным шрифтом: [Рисуем сердечко в консоли]({{ '/ru/2023/03/08/drawing-heart-in-console.html#text-as-picture-and-picture-as-text' | relative_url }}). + +## Описание алгоритма {#algorithm-description} + +Подготавливаем массив символов, состоящий из заглавных латинских букв и цифр. Затем обходим +этот массив и отрисовываем каждый символ отдельно — получаем картинку. Затем поворачиваем +картинки поочерёдно на ±35 градусов — получаем массив картинок с символами. Второй раз обходим +массив с картинками и собираем общее изображение — присоединяем картинки слева направо таким +образом, чтобы последующая картинка наезжала на предыдущую на 40% её ширины. + +Почему 35 градусов? Если взять угол больше, тогда пользователю будет сложно разгадать такую +капчу. Например, буквы `N` и `Z` будут похожи друг на друга. Если взять угол меньше, то такую +капчу будет легко разгадать с помощью машинного распознавания текста. + +Наложение последующей картинки на предыдущую на 40% её ширины нужно, чтобы символы располагались +очень близко или слегка касались друг друга — это также затрудняет машинное распознавание текста. + +## Отрисовка шрифта {#font-rendering} + +При отрисовке шрифта будем использовать сглаживание *anti-aliasing*, иначе буквы будут +с зазубренными краями. Устанавливаем изображение с поддержкой прозрачности, цвет чёрный, +шрифт *Comic Sans*. + +```java +// преобразовываем строку с текстом в картинку с текстом +private static BufferedImage stringToImage(String str, Font font) { + // контекст отображения шрифта + FontRenderContext ctx = new FontRenderContext(font.getTransform(), true, true); + // получаем размеры картинки с текстом при отрисовке + Rectangle bnd = font.getStringBounds(str, ctx).getBounds(); + // создаём новое изображение с поддержкой прозрачности + BufferedImage image = new BufferedImage(bnd.width, bnd.height, BufferedImage.TYPE_INT_ARGB); + // включаем режим редактирования нового изображения + Graphics2D graphics = image.createGraphics(); + // шрифт для отрисовки + graphics.setFont(font); + // цвет, которым будем рисовать + graphics.setColor(Color.BLACK); + // применяем сглаживание шрифта при отрисовке текста + graphics.setRenderingHint( // сглаживание пикселей вдоль границы фигуры + RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); + // рисуем картинку с текстом + graphics.drawString(str, bnd.x, -bnd.y); + // отключаем режим редактирования + graphics.dispose(); + // возвращаем картинку с текстом + return image; +} +``` + +## Поворот изображения {#image-rotation} + +При повороте изображения для сглаживания будем использовать *билинейную интерполяцию*, иначе +будет много лишних артефактов по границам изображения. По дороге пересчитываем размеры для +нового изображения. + +```java +// поворачиваем картинку на заданный угол и изменяем её размеры +private static BufferedImage rotateImage(BufferedImage image, double angle) { + // переводим градусы в радианы + double radian = Math.toRadians(angle); + double sin = Math.abs(Math.sin(radian)); + double cos = Math.abs(Math.cos(radian)); + // получаем размеры текущего изображения + int width = image.getWidth(); + int height = image.getHeight(); + // вычисляем размеры нового изображения + int nWidth = (int) Math.floor(width * cos + height * sin); + int nHeight = (int) Math.floor(height * cos + width * sin); + // создаём новое изображение с поддержкой прозрачности + BufferedImage rotated = new BufferedImage(nWidth, nHeight, BufferedImage.TYPE_INT_ARGB); + // включаем режим редактирования нового изображения + Graphics2D graphics = rotated.createGraphics(); + // применяем сглаживание изображения при повороте + graphics.setRenderingHint( // билинейная интерполяция + RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR); + // сдвигаем начало координат нового изображения в его центр + graphics.translate(nWidth / 2, nHeight / 2); + // поворачиваем новое изображение вместе с его системой координат + graphics.rotate(radian); + // помещаем текущее изображение в новое, чтобы их центры совпали + graphics.drawImage(image, -width / 2, -height / 2, null); + // отключаем режим редактирования + graphics.dispose(); + // возвращаем новое изображение + return rotated; +} +``` + +## Рисуем простую капчу {#drawing-simple-captcha} + +Обходим массив символов, отрисовываем и поворачиваем каждый символ в отдельности, по дороге +вычисляем размеры для общего изображения. Создаём общее изображение и после этого ещё раз +обходим массив картинок и дорисовываем их по одной слева направо к общему изображению. +Возвращаем пару объектов: текстовое значение капчи и картинку с символами. + +```java +// отрисовываем массив символов, поворачиваем их и объединяем результаты +private static Map.Entry drawSimpleCaptcha(String[] symbols) + throws IOException, FontFormatException { + Font font = Font // устанавливаем файл шрифта + .createFont(Font.TRUETYPE_FONT, new File("ComicSansMS.ttf")) + // устанавливаем стиль и размер шрифта + .deriveFont(Font.BOLD, 32); + // размеры итогового изображения + int width = 0, height = 0; + // подготавливаем массив картинок + BufferedImage[] images = new BufferedImage[symbols.length]; + // обходим массив символов, получаем картинки + // и вычисляем размеры итогового изображения + for (int i = 0; i < symbols.length; i++) { + if (i % 2 == 0) // отрисовываем символы и поворачиваем изображения + images[i] = rotateImage(stringToImage(symbols[i], font), 35); + else + images[i] = rotateImage(stringToImage(symbols[i], font), -35); + // размеры картинки с текущим символом + int h = images[i].getHeight(), w = images[i].getWidth(); + // высота самого большого символа + height = Math.max(height, h); + // последующий символ будем сдвигать на 40% ширины предыдущего + if (i < symbols.length - 1) + width += w * 6 / 10; // берём 60% ширины текущего символа + else // ширину последнего символа берём целиком + width += w; + } + // создаём новое изображение с поддержкой прозрачности + BufferedImage captcha = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB); + // включаем режим редактирования нового изображения + Graphics2D graphics = captcha.createGraphics(); + // обходим массив картинок и дорисовываем их к общему изображению слева направо + for (BufferedImage image : images) { + // отрисовываем текущий символ в начале координат + graphics.drawImage(image, 0, 0, null); + // сдвигаем начало координат на 60% ширины текущего символа + graphics.translate(image.getWidth() * 6 / 10, 0); + } + // отключаем режим редактирования + graphics.dispose(); + // возвращаем пару объектов: текстовое значение капчи и картинку с символами + return Map.entry(String.join("", symbols), captcha); +} +``` + +{% capture collapsed_md %} +```java +// метод для внешних вызовов, возвращает случайную комбинацию из 5 символов +public static Map.Entry drawSimpleCaptcha() + throws IOException, FontFormatException { + return drawSimpleCaptcha(5); +} +``` +```java +// метод для внешних вызовов, требуется указать длину строки +public static Map.Entry drawSimpleCaptcha(int length) + throws IOException, FontFormatException { + // получаем случайную комбинацию символов указанной длины + String[] symbols = getRandomString(length); + return drawSimpleCaptcha(symbols); +} +``` +```java +// получаем случайную комбинацию заглавных латинских букв и цифр +private static String[] getRandomString(int length) { + String[] symbols = new String[length]; + Random random = new Random(); + for (int i = 0; i < length; i++) { + // 26 заглавных букв и 10 цифр + int rnd = random.nextInt(36); + if (rnd < 26) // буквы [A..Z] + symbols[i] = Character.toString('A' + rnd); + else // цифры [0..9] + symbols[i] = Character.toString('0' + rnd - 26); + } + return symbols; +} +``` +{% endcapture %} +{%- include collapsed_block.html summary="Дополнительные методы" content=collapsed_md -%} + +## Тестирование и запуск {#testing-n-launching} + +Алгоритм получился универсальный — отрисовать можно почти любую строку и почти любым шрифтом, +но с длинным списком исключений, связанных с диапазонами символов юникода и типами шрифтов. +Вариантов много, тестировать долго, а упрощенная модель меня вполне устраивает. + +В завершение этого примера для визуализации нарисуем строку: `SIMPLE+CAPTCHA+1+1`. + +```java +// запускаем программу и выводим результат +public static void main(String[] args) throws IOException, FontFormatException { + String[] symbols = "simple+captcha+1+1".toUpperCase().split(""); + // Map.Entry captcha = drawSimpleCaptcha(18); + Map.Entry captcha = drawSimpleCaptcha(symbols); + // сохраняем картинку в файл, текст в консоль + ImageIO.write(captcha.getValue(), "png", new File("captcha.png")); + // System.out.println(captcha.getKey()); +} +``` + +Картинку из этого кода см. выше: [captcha.png](#captcha.png). diff --git a/jekyll_site/ru/2023/02/05/function-graph-in-console.md b/jekyll_site/ru/2023/02/05/function-graph-in-console.md new file mode 100644 index 0000000..198ed0c --- /dev/null +++ b/jekyll_site/ru/2023/02/05/function-graph-in-console.md @@ -0,0 +1,145 @@ +--- +title: График функции в консоли +description: Напишем алгоритм для вывода графика функции или системы уравнений в консоль в виде текста. Будем использовать средства Java. Для вычислений будем использовать. +sections: [Геометрические фигуры,Текстовое изображение] +tags: [java,math,fdlibm,консоль,ромб,квадрат,окружность,круг,текст,изображение] +canonical_url: /ru/2023/02/05/function-graph-in-console.html +url_translated: /en/2023/02/06/function-graph-in-console.html +title_translated: Function graph in console +date: 2023.02.05 +--- + +Напишем алгоритм для вывода графика функции или системы уравнений в консоль в виде текста. Будем +использовать средства Java. Для вычислений будем использовать класс `Math`, а для обхода диапазона +координат — два вложенных цикла `for`. Нарисуем в консоли график окружности и графики вписанных +в неё ромба и квадрата. + +График функции с заполнением: [Рисуем сердечко в консоли]({{ '/ru/2023/03/08/drawing-heart-in-console.html' | relative_url }}). + +### Уравнения функций {#equations-of-functions} + +Каждую точку `(x,y)` из выводимого диапазона координат будем проверять на принадлежность графикам +функций и в соответствии с этим выводить. Параметры функций и выводимый диапазон определим заранее +перед началом обхода. + +*Ромб*. + +{% include image_svg.html src="/img/rhombus-equation.svg" +style="width: 129.297pt; height: 14.944pt;" +alt="|x-a|+|y-b|=r." %} + +*Квадрат*. + +{% include image_svg.html src="/img/square-equations.svg" +style="width: 167.599pt; height: 62.7502pt;" +alt="\begin{cases}|x-a|=c\land|y-b|\leqslant c,\\|y-b|=c\land|x-a|\leqslant c,\\c=r\times1/\sqrt2.\end{cases}" %} + +*Окружность*. + +{% include image_svg.html src="/img/circumference-equation.svg" +style="width: 162.53pt; height: 17.9328pt;" +alt="\sqrt{(x-a)^2+(y-b)^2}=r." %} + +*Параметры*. + +`r` — радиус окружности; + +`(a,b)` — центр фигуры; + +`c` — половина стороны квадрата. + +### Математические операции {#mathematical-operations} + +Для выполнения *базовых* математических операций в Java используется библиотека `FdLibm` — +*Свободно распространяемая математическая библиотека*. Большинство методов реализовано на уровне +платформы для увеличения производительности. Обращаться к ним будем через класс `Math`. + +Для вычислений с плавающей точкой и округлений результатов будем использовать методы класса `Math`. + +`abs(a)` — абсолютное значение аргумента `a`; + +`pow(a,b)` — возведение аргумента `a` в степень `b`; + +`sqrt(a)` — квадратный корень из аргумента `a`; + +`ceil(a)` — округление аргумента `a` в бо́льшую сторону; + +`floor(a)` — округление аргумента `a` в ме́ньшую сторону. + +### Описание алгоритма {#algorithm-description} + +Берём диапазон координат на плоскости таким образом, чтобы выводимая фигура полностью помещалась +в печатаемой области. Обходим выбранный диапазон двумя вложенными циклами `for`: сначала по оси +`y` и затем по оси `x`. Каждую точку проверяем на принадлежность графикам функций и выводим. Для +наглядности также выводим координатные оси, начало координат и центр фигуры. + +```java +// радиус, центр фигуры, отступ +int r=12, a=5, b=-1, gap=2; +// границы текстового изображения +int xMin=a-r-gap, xMax=a+r+gap; +int yMin=b-r-gap, yMax=b+r+gap; +// половина стороны вписанного квадрата +double c = Math.ceil(r/Math.sqrt(2)); +// выводим заголовок +System.out.println("Радиус: "+r+"; центр: 0("+a+","+b+")."); +// вывод в консоль построчно слева направо сверху вниз +for (int y = yMax; y >= yMin; y--) { + for (int x = xMin; x <= xMax; x++) { + // каждую точку проверяем на принадлежность графикам и выводим + if (Math.abs(x-a) + Math.abs(y-b) == r) + System.out.print("z "); // ромб + else if (Math.abs(x-a) == c && Math.abs(y-b) <= c + || Math.abs(y-b) == c && Math.abs(x-a) <= c) + System.out.print("* "); // квадрат + else if (Math.floor(Math.sqrt(Math.pow(x-a,2)+Math.pow(y-b,2))) == r) + System.out.print("o "); // окружность + else if (y == b && x == a) + System.out.print("0 "); // центр фигуры + else if (y == 0 && x == 0) + System.out.print("+-"); // центр координат + else if (y == 0) // ось абсцисс (x) + System.out.print(x == xMax ? ">x" : "--"); + else if (x == 0) // ось ординат (y) + System.out.print(y == yMax ? "↑y" : "¦ "); + else // пустое место + System.out.print(" "); + } // переход на новую строку + System.out.println(); +} +``` + +Текстовое изображение в консоли. + +``` +Радиус: 12; центр: 0(5,-1). + ↑y + ¦ + ¦ o o o o z o o o o + o o z z o o + o o ¦ z z o o + * * * * * * z * * * * * z * * * * * * + o * ¦ z z * o + o * z z * o + o * z ¦ z * o + o * z ¦ z * o + o * z ¦ z * o + o z ¦ z o + o z * ¦ * z o +----o z --* ------+---------------------------* --z o -->x + z * ¦ 0 * z + o z * ¦ * z o + o z * ¦ * z o + o z ¦ z o + o * z ¦ z * o + o * z ¦ z * o + o * z ¦ z * o + o * z z * o + o * ¦ z z * o + * * * * * * z * * * * * z * * * * * * + o o ¦ z z o o + o o z z o o + ¦ o o o o z o o o o + ¦ + ¦ +``` diff --git a/jekyll_site/ru/2023/03/08/drawing-heart-in-console.md b/jekyll_site/ru/2023/03/08/drawing-heart-in-console.md new file mode 100644 index 0000000..ef9b952 --- /dev/null +++ b/jekyll_site/ru/2023/03/08/drawing-heart-in-console.md @@ -0,0 +1,300 @@ +--- +title: Рисуем сердечко в консоли +description: Напишем два варианта алгоритма на Java для вывода сердечка в консоль в форме текстового изображения — поздравим женщин с восьмым марта. Нарисуем график... +sections: [Геометрические фигуры,Отрисовка шрифта,Текстовое изображение] +tags: [java,awt,консоль,ромб,окружность,круг,текст,изображение,шрифт] +canonical_url: /ru/2023/03/08/drawing-heart-in-console.html +url_translated: /en/2023/03/08/drawing-heart-in-console.html +title_translated: Drawing heart in console +date: 2023.03.08 +--- + +Напишем два варианта алгоритма на Java для вывода сердечка в консоль в форме текстового +изображения — поздравим женщин с восьмым марта. Нарисуем график функции в форме сердечка +и в дополнение нарисуем символ *сердечко* в форме картинки, а картинку выведем текстом +— консольное поздравление с восьмым марта. + +## График в форме сердечка {#heart-shaped-graph} + +Нарисуем два полукруга и один полуромб, заполненные внутри и снаружи. В предыдущем примере мы выводили +[график функции в консоль]({{ '/ru/2023/02/05/function-graph-in-console.html' | relative_url }}) +— формулы для окружности и для ромба возьмём из него, а в этом примере для заполнения фигуры внутри, +в формуле вместо знака *равно* подставляем знак *меньше*, а для заполнения снаружи, наоборот, — знак +*больше*. Условий получится много, в отличие от предыдущего примера. + +Нарисуем картинку для наглядности. + +{% include picture.html src="/img/heart-graph.png" background=true +alt="График в форме сердечка — это два полукруга и один полуромб" +caption="Два полукруга и один полуромб" %} + +Выводим верхнюю часть фигуры, нижнюю часть фигуры, закрашиваем в шахматном порядке и выводим +координатные оси. Получаем несколько текстовых изображений, которые выглядят следующим образом. + +``` +Радиус: 5, внутри/снаружи/оси: true/true/true. + · · · · · ↑y · · · · · +· · o o o o o · ¦ · o o o o o · · + · o * * o · o * * o · +· o * * * o ¦ o * * * o · + o * * * * o * * * * o +· o * * * * * o * * * * * o · +--o --* --* --* --* --+---* --* --* --* --o >x +· o * * * * ¦ * * * * o · + · o * * * * * * * o · +· · o * * * ¦ * * * o · · + · · o * * * * * o · · +· · · o * * ¦ * * o · · · + · · · o * * * o · · · +· · · · o * ¦ * o · · · · + · · · · o * o · · · · +· · · · · o ¦ o · · · · · + · · · · · o · · · · · +· · · · · · ¦ · · · · · · +``` +{% capture collapsed_md %} +``` +Радиус: 4, внутри/снаружи/оси: false/true/false. +· · · · · · · · · · + · o o o o o · o o o o o · +· o o o o o o o o · + o o o o o o o +· o o o · + o o +· o o · + · o o · +· · o o · · + · · o o · · +· · · o o · · · + · · · o o · · · +· · · · o o · · · · + · · · · o · · · · +· · · · · · · · · · +``` +``` +Радиус: 3, внутри/снаружи/оси: true/false/false. + o o o o o o + o * * o o * * o +o * * * o * * * o +o * * * * * o + o * * * * o + o * * * o + o * * o + o * o + o o + o +``` +``` +Радиус: 2, внутри/снаружи/оси: false/false/false. + o o o o o o +o o o +o o + o o + o o + o o + o +``` +{% endcapture %} +{%- include collapsed_block.html summary="Полный вывод" content=collapsed_md -%} + +Обходим диапазон координат двумя вложенными циклами `for`: сначала по оси `y` и затем по оси `x`. Каждую +точку проверяем на соответствие условиям и выводим. В верхней части рисуем два полукруга и опционально +закрашиваем их внутри/снаружи. В нижней части рисуем полуромб и также опционально закрашиваем внутри/снаружи. + +```java +/** + * @param r радиус + * @param gap отступ + * @param in заполнение внутри + * @param out заполнение снаружи + * @param axes координатные оси + */ +public static void printHeartGraph( + int r, int gap, boolean in, boolean out, boolean axes) { + // границы текстового изображения + int xMax = 2*r+gap, xMin = -xMax; + int yMax = r+gap, yMin = -r-yMax; + System.out.println( // заголовок с параметрами + "Радиус: "+r+", внутри/снаружи/оси: "+in+"/"+out+"/"+axes+"."); + // вывод в консоль построчно слева направо сверху вниз + for (int y = yMax; y >= yMin; y--) { + for (int x = xMin; x <= xMax; x++) { + double[] circle = { // две окружности левая/правая + Math.round(Math.sqrt(Math.pow(x+r,2)+Math.pow(y,2))), // левая + Math.round(Math.sqrt(Math.pow(x-r,2)+Math.pow(y,2)))}; // правая + int rhombus = Math.abs(x)+Math.abs(y); // ромб + boolean inCh = in && (x+y)%2 == 0; // шахматный порядок внутри + boolean outCh = out && (x+y)%2 == 0; // шахматный порядок снаружи + // каждую точку проверяем на соответствие условиям и выводим + if (axes && y == 0 && x == 0) + System.out.print("+-"); // начало координат + else if (axes && y == 0 && x == xMax) + System.out.print(">x"); // максимум оси абсцисс (x) + else if (axes && x == 0 && y == yMax) + System.out.print("↑y"); // максимум оси ординат (y) + else if (y > 0 && (circle[0] == r || circle[1] == r)) + System.out.print("o "); // два полукруга, верх + else if (y > 0 && inCh && (circle[0] < r || circle[1] < r)) + System.out.print("* "); // верх внутри + else if (y > 0 && outCh && (circle[0] > r && circle[1] > r)) + System.out.print("· "); // верх снаружи + else if (y <= 0 && rhombus == 2*r) + System.out.print("o "); // полуромб, низ + else if (y <= 0 && inCh && rhombus < 2*r) + System.out.print("* "); // низ внутри + else if (y <= 0 && outCh && rhombus > 2*r) + System.out.print("· "); // низ снаружи + else if (axes && y == 0) + System.out.print("--"); // ось абсцисс (x) + else if (axes && x == 0) + System.out.print("¦ "); // ось ординат (y) + else + System.out.print(" "); // пустое место + } // переход на новую строку + System.out.println(); + } +} +``` +```java +// запускаем программу и выводим результат +public static void main(String[] args) { + printHeartGraph(5, 1, true, true, true); + printHeartGraph(4, 1, false, true, false); + printHeartGraph(3, 0, true, false, false); + printHeartGraph(2, 0, false, false, false); +} +``` + +## Текст картинкой и картинка текстом {#text-as-picture-and-picture-as-text} + +В предыдущем примере мы [рисовали простую капчу]({{ '/ru/2023/01/03/drawing-simple-captcha.html' | relative_url }}) +— алгоритм отрисовки шрифта возьмём из него, только на этот раз нарисуем бинарное чёрно-белое +изображение моноширинным шрифтом, сглаживание не используем. Символ *сердечко* в форме картинки +выглядит следующим образом. + +{% include picture.html src="/img/heart-monospaced-plain-22.bmp" +alt="Символ сердечко, шрифт моноширинный, обычный, 22" +title="Символ сердечко, шрифт моноширинный, обычный, 22" %} + +Поскольку символ находится в середине строки, то на полученном изображении половина пикселей пустые. +Обходим пиксели построчно и выводим только непустые строки, то есть центральную часть изображения. + +``` +Monospaced.plain, 22, символы: ♡ + o o o + o o o o o o o +o o o o +o o o +o o o +o o +o o + o o + o o + o o + o o + o o + o o + o +``` +{% capture collapsed_md %} +``` +Monospaced.plain, 28, символы: ♡ + o o + o o o o o o o o + o o o o +o o o o +o o o +o o o +o o o +o o + o o + o o + o o + o o + o o + o o + o o + o o + o +``` +``` +Monospaced.plain, 36, символы: ♡ + o o o o o o o + o o o o o o o + o o o o + o o o o +o o o o +o o o +o o o +o o + o o + o o + o o + o o + o o + o o + o o + o o + o o + o o + o o + o o + o o +``` +{% endcapture %} +{%- include collapsed_block.html summary="Полный вывод" content=collapsed_md -%} + +Рисуем строку текста в виде чёрно-белого изображения, затем обходим пиксели этого изображения и +выводим их текстом в консоль построчно. Рисуем только центральную часть изображения с текстом, +пустые строки пикселей не выводим. + +```java +// рисуем текст в форме картинки и картинку в форме текста +public static void printTextImage(String str, Font font) { + FontRenderContext ctx = // контекст отображения шрифта + new FontRenderContext(font.getTransform(), false, false); + // получаем размеры картинки с текстом при отрисовке + Rectangle bnd = font.getStringBounds(str, ctx).getBounds(); + // создаём новое бинарное чёрно-белое изображение + BufferedImage image = new BufferedImage( + bnd.width, bnd.height, BufferedImage.TYPE_BYTE_BINARY); + // включаем режим редактирования нового изображения + Graphics2D graphics = image.createGraphics(); + // шрифт для отрисовки, сглаживание не используем + graphics.setFont(font); + // рисуем картинку с текстом + graphics.drawString(str, bnd.x, -bnd.y); + // отключаем режим редактирования + graphics.dispose(); + // выводим заголовок + System.out.println( + font.getFontName()+", "+font.getSize()+", символы: "+str); + // обходим пиксели построчно и выводим непустые строки + for (int y = 0; y < bnd.height; y++) { + StringBuilder line = new StringBuilder(); + for (int x = 0; x < bnd.width; x++) + line.append(image.getRGB(x, y) == -1 ? "o " : " "); + // рисуем только непустые строки + if (line.indexOf("o") != -1) System.out.println(line); + } +} +``` +```java +// запускаем программу и выводим результат +public static void main(String[] args) { + printTextImage("♡", new Font(Font.MONOSPACED, Font.PLAIN, 22)); + printTextImage("♡", new Font(Font.MONOSPACED, Font.PLAIN, 28)); + printTextImage("♡", new Font(Font.MONOSPACED, Font.PLAIN, 36)); +} +``` + +В последнем примере используется библиотека Java AWT. + +{% capture collapsed_md %} +```java +import java.awt.*; +import java.awt.font.FontRenderContext; +import java.awt.image.BufferedImage; +``` +{% endcapture %} +{%- include collapsed_block.html summary="Необходимые импорты" content=collapsed_md -%} diff --git a/jekyll_site/ru/2023/06/19/password-generator.md b/jekyll_site/ru/2023/06/19/password-generator.md new file mode 100644 index 0000000..3b6140a --- /dev/null +++ b/jekyll_site/ru/2023/06/19/password-generator.md @@ -0,0 +1,87 @@ +--- +title: Генератор паролей +description: Пишем программу на JavaScript для формирования случайных 20-значных комбинаций из латинских букв, цифр и спецсимволов. На выбор 60 вариантов — 4 столбца по... +sections: [Криптография,Случайные комбинации] +tags: [javascript,текст,символы,буквы,цифры,знаки,комбинации] +scripts: [/js/password-generator.js] +styles: [/css/pomodoro5.css] +canonical_url: /ru/2023/06/19/password-generator.html +url_translated: /en/2023/06/20/password-generator.html +title_translated: Password generator +date: 2023.06.19 +--- + +Пишем программу на JavaScript для формирования случайных 20-значных комбинаций из латинских букв, цифр и +спецсимволов. На выбор 60 вариантов — 4 столбца по 15 строк. Первый символ — всегда буква, все символы в +каждой комбинации идут без повторов. Будем генерировать пароли для вёб-сайтов в браузере. + +
+ +
+ +
+
+
+
+
+ +Используем символы в диапазоне от `!` до `~` и их десятичные коды от `33` до `126`. + +{% capture collapsed_md %} +``` +! " # $ % & ' ( ) * + , - . / 0 1 2 3 4 5 6 7 8 9 : +33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 + +; < = > ? @ A B C D E F G H I J K L M N O P Q R S T +59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 + +U V W X Y Z [ \ ] ^ _ ` a b c d e f g h i j k +85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 + +l m n o p q r s t u v w x y z { | } ~ +108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 +``` +{% endcapture %} +{%- include collapsed_block.html summary="Таблица символов" content=collapsed_md -%} + +Три вложенных цикла: по строкам, по колонкам и по комбинациям. Получаем случайное число — код символа в +диапазоне `[33-126]`, затем проверяем на соответствие условиям и добавляем в комбинацию. Первый символ +— буква, все символы в каждой комбинации идут без повторов. Экранируем символы `&`, `>` и `<` для +корректного отображения в браузере. Собираем 15 строк, в каждой строке по 4 комбинации. + +```js +// собираем комбинации для вёб-сайта +const generate = function() { + let text = ""; + for (let row = 0; row < 15; row++) { + let line = ""; + for (let col = 0; col < 4; col++) { + let combo = ""; + for (let len = 0; len < 20; len = combo.length) { + const co = random("!".charCodeAt(0), "~".charCodeAt(0)); + const ch = String.fromCharCode(co); + if (len == 0 && (ch >= "A" && ch <= "Z" || ch >= "a" && ch <= "z") + || len > 0 && combo.indexOf(ch) < 0) + combo += ch; + } + line += escapeHTML(combo) + " "; + } + text += line + (row < 14 ? "\n" : ""); + } + return text; +} +``` +```js +// получаем случайное число в заданном диапазоне +const random = (min, max) => Math.round(min + (max-min) * Math.random()); +// экранируем спецсимволы HTML для корректного отображения в браузере +const escapeHTML = (str) => str.replace("&", "&").replace(">", ">").replace("<", "<"); +// кнопка обновить на странице — обновляем комбинации +const refresh = () => document.getElementById("combinations").innerHTML = generate(); +// после загрузки всех частей страницы — обновляем комбинации +document.addEventListener("DOMContentLoaded", refresh); +``` + +Давно пользуюсь этим алгоритмом — у меня все пароли для вёб-сайтов по этой схеме сгенерированы, +поэтому рекомендую. Первая версия была написана на Java, но для вёб-сайтов проще получается на +JavaScript, чтобы далеко не ходить. diff --git a/jekyll_site/ru/index.md b/jekyll_site/ru/index.md new file mode 100644 index 0000000..01844bb --- /dev/null +++ b/jekyll_site/ru/index.md @@ -0,0 +1,48 @@ +--- +title: Код с комментариями +description: Заметки на тему программирования с примерами кода и комментариями. Решения задач и описания решений. +sections: [Решения задач и описания решений] +tags: [java,javascript,алгоритмы,реализация,текст,шрифты,комбинации,изображения,картинки,криптография] +canonical_url: / +url_translated: /en/ +title_translated: Code with comments +--- + +{%- assign articles = "" | split: "" %} +{%- assign articles = articles | push: "Генератор паролей" %} +{%- capture article_brief %} +Пишем программу на JavaScript для формирования случайных 20-значных комбинаций из латинских букв, цифр и +спецсимволов. На выбор 60 вариантов — 4 столбца по 15 строк. Первый символ — всегда буква, все символы в +каждой комбинации идут без повторов. Будем генерировать пароли для вёб-сайтов в браузере. +{%- endcapture %} +{%- assign articles = articles | push: article_brief %} +{%- assign articles = articles | push: "Рисуем сердечко в консоли" %} +{%- capture article_brief %} +Напишем два варианта алгоритма на Java для вывода сердечка в консоль в форме текстового +изображения — поздравим женщин с восьмым марта. Нарисуем график функции в форме сердечка +и в дополнение нарисуем символ *сердечко* в форме картинки, а картинку выведем текстом +— консольное поздравление с восьмым марта. +{%- endcapture %} +{%- assign articles = articles | push: article_brief %} +{%- assign articles = articles | push: "График функции в консоли" %} +{%- capture article_brief %} +Напишем алгоритм для вывода графика функции или системы уравнений в консоль в виде текста. Будем +использовать средства Java. Для вычислений будем использовать класс `Math`, а для обхода диапазона +координат — два вложенных цикла `for`. Нарисуем в консоли график окружности и графики вписанных +в неё ромба и квадрата. +{%- endcapture %} +{%- assign articles = articles | push: article_brief %} +{%- assign articles = articles | push: "Рисуем простую капчу" %} +{%- capture article_brief %} +Напишем алгоритм для отображения текста в виде картинки с использованием библиотеки Java AWT. +Символы и шрифт могут быть любыми, но для этого примера будем использовать комбинацию заглавных +латинских букв и цифр со шрифтом *Comic Sans* — будем рисовать простую капчу для сайта или блога. + +Спецсимволы рассмотрим, но пользоваться ими не будем, потому что угадать их с таким оформлением +текста пользователю будет сложно. Например, плюс `+` угадать ещё можно, а вот минус `-` или +нижнее подчёркивание `_` уже с трудом, и даже если угадаешь, тогда найти эти кнопки с трудом, +особенно на телефоне. Поэтому для капчи будем использовать комбинацию только из заглавных +латинских букв и цифр. +{%- endcapture %} +{%- assign articles = articles | push: article_brief %} +{%- include main_page.html articles = articles -%} diff --git a/package.sh b/package.sh new file mode 100755 index 0000000..78aedae --- /dev/null +++ b/package.sh @@ -0,0 +1,5 @@ +#!/bin/bash +echo "Подготовка архива для последующего развёртывания." +cd _site || exit +rm -rf ../pomodoro5.zip +7z a ../pomodoro5.zip ./* diff --git a/serve.sh b/serve.sh new file mode 100755 index 0000000..55b689c --- /dev/null +++ b/serve.sh @@ -0,0 +1,4 @@ +#!/bin/bash +echo "Локальное развёртывание для проверки корректности сборки." +jekyll serve --skip-initial-build --disable-disk-cache --host localhost +echo "Адрес сервера: http://localhost:4000"