[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"technologies":3,"\u002Fblog\u002Fskia-rust-pdf-rendering":167,"team-members":3680,"blog-topics":4022,"mdc--8floov-key":4063,"glossary":4180},[4,10,15,21,26,32,38,43,48,53,58,63,68,73,77,82,87,92,97,103,108,113,118,123,128,133,138,143,147,152,157,162],{"title":5,"description":6,"slug":7,"category":8,"icon":9},"Android","Каждое наше приложение выходит и на Android — платформенные API, фоновое выполнение, разрешения и требования магазина вместе с ними.","android","framework","logos:android-icon",{"title":11,"description":12,"slug":13,"category":8,"icon":14},"Angular","Фронтенд-фреймворк, в котором мы работаем, когда продукт уже на Angular, — строгая структура, подходящая большим долгоживущим приложениям.","angular","logos:angular-icon",{"title":16,"description":17,"slug":18,"category":19,"icon":20},"Dart","Язык, на котором написано каждое наше Flutter-приложение, — строгая null safety, полноценный pattern matching и компиляция в нативный код и веб.","dart","language","logos:dart",{"title":22,"description":23,"slug":24,"category":8,"icon":25},"Django","Python-фреймворк, который мы берём, когда продукту нужны админка, аутентификация и модель данных в первый же день, а не самописный сервис.","django","logos:django-icon",{"title":27,"description":28,"slug":29,"category":30,"icon":31},"Docker","Каждый наш сервис поставляется в виде контейнера, поэтому на ноутбуке, в CI и в продакшене работает один и тот же артефакт, а не три разных.","docker","infrastructure","logos:docker-icon",{"title":33,"description":34,"slug":35,"category":36,"icon":37},"Fastlane","Автоматизация релизов в нашей мобильной разработке — подпись, сборки и загрузка в сторы идут из CI, а не с ноутбука одного инженера в день релиза.","fastlane","tools","logos:fastlane",{"title":39,"description":40,"slug":41,"category":8,"icon":42},"Flutter","Наш основной мобильный стек с 2018 года — одна кодовая база на Dart для iOS, Android, веба и десктопа без отдельной команды под каждую платформу.","flutter","logos:flutter",{"title":44,"description":45,"slug":46,"category":36,"icon":47},"Git","Все проекты, к которым мы прикасаемся, живут в Git — ревью пул-реквестов, CI на каждой ветке и история, понятная и спустя месяцы.","git","logos:git-icon",{"title":49,"description":50,"slug":51,"category":19,"icon":52},"Go","Наш бэкенд-язык для real-time API и сервисов под нагрузкой — маленькие бинарники, быстрая сборка и конкурентность, которая остаётся читаемой.","go","logos:gopher",{"title":54,"description":55,"slug":56,"category":30,"icon":57},"Google Cloud","Облако, в котором работают наши продакшен-нагрузки, — управляемый Kubernetes, хранилище и сеть без серверов, которые надо обслуживать руками.","gcp","logos:google-cloud",{"title":59,"description":60,"slug":61,"category":36,"icon":62},"Gradle","Система сборки, через которую проходит каждый Android-релиз, — флейворы, конфигурации подписи и вся обвязка зависимостей под Flutter-приложением.","gradle","logos:gradle",{"title":64,"description":65,"slug":66,"category":30,"icon":67},"Helm","Так мы упаковываем деплой в Kubernetes — сервис, конфигурация, секреты и ingress как одна версионируемая единица, которую можно выкатить и откатить.","helm","logos:helm",{"title":69,"description":70,"slug":71,"category":19,"icon":72},"Kotlin","То, что мы берём, когда Flutter-приложению нужен настоящий Android под ним, — platform channels, фоновые задачи и интеграции с нативными SDK.","kotlin","logos:kotlin-icon",{"title":74,"description":75,"slug":76,"category":8,"icon":72},"Kotlin Multiplatform","Общая бизнес-логика для iOS и Android, а нативный интерфейс у каждой платформы свой, — альтернатива, когда Flutter не подходит.","kmp",{"title":78,"description":79,"slug":80,"category":30,"icon":81},"Kubernetes","Так мы запускаем сервисы в продакшене — деплой в Helm-чартах, откатываемые выкатки и масштабирование, для которого не нужен человек в три ночи.","kubernetes","logos:kubernetes",{"title":83,"description":84,"slug":85,"category":30,"icon":86},"NATS","Лёгкий обмен сообщениями между сервисами — publish\u002Fsubscribe и request\u002Freply без эксплуатационного веса полноценного кластера брокера.","nats","logos:nats-icon",{"title":88,"description":89,"slug":90,"category":8,"icon":91},"Nuxt","Vue с серверным рендерингом, роутингом и SEO из коробки — так мы делаем сайты и веб-приложения, которые должны быстро грузиться и индексироваться.","nuxt","logos:nuxt-icon",{"title":93,"description":94,"slug":95,"category":19,"icon":96},"PHP","Там, где PHP-бэкенд уже есть: дорабатываем его, интегрируемся с ним и делаем мобильные и веб-клиенты, которые он обслуживает.","php","logos:php",{"title":98,"description":99,"slug":100,"category":101,"icon":102},"PostgreSQL","Наша база данных по умолчанию — от проектирования схемы до настройки индексов мы берём её, если у продукта нет конкретной причины взять другую.","postgres","database","logos:postgresql",{"title":104,"description":105,"slug":106,"category":19,"icon":107},"Python","Наш язык для бэкендов, работы с данными и AI-интеграций — включая Python-биндинги, которые мы выпускаем к собственным инструментам на Rust.","python","logos:python",{"title":109,"description":110,"slug":111,"category":8,"icon":112},"React","Фронтенд-библиотека, в которой мы работаем, когда продукт уже на React, — компоненты, хуки и выросшая вокруг них экосистема.","react","logos:react",{"title":114,"description":115,"slug":116,"category":101,"icon":117},"Redis","Место для данных, которые должны быть быстрыми и которые не жалко пересчитать, — кэши, сессии, лимиты и очереди фоновых задач.","redis","logos:redis",{"title":119,"description":120,"slug":121,"category":19,"icon":122},"Ruby","Язык, на котором написана автоматизация наших мобильных релизов, — лейны Fastlane, кастомные действия и связка с CI, доставляющая сборки в сторы.","ruby","logos:ruby",{"title":124,"description":125,"slug":126,"category":19,"icon":127},"Rust","Куда мы идём, когда важны и скорость, и корректность, — рендеринг документов, CLI-инструменты и сервисы, которые обязаны оставаться быстрыми и предсказуемыми.","rust","simple-icons:rust",{"title":129,"description":130,"slug":131,"category":101,"icon":132},"SQLite","База данных, которая поставляется внутри приложения, — локальные кэши, offline-first хранилище и всё, что должно работать без сети.","sqlite","logos:sqlite",{"title":134,"description":135,"slug":136,"category":8,"icon":137},"Strapi","Headless CMS, к которой мы обращаемся, когда контент должны вести сами редакторы, — готовая админка и чистый API вместо самописных.","strapi","logos:strapi-icon",{"title":139,"description":140,"slug":141,"category":19,"icon":142},"Swift","То, что мы берём, когда Flutter-приложению нужен настоящий iOS под ним, — platform channels, интеграции нативных SDK, виджеты и App Clips.","swift","logos:swift",{"title":144,"description":145,"slug":146,"category":8,"icon":142},"SwiftUI","UI-фреймворк Apple, на котором мы делаем нативные поверхности, недоступные Flutter-приложению, — виджеты, App Clips и расширения.","swiftui",{"title":148,"description":149,"slug":150,"category":8,"icon":151},"Tailwind CSS","Так мы стилизуем весь фронтенд — утилитарные классы и дизайн-токены вместо таблицы стилей, которая только растёт и никогда не чистится.","tailwind","logos:tailwindcss-icon",{"title":153,"description":154,"slug":155,"category":19,"icon":156},"TypeScript","Стандарт для всего, что мы пишем для браузера и для Node-сервисов, — типы ловят ошибки интеграции раньше, чем код доходит до ревью.","typescript","logos:typescript-icon",{"title":158,"description":159,"slug":160,"category":8,"icon":161},"Vue","Наш фронтенд-фреймворк для админок, дашбордов мерчантов и веб-приложений, которые должны оставаться поддерживаемыми годами, а не спринтами.","vue","logos:vue",{"title":163,"description":164,"slug":165,"category":36,"icon":166},"Whisper","Открытая модель распознавания речи от OpenAI — движок транскрипции для голосового ввода, который работает и как бэкенд-сервис, и прямо на устройстве в приложении.","whisper","simple-icons:openai",{"id":168,"title":169,"author":170,"body":171,"description":3663,"extension":3664,"hero":3665,"meta":3667,"metaDescription":3668,"metaTitle":3669,"navigation":399,"ogDescription":3670,"path":3671,"seo":3672,"slug":3673,"stem":3674,"summary":3675,"timestamp":3676,"topic":3677,"updated":3678,"__hash__":3679},"blog_ru\u002Fblog\u002Fskia-rust-pdf-rendering.md","Как генерировать PDF на Rust с помощью Skia: полное руководство по крейту skia-safe","nixan",{"type":172,"value":173,"toc":3636},"minimark",[174,188,196,201,256,260,263,266,298,302,307,310,356,363,367,370,920,926,933,937,952,1086,1097,1103,1107,1114,1321,1324,1369,1372,1376,1382,1663,1666,1670,1676,1681,2169,2173,2469,2472,2476,2619,2622,2626,2629,2790,2793,2796,2844,2848,2851,3011,3016,3021,3026,3031,3037,3040,3044,3047,3067,3077,3087,3091,3094,3515,3521,3525,3535,3545,3548,3556,3560,3564,3575,3579,3584,3588,3598,3602,3611,3615,3621,3625,3632],[175,176,177,178,187],"p",{},"Программная генерация PDF — одна из тех задач, которые кажутся простыми, пока дело не доходит до реализации. Большинство библиотек либо дают низкоуровневый поток байтов, с которым приходится бороться, либо шаблонизатор высокого уровня, который ломается, как только требуется точный контроль над вёрсткой, типографикой или графикой. Но есть золотая середина — тот же графический движок, который рендерит каждую веб-страницу при печати в PDF в Chrome, каждый экран Android и каждый кадр приложения на Flutter: ",[179,180,181,182],"strong",{},"Google ",[183,184,186],"term",{"slug":185},"skia","Skia",".",[175,189,190,191,195],{},"В этом руководстве разберём, как использовать Skia из Rust через крейт ",[192,193,194],"code",{},"skia-safe"," для создания векторных PDF с полным контролем над текстом, фигурами, изображениями и многостраничной вёрсткой.",[197,198,200],"h2",{"id":199},"ключевые-выводы","Ключевые выводы",[202,203,204,217,223,230,249],"ul",{},[205,206,207,209,210,187],"li",{},[179,208,186],{}," — это open-source библиотека 2D-графики от Google, в активной разработке с 2005 года, с более чем 2 миллионами загрузок Rust-обёрток на ",[211,212,216],"a",{"href":213,"rel":214},"https:\u002F\u002Fcrates.io\u002Fcrates\u002Fskia-safe",[215],"nofollow","crates.io",[205,218,219,220,222],{},"Крейт ",[179,221,194],{}," (v0.93) предоставляет безопасные, идиоматичные Rust-обёртки с паттерном typestate, который предотвращает создание некорректных PDF на этапе компиляции.",[205,224,225,226,229],{},"Skia создаёт ",[179,227,228],{},"настоящие векторные PDF"," с выделяемым текстом, а не растеризованные изображения — тот же подход, который Chrome использует для печати в PDF.",[205,231,232,233,236,237,240,241,244,245,248],{},"Поддерживает ",[179,234,235],{},"архивное соответствие PDF\u002FA",", ",[179,238,239],{},"теговые\u002Fдоступные PDF"," (PDF\u002FUA), ",[179,242,243],{},"подмножества шрифтов"," через HarfBuzz и ",[179,246,247],{},"многостраничные документы"," с разными размерами страниц.",[205,250,251,252,255],{},"Предсобранные бинарники поставляются по умолчанию, поэтому первая сборка занимает ",[179,253,254],{},"менее одной минуты"," вместо 20–40 минут из исходников.",[197,257,259],{"id":258},"что-такое-skia-и-зачем-использовать-её-для-генерации-pdf-на-rust","Что такое Skia и зачем использовать её для генерации PDF на Rust",[175,261,262],{},"Skia — это open-source библиотека 2D-графики, которую поддерживает Google. Она в активной разработке с 2005 года — один из самых проверенных временем движков рендеринга. Когда вы печатаете веб-страницу в PDF в Google Chrome, PDF-бэкенд Skia транслирует отрисованную страницу в PDF-файл. Когда Android отрисовывает свой интерфейс — за это отвечает Skia. Flutter использует Skia в качестве движка рендеринга для кроссплатформенных приложений.",[175,264,265],{},"Чем Skia привлекательна для программной генерации PDF на Rust:",[202,267,268,274,280,286,292],{},[205,269,270,273],{},[179,271,272],{},"Настоящий векторный вывод."," Текст остаётся выделяемым, фигуры сохраняют чёткость при любом масштабе, а размеры файлов остаются разумными — потому что Skia транслирует свои операции рисования в нативные PDF-примитивы, а не растеризует пиксели.",[205,275,276,279],{},[179,277,278],{},"Полноценный 2D-графический API."," Кривые Безье, градиенты, аффинные преобразования, области отсечения, режимы наложения, настройки типографики — всё, что ожидается от профессиональной графической библиотеки, и всё это напрямую отображается в PDF.",[205,281,282,285],{},[179,283,284],{},"Теговые и доступные PDF."," Skia поддерживает деревья структурных элементов PDF и идентификаторы узлов, что позволяет создавать документы, соответствующие стандартам доступности PDF\u002FUA.",[205,287,288,291],{},[179,289,290],{},"Архивное соответствие PDF\u002FA."," Один флаг в метаданных включает вывод PDF\u002FA-2b для долгосрочного хранения документов — обязательное требование для юридических, медицинских и государственных документов.",[205,293,294,297],{},[179,295,296],{},"Подмножества шрифтов."," При встраивании шрифтов Skia удаляет неиспользуемые глифы через HarfBuzz, сохраняя компактный размер файлов.",[197,299,301],{"id":300},"настройка-крейта-skia-safe-для-генерации-pdf-на-rust","Настройка крейта skia-safe для генерации PDF на Rust",[175,303,219,304,306],{},[192,305,194],{}," предоставляет безопасные, идиоматичные Rust-обёртки для Skia. Он оборачивает C++-библиотеку через FFI и даёт API, естественный для Rust: с семантикой владения, гарантиями времени жизни и паттерном typestate, который отлавливает целые категории ошибок на этапе компиляции.",[175,308,309],{},"Добавьте в проект:",[311,312,317],"pre",{"className":313,"code":314,"language":315,"meta":316,"style":316},"language-toml shiki shiki-themes material-theme-lighter github-light github-dark","[dependencies]\nskia-safe = \"0.93\"\n","toml","",[192,318,319,335],{"__ignoreMap":316},[320,321,324,328,332],"span",{"class":322,"line":323},"line",1,[320,325,327],{"class":326},"sP7_E","[",[320,329,331],{"class":330},"sbgvK","dependencies",[320,333,334],{"class":326},"]\n",[320,336,338,342,345,349,353],{"class":322,"line":337},2,[320,339,341],{"class":340},"su5hD","skia-safe ",[320,343,344],{"class":326},"=",[320,346,348],{"class":347},"sjJ54"," \"",[320,350,352],{"class":351},"s_sjI","0.93",[320,354,355],{"class":347},"\"\n",[175,357,358,359,362],{},"Фича ",[192,360,361],{},"pdf"," включена по умолчанию, дополнительных флагов не нужно. По умолчанию крейт скачивает предсобранные бинарники Skia для вашей платформы, поэтому первая сборка занимает меньше минуты, а не 20–40 минут, которые потребовались бы для компиляции Skia из исходников.",[197,364,366],{"id":365},"как-сгенерировать-pdf-файл-на-rust-с-помощью-skia","Как сгенерировать PDF-файл на Rust с помощью Skia",[175,368,369],{},"Основная концепция проста: создать документ, начать страницу, рисовать на холсте, завершить страницу, закрыть документ. Вот минимальный пример, создающий одностраничный PDF:",[311,371,374],{"className":372,"code":373,"language":126,"meta":316,"style":316},"language-rust shiki shiki-themes material-theme-lighter github-light github-dark","use skia_safe::pdf;\n\nfn main() {\n    let mut output = Vec::new();\n\n    \u002F\u002F Создаём документ — US Letter (612 x 792 пункта)\n    let mut document = pdf::new_document(&mut output, None)\n        .begin_page((612, 792), None);\n\n    let canvas = document.canvas();\n\n    \u002F\u002F Рисуем залитый прямоугольник\n    let mut paint = skia_safe::Paint::default();\n    paint.set_color(skia_safe::Color::from_rgb(41, 98, 255));\n    canvas.draw_rect(\n        skia_safe::Rect::from_xywh(72.0, 72.0, 468.0, 100.0),\n        &paint,\n    );\n\n    \u002F\u002F Рисуем текст\n    let font = skia_safe::Font::default();\n    paint.set_color(skia_safe::Color::WHITE);\n    canvas.draw_str(\"Hello from Skia + Rust\", (90.0, 130.0), &font, &paint);\n\n    \u002F\u002F Финализируем\n    document.end_page().close();\n\n    std::fs::write(\"hello.pdf\", &output).unwrap();\n}\n",[192,375,376,395,401,417,443,448,455,496,525,530,550,555,561,587,631,645,702,714,720,725,731,754,779,836,841,847,867,872,914],{"__ignoreMap":316},[320,377,378,382,385,389,392],{"class":322,"line":323},[320,379,381],{"class":380},"sw1J6","use",[320,383,384],{"class":330}," skia_safe",[320,386,388],{"class":387},"smGrS","::",[320,390,361],{"class":391},"s3--O",[320,393,394],{"class":326},";\n",[320,396,397],{"class":322,"line":337},[320,398,400],{"emptyLinePlaceholder":399},true,"\n",[320,402,404,407,411,414],{"class":322,"line":403},3,[320,405,406],{"class":380},"fn",[320,408,410],{"class":409},"sGLFI"," main",[320,412,413],{"class":326},"()",[320,415,416],{"class":326}," {\n",[320,418,420,424,427,430,432,435,437,440],{"class":322,"line":419},4,[320,421,423],{"class":422},"sbsja","    let",[320,425,426],{"class":422}," mut",[320,428,429],{"class":340}," output ",[320,431,344],{"class":387},[320,433,434],{"class":330}," Vec",[320,436,388],{"class":387},[320,438,439],{"class":409},"new",[320,441,442],{"class":326},"();\n",[320,444,446],{"class":322,"line":445},5,[320,447,400],{"emptyLinePlaceholder":399},[320,449,451],{"class":322,"line":450},6,[320,452,454],{"class":453},"sutJx","    \u002F\u002F Создаём документ — US Letter (612 x 792 пункта)\n",[320,456,458,460,462,465,467,470,472,475,478,481,484,487,490,493],{"class":322,"line":457},7,[320,459,423],{"class":422},[320,461,426],{"class":422},[320,463,464],{"class":340}," document ",[320,466,344],{"class":387},[320,468,469],{"class":330}," pdf",[320,471,388],{"class":387},[320,473,474],{"class":409},"new_document",[320,476,477],{"class":326},"(",[320,479,480],{"class":387},"&",[320,482,483],{"class":422},"mut",[320,485,486],{"class":340}," output",[320,488,489],{"class":326},",",[320,491,492],{"class":330}," None",[320,494,495],{"class":326},")\n",[320,497,499,502,505,508,512,514,517,520,522],{"class":322,"line":498},8,[320,500,501],{"class":387},"        .",[320,503,504],{"class":409},"begin_page",[320,506,507],{"class":326},"((",[320,509,511],{"class":510},"srdBf","612",[320,513,489],{"class":326},[320,515,516],{"class":510}," 792",[320,518,519],{"class":326},"),",[320,521,492],{"class":330},[320,523,524],{"class":326},");\n",[320,526,528],{"class":322,"line":527},9,[320,529,400],{"emptyLinePlaceholder":399},[320,531,533,535,538,540,543,545,548],{"class":322,"line":532},10,[320,534,423],{"class":422},[320,536,537],{"class":340}," canvas ",[320,539,344],{"class":387},[320,541,542],{"class":340}," document",[320,544,187],{"class":387},[320,546,547],{"class":409},"canvas",[320,549,442],{"class":326},[320,551,553],{"class":322,"line":552},11,[320,554,400],{"emptyLinePlaceholder":399},[320,556,558],{"class":322,"line":557},12,[320,559,560],{"class":453},"    \u002F\u002F Рисуем залитый прямоугольник\n",[320,562,564,566,568,571,573,575,577,580,582,585],{"class":322,"line":563},13,[320,565,423],{"class":422},[320,567,426],{"class":422},[320,569,570],{"class":340}," paint ",[320,572,344],{"class":387},[320,574,384],{"class":330},[320,576,388],{"class":387},[320,578,579],{"class":330},"Paint",[320,581,388],{"class":387},[320,583,584],{"class":409},"default",[320,586,442],{"class":326},[320,588,590,593,595,598,600,603,605,608,610,613,615,618,620,623,625,628],{"class":322,"line":589},14,[320,591,592],{"class":340},"    paint",[320,594,187],{"class":387},[320,596,597],{"class":409},"set_color",[320,599,477],{"class":326},[320,601,602],{"class":330},"skia_safe",[320,604,388],{"class":387},[320,606,607],{"class":330},"Color",[320,609,388],{"class":387},[320,611,612],{"class":409},"from_rgb",[320,614,477],{"class":326},[320,616,617],{"class":510},"41",[320,619,489],{"class":326},[320,621,622],{"class":510}," 98",[320,624,489],{"class":326},[320,626,627],{"class":510}," 255",[320,629,630],{"class":326},"));\n",[320,632,634,637,639,642],{"class":322,"line":633},15,[320,635,636],{"class":340},"    canvas",[320,638,187],{"class":387},[320,640,641],{"class":409},"draw_rect",[320,643,644],{"class":326},"(\n",[320,646,648,651,653,656,658,661,663,666,669,672,674,677,679,681,683,686,688,690,692,695,697,699],{"class":322,"line":647},16,[320,649,650],{"class":330},"        skia_safe",[320,652,388],{"class":387},[320,654,655],{"class":330},"Rect",[320,657,388],{"class":387},[320,659,660],{"class":409},"from_xywh",[320,662,477],{"class":326},[320,664,665],{"class":510},"72",[320,667,187],{"class":668},"s39Yj",[320,670,671],{"class":510},"0",[320,673,489],{"class":326},[320,675,676],{"class":510}," 72",[320,678,187],{"class":668},[320,680,671],{"class":510},[320,682,489],{"class":326},[320,684,685],{"class":510}," 468",[320,687,187],{"class":668},[320,689,671],{"class":510},[320,691,489],{"class":326},[320,693,694],{"class":510}," 100",[320,696,187],{"class":668},[320,698,671],{"class":510},[320,700,701],{"class":326},"),\n",[320,703,705,708,711],{"class":322,"line":704},17,[320,706,707],{"class":387},"        &",[320,709,710],{"class":340},"paint",[320,712,713],{"class":326},",\n",[320,715,717],{"class":322,"line":716},18,[320,718,719],{"class":326},"    );\n",[320,721,723],{"class":322,"line":722},19,[320,724,400],{"emptyLinePlaceholder":399},[320,726,728],{"class":322,"line":727},20,[320,729,730],{"class":453},"    \u002F\u002F Рисуем текст\n",[320,732,734,736,739,741,743,745,748,750,752],{"class":322,"line":733},21,[320,735,423],{"class":422},[320,737,738],{"class":340}," font ",[320,740,344],{"class":387},[320,742,384],{"class":330},[320,744,388],{"class":387},[320,746,747],{"class":330},"Font",[320,749,388],{"class":387},[320,751,584],{"class":409},[320,753,442],{"class":326},[320,755,757,759,761,763,765,767,769,771,773,777],{"class":322,"line":756},22,[320,758,592],{"class":340},[320,760,187],{"class":387},[320,762,597],{"class":409},[320,764,477],{"class":326},[320,766,602],{"class":330},[320,768,388],{"class":387},[320,770,607],{"class":330},[320,772,388],{"class":387},[320,774,776],{"class":775},"s_hVV","WHITE",[320,778,524],{"class":326},[320,780,782,784,786,789,791,794,797,799,801,804,807,809,811,813,816,818,820,822,825,828,830,832,834],{"class":322,"line":781},23,[320,783,636],{"class":340},[320,785,187],{"class":387},[320,787,788],{"class":409},"draw_str",[320,790,477],{"class":326},[320,792,793],{"class":347},"\"",[320,795,796],{"class":351},"Hello from Skia + Rust",[320,798,793],{"class":347},[320,800,489],{"class":326},[320,802,803],{"class":326}," (",[320,805,806],{"class":510},"90",[320,808,187],{"class":668},[320,810,671],{"class":510},[320,812,489],{"class":326},[320,814,815],{"class":510}," 130",[320,817,187],{"class":668},[320,819,671],{"class":510},[320,821,519],{"class":326},[320,823,824],{"class":387}," &",[320,826,827],{"class":340},"font",[320,829,489],{"class":326},[320,831,824],{"class":387},[320,833,710],{"class":340},[320,835,524],{"class":326},[320,837,839],{"class":322,"line":838},24,[320,840,400],{"emptyLinePlaceholder":399},[320,842,844],{"class":322,"line":843},25,[320,845,846],{"class":453},"    \u002F\u002F Финализируем\n",[320,848,850,853,855,858,860,862,865],{"class":322,"line":849},26,[320,851,852],{"class":340},"    document",[320,854,187],{"class":387},[320,856,857],{"class":409},"end_page",[320,859,413],{"class":326},[320,861,187],{"class":387},[320,863,864],{"class":409},"close",[320,866,442],{"class":326},[320,868,870],{"class":322,"line":869},27,[320,871,400],{"emptyLinePlaceholder":399},[320,873,875,878,880,883,885,888,890,892,895,897,899,901,904,907,909,912],{"class":322,"line":874},28,[320,876,877],{"class":330},"    std",[320,879,388],{"class":387},[320,881,882],{"class":330},"fs",[320,884,388],{"class":387},[320,886,887],{"class":409},"write",[320,889,477],{"class":326},[320,891,793],{"class":347},[320,893,894],{"class":351},"hello.pdf",[320,896,793],{"class":347},[320,898,489],{"class":326},[320,900,824],{"class":387},[320,902,903],{"class":340},"output",[320,905,906],{"class":326},")",[320,908,187],{"class":387},[320,910,911],{"class":409},"unwrap",[320,913,442],{"class":326},[320,915,917],{"class":322,"line":916},29,[320,918,919],{"class":326},"}\n",[175,921,922,923,925],{},"Запустите, откройте ",[192,924,894],{}," — и вы увидите синий прямоугольник с белым текстом. Всё в виде векторной графики, полностью выделяемой и масштабируемой.",[175,927,928],{},[929,930],"img",{"alt":931,"src":932},"Сгенерированный PDF в просмотрщике — синий прямоугольник с белым текстом, отрисованный как векторная графика на странице US Letter с помощью skia-safe на Rust","\u002Fblog\u002Fskia-rust-pdf-rendering.webp",[197,934,936],{"id":935},"безопасность-на-этапе-компиляции-с-паттерном-typestate","Безопасность на этапе компиляции с паттерном typestate",[175,938,939,940,943,944,947,948,951],{},"Одна из самых элегантных особенностей Rust-обёрток — паттерн typestate для типа ",[192,941,942],{},"Document",". Документ переходит между двумя состояниями: ",[192,945,946],{},"Open"," (готов начать страницу) и ",[192,949,950],{},"OnPage"," (активно рисуем).",[311,953,955],{"className":372,"code":954,"language":126,"meta":316,"style":316},"\u002F\u002F Document\u003Cstate::Open> — можно начать страницу или закрыть документ\nlet document = pdf::new_document(&mut output, None);\n\n\u002F\u002F Document\u003Cstate::OnPage> — можно обращаться к холсту или завершить страницу\nlet page = document.begin_page((612, 792), None);\nlet canvas = page.canvas();\n\n\u002F\u002F Обратно к Document\u003Cstate::Open> — можно начать другую страницу или закрыть\nlet document = page.end_page();\n\ndocument.close();\n",[192,956,957,962,991,995,1000,1029,1046,1050,1055,1071,1075],{"__ignoreMap":316},[320,958,959],{"class":322,"line":323},[320,960,961],{"class":453},"\u002F\u002F Document\u003Cstate::Open> — можно начать страницу или закрыть документ\n",[320,963,964,967,969,971,973,975,977,979,981,983,985,987,989],{"class":322,"line":337},[320,965,966],{"class":422},"let",[320,968,464],{"class":340},[320,970,344],{"class":387},[320,972,469],{"class":330},[320,974,388],{"class":387},[320,976,474],{"class":409},[320,978,477],{"class":326},[320,980,480],{"class":387},[320,982,483],{"class":422},[320,984,486],{"class":340},[320,986,489],{"class":326},[320,988,492],{"class":330},[320,990,524],{"class":326},[320,992,993],{"class":322,"line":403},[320,994,400],{"emptyLinePlaceholder":399},[320,996,997],{"class":322,"line":419},[320,998,999],{"class":453},"\u002F\u002F Document\u003Cstate::OnPage> — можно обращаться к холсту или завершить страницу\n",[320,1001,1002,1004,1007,1009,1011,1013,1015,1017,1019,1021,1023,1025,1027],{"class":322,"line":445},[320,1003,966],{"class":422},[320,1005,1006],{"class":340}," page ",[320,1008,344],{"class":387},[320,1010,542],{"class":340},[320,1012,187],{"class":387},[320,1014,504],{"class":409},[320,1016,507],{"class":326},[320,1018,511],{"class":510},[320,1020,489],{"class":326},[320,1022,516],{"class":510},[320,1024,519],{"class":326},[320,1026,492],{"class":330},[320,1028,524],{"class":326},[320,1030,1031,1033,1035,1037,1040,1042,1044],{"class":322,"line":450},[320,1032,966],{"class":422},[320,1034,537],{"class":340},[320,1036,344],{"class":387},[320,1038,1039],{"class":340}," page",[320,1041,187],{"class":387},[320,1043,547],{"class":409},[320,1045,442],{"class":326},[320,1047,1048],{"class":322,"line":457},[320,1049,400],{"emptyLinePlaceholder":399},[320,1051,1052],{"class":322,"line":498},[320,1053,1054],{"class":453},"\u002F\u002F Обратно к Document\u003Cstate::Open> — можно начать другую страницу или закрыть\n",[320,1056,1057,1059,1061,1063,1065,1067,1069],{"class":322,"line":527},[320,1058,966],{"class":422},[320,1060,464],{"class":340},[320,1062,344],{"class":387},[320,1064,1039],{"class":340},[320,1066,187],{"class":387},[320,1068,857],{"class":409},[320,1070,442],{"class":326},[320,1072,1073],{"class":322,"line":532},[320,1074,400],{"emptyLinePlaceholder":399},[320,1076,1077,1080,1082,1084],{"class":322,"line":552},[320,1078,1079],{"class":340},"document",[320,1081,187],{"class":387},[320,1083,864],{"class":409},[320,1085,442],{"class":326},[175,1087,1088,1089,1092,1093,1096],{},"Если вы попытаетесь вызвать ",[192,1090,1091],{},"canvas()"," у документа без открытой страницы или попытаетесь ",[192,1094,1095],{},"close()"," документ при активной странице — код не скомпилируется. Это не проверка во время выполнения и не assertion — это гарантия на этапе компиляции, обеспечиваемая системой типов Rust. Вы не можете создать некорректный PDF, потому что компилятор это предотвращает.",[175,1098,1099,1100,1102],{},"Сравните с библиотеками PDF на C++ или Python, где такие ошибки приводят к повреждённому выводу, незаметным сбоям или падениям во время выполнения. В Rust с ",[192,1101,194],{}," компилятор — ваш корректор.",[197,1104,1106],{"id":1105},"добавление-метаданных-pdf-для-seo-и-доступности","Добавление метаданных PDF для SEO и доступности",[175,1108,1109,1110,1113],{},"Метаданные PDF — это то, что поисковые системы, системы управления документами и инструменты доступности используют для понимания вашего документа. Skia предоставляет их через структуру ",[192,1111,1112],{},"Metadata",":",[311,1115,1117],{"className":372,"code":1116,"language":126,"meta":316,"style":316},"use skia_safe::pdf::{self, Metadata};\n\nlet metadata = Metadata {\n    title: \"Финансовый отчёт Q4\".to_string(),\n    author: \"Финансовый отдел\".to_string(),\n    subject: \"Квартальные результаты и прогнозы\".to_string(),\n    creator: \"Генератор отчётов v2.1\".to_string(),\n    ..Default::default()\n};\n\nlet mut output = Vec::new();\nlet document = pdf::new_document(&mut output, Some(&metadata));\n",[192,1118,1119,1145,1149,1162,1184,1204,1224,1244,1259,1263,1267,1285],{"__ignoreMap":316},[320,1120,1121,1123,1125,1127,1129,1131,1134,1137,1139,1142],{"class":322,"line":323},[320,1122,381],{"class":380},[320,1124,384],{"class":330},[320,1126,388],{"class":387},[320,1128,361],{"class":330},[320,1130,388],{"class":387},[320,1132,1133],{"class":326},"{",[320,1135,1136],{"class":775},"self",[320,1138,489],{"class":326},[320,1140,1141],{"class":330}," Metadata",[320,1143,1144],{"class":326},"};\n",[320,1146,1147],{"class":322,"line":337},[320,1148,400],{"emptyLinePlaceholder":399},[320,1150,1151,1153,1156,1158,1160],{"class":322,"line":403},[320,1152,966],{"class":422},[320,1154,1155],{"class":340}," metadata ",[320,1157,344],{"class":387},[320,1159,1141],{"class":330},[320,1161,416],{"class":326},[320,1163,1164,1167,1169,1171,1174,1176,1178,1181],{"class":322,"line":419},[320,1165,1166],{"class":340},"    title",[320,1168,1113],{"class":387},[320,1170,348],{"class":347},[320,1172,1173],{"class":351},"Финансовый отчёт Q4",[320,1175,793],{"class":347},[320,1177,187],{"class":387},[320,1179,1180],{"class":409},"to_string",[320,1182,1183],{"class":326},"(),\n",[320,1185,1186,1189,1191,1193,1196,1198,1200,1202],{"class":322,"line":445},[320,1187,1188],{"class":340},"    author",[320,1190,1113],{"class":387},[320,1192,348],{"class":347},[320,1194,1195],{"class":351},"Финансовый отдел",[320,1197,793],{"class":347},[320,1199,187],{"class":387},[320,1201,1180],{"class":409},[320,1203,1183],{"class":326},[320,1205,1206,1209,1211,1213,1216,1218,1220,1222],{"class":322,"line":450},[320,1207,1208],{"class":340},"    subject",[320,1210,1113],{"class":387},[320,1212,348],{"class":347},[320,1214,1215],{"class":351},"Квартальные результаты и прогнозы",[320,1217,793],{"class":347},[320,1219,187],{"class":387},[320,1221,1180],{"class":409},[320,1223,1183],{"class":326},[320,1225,1226,1229,1231,1233,1236,1238,1240,1242],{"class":322,"line":457},[320,1227,1228],{"class":340},"    creator",[320,1230,1113],{"class":387},[320,1232,348],{"class":347},[320,1234,1235],{"class":351},"Генератор отчётов v2.1",[320,1237,793],{"class":347},[320,1239,187],{"class":387},[320,1241,1180],{"class":409},[320,1243,1183],{"class":326},[320,1245,1246,1249,1252,1254,1256],{"class":322,"line":498},[320,1247,1248],{"class":387},"    ..",[320,1250,1251],{"class":330},"Default",[320,1253,388],{"class":387},[320,1255,584],{"class":409},[320,1257,1258],{"class":326},"()\n",[320,1260,1261],{"class":322,"line":527},[320,1262,1144],{"class":326},[320,1264,1265],{"class":322,"line":532},[320,1266,400],{"emptyLinePlaceholder":399},[320,1268,1269,1271,1273,1275,1277,1279,1281,1283],{"class":322,"line":552},[320,1270,966],{"class":422},[320,1272,426],{"class":422},[320,1274,429],{"class":340},[320,1276,344],{"class":387},[320,1278,434],{"class":330},[320,1280,388],{"class":387},[320,1282,439],{"class":409},[320,1284,442],{"class":326},[320,1286,1287,1289,1291,1293,1295,1297,1299,1301,1303,1305,1307,1309,1312,1314,1316,1319],{"class":322,"line":557},[320,1288,966],{"class":422},[320,1290,464],{"class":340},[320,1292,344],{"class":387},[320,1294,469],{"class":330},[320,1296,388],{"class":387},[320,1298,474],{"class":409},[320,1300,477],{"class":326},[320,1302,480],{"class":387},[320,1304,483],{"class":422},[320,1306,486],{"class":340},[320,1308,489],{"class":326},[320,1310,1311],{"class":330}," Some",[320,1313,477],{"class":326},[320,1315,480],{"class":387},[320,1317,1318],{"class":340},"metadata",[320,1320,630],{"class":326},[175,1322,1323],{},"Для архивного соответствия включите PDF\u002FA одним флагом:",[311,1325,1327],{"className":372,"code":1326,"language":126,"meta":316,"style":316},"let metadata = Metadata {\n    pdf_a: true,\n    ..Default::default()\n};\n",[192,1328,1329,1341,1353,1365],{"__ignoreMap":316},[320,1330,1331,1333,1335,1337,1339],{"class":322,"line":323},[320,1332,966],{"class":422},[320,1334,1155],{"class":340},[320,1336,344],{"class":387},[320,1338,1141],{"class":330},[320,1340,416],{"class":326},[320,1342,1343,1346,1348,1351],{"class":322,"line":337},[320,1344,1345],{"class":340},"    pdf_a",[320,1347,1113],{"class":387},[320,1349,1350],{"class":668}," true",[320,1352,713],{"class":326},[320,1354,1355,1357,1359,1361,1363],{"class":322,"line":403},[320,1356,1248],{"class":387},[320,1358,1251],{"class":330},[320,1360,388],{"class":387},[320,1362,584],{"class":409},[320,1364,1258],{"class":326},[320,1366,1367],{"class":322,"line":419},[320,1368,1144],{"class":326},[175,1370,1371],{},"Это создаёт вывод, соответствующий PDF\u002FA-2b — подходящий для юридических документов, медицинских записей и любого контента, который должен оставаться читаемым через десятилетия.",[197,1373,1375],{"id":1374},"создание-многостраничных-pdf-документов-на-rust","Создание многостраничных PDF-документов на Rust",[175,1377,1378,1379,1381],{},"Реальные документы редко бывают одностраничными. Skia естественно работает с многостраничными PDF — каждый вызов ",[192,1380,504],{}," начинает новую страницу, причём страницы могут иметь разные размеры:",[311,1383,1385],{"className":372,"code":1384,"language":126,"meta":316,"style":316},"let mut output = Vec::new();\nlet document = pdf::new_document(&mut output, None);\n\n\u002F\u002F Страница 1 — A4, книжная\nlet mut page = document.begin_page((595, 842), None);\ndraw_cover_page(page.canvas());\nlet document = page.end_page();\n\n\u002F\u002F Страница 2 — A4, альбомная\nlet mut page = document.begin_page((842, 595), None);\ndraw_data_table(page.canvas());\nlet document = page.end_page();\n\n\u002F\u002F Страница 3 — снова книжная\nlet mut page = document.begin_page((595, 842), None);\ndraw_charts(page.canvas());\nlet document = page.end_page();\n\ndocument.close();\n",[192,1386,1387,1405,1433,1437,1442,1474,1491,1507,1511,1516,1548,1563,1579,1583,1588,1618,1633,1649,1653],{"__ignoreMap":316},[320,1388,1389,1391,1393,1395,1397,1399,1401,1403],{"class":322,"line":323},[320,1390,966],{"class":422},[320,1392,426],{"class":422},[320,1394,429],{"class":340},[320,1396,344],{"class":387},[320,1398,434],{"class":330},[320,1400,388],{"class":387},[320,1402,439],{"class":409},[320,1404,442],{"class":326},[320,1406,1407,1409,1411,1413,1415,1417,1419,1421,1423,1425,1427,1429,1431],{"class":322,"line":337},[320,1408,966],{"class":422},[320,1410,464],{"class":340},[320,1412,344],{"class":387},[320,1414,469],{"class":330},[320,1416,388],{"class":387},[320,1418,474],{"class":409},[320,1420,477],{"class":326},[320,1422,480],{"class":387},[320,1424,483],{"class":422},[320,1426,486],{"class":340},[320,1428,489],{"class":326},[320,1430,492],{"class":330},[320,1432,524],{"class":326},[320,1434,1435],{"class":322,"line":403},[320,1436,400],{"emptyLinePlaceholder":399},[320,1438,1439],{"class":322,"line":419},[320,1440,1441],{"class":453},"\u002F\u002F Страница 1 — A4, книжная\n",[320,1443,1444,1446,1448,1450,1452,1454,1456,1458,1460,1463,1465,1468,1470,1472],{"class":322,"line":445},[320,1445,966],{"class":422},[320,1447,426],{"class":422},[320,1449,1006],{"class":340},[320,1451,344],{"class":387},[320,1453,542],{"class":340},[320,1455,187],{"class":387},[320,1457,504],{"class":409},[320,1459,507],{"class":326},[320,1461,1462],{"class":510},"595",[320,1464,489],{"class":326},[320,1466,1467],{"class":510}," 842",[320,1469,519],{"class":326},[320,1471,492],{"class":330},[320,1473,524],{"class":326},[320,1475,1476,1479,1481,1484,1486,1488],{"class":322,"line":450},[320,1477,1478],{"class":409},"draw_cover_page",[320,1480,477],{"class":326},[320,1482,1483],{"class":340},"page",[320,1485,187],{"class":387},[320,1487,547],{"class":409},[320,1489,1490],{"class":326},"());\n",[320,1492,1493,1495,1497,1499,1501,1503,1505],{"class":322,"line":457},[320,1494,966],{"class":422},[320,1496,464],{"class":340},[320,1498,344],{"class":387},[320,1500,1039],{"class":340},[320,1502,187],{"class":387},[320,1504,857],{"class":409},[320,1506,442],{"class":326},[320,1508,1509],{"class":322,"line":498},[320,1510,400],{"emptyLinePlaceholder":399},[320,1512,1513],{"class":322,"line":527},[320,1514,1515],{"class":453},"\u002F\u002F Страница 2 — A4, альбомная\n",[320,1517,1518,1520,1522,1524,1526,1528,1530,1532,1534,1537,1539,1542,1544,1546],{"class":322,"line":532},[320,1519,966],{"class":422},[320,1521,426],{"class":422},[320,1523,1006],{"class":340},[320,1525,344],{"class":387},[320,1527,542],{"class":340},[320,1529,187],{"class":387},[320,1531,504],{"class":409},[320,1533,507],{"class":326},[320,1535,1536],{"class":510},"842",[320,1538,489],{"class":326},[320,1540,1541],{"class":510}," 595",[320,1543,519],{"class":326},[320,1545,492],{"class":330},[320,1547,524],{"class":326},[320,1549,1550,1553,1555,1557,1559,1561],{"class":322,"line":552},[320,1551,1552],{"class":409},"draw_data_table",[320,1554,477],{"class":326},[320,1556,1483],{"class":340},[320,1558,187],{"class":387},[320,1560,547],{"class":409},[320,1562,1490],{"class":326},[320,1564,1565,1567,1569,1571,1573,1575,1577],{"class":322,"line":557},[320,1566,966],{"class":422},[320,1568,464],{"class":340},[320,1570,344],{"class":387},[320,1572,1039],{"class":340},[320,1574,187],{"class":387},[320,1576,857],{"class":409},[320,1578,442],{"class":326},[320,1580,1581],{"class":322,"line":563},[320,1582,400],{"emptyLinePlaceholder":399},[320,1584,1585],{"class":322,"line":589},[320,1586,1587],{"class":453},"\u002F\u002F Страница 3 — снова книжная\n",[320,1589,1590,1592,1594,1596,1598,1600,1602,1604,1606,1608,1610,1612,1614,1616],{"class":322,"line":633},[320,1591,966],{"class":422},[320,1593,426],{"class":422},[320,1595,1006],{"class":340},[320,1597,344],{"class":387},[320,1599,542],{"class":340},[320,1601,187],{"class":387},[320,1603,504],{"class":409},[320,1605,507],{"class":326},[320,1607,1462],{"class":510},[320,1609,489],{"class":326},[320,1611,1467],{"class":510},[320,1613,519],{"class":326},[320,1615,492],{"class":330},[320,1617,524],{"class":326},[320,1619,1620,1623,1625,1627,1629,1631],{"class":322,"line":647},[320,1621,1622],{"class":409},"draw_charts",[320,1624,477],{"class":326},[320,1626,1483],{"class":340},[320,1628,187],{"class":387},[320,1630,547],{"class":409},[320,1632,1490],{"class":326},[320,1634,1635,1637,1639,1641,1643,1645,1647],{"class":322,"line":704},[320,1636,966],{"class":422},[320,1638,464],{"class":340},[320,1640,344],{"class":387},[320,1642,1039],{"class":340},[320,1644,187],{"class":387},[320,1646,857],{"class":409},[320,1648,442],{"class":326},[320,1650,1651],{"class":322,"line":716},[320,1652,400],{"emptyLinePlaceholder":399},[320,1654,1655,1657,1659,1661],{"class":322,"line":722},[320,1656,1079],{"class":340},[320,1658,187],{"class":387},[320,1660,864],{"class":409},[320,1662,442],{"class":326},[175,1664,1665],{},"Смешанные ориентации и размеры страниц в одном документе — то, с чем многие PDF-библиотеки испытывают трудности, — работают из коробки.",[197,1667,1669],{"id":1668},"рисование-фигур-текста-и-путей-в-skia-pdf","Рисование фигур, текста и путей в Skia PDF",[175,1671,1672,1675],{},[192,1673,1674],{},"Canvas"," в Skia — это полноценная 2D-поверхность для рисования. Всё, что вы рисуете на ней, преобразуется в векторные PDF-примитивы.",[1677,1678,1680],"h3",{"id":1679},"фигуры-и-пути","Фигуры и пути",[311,1682,1684],{"className":372,"code":1683,"language":126,"meta":316,"style":316},"let canvas = page.canvas();\nlet mut paint = Paint::default();\npaint.set_anti_alias(true);\n\n\u002F\u002F Залитый прямоугольник\npaint.set_color(Color::from_rgb(59, 130, 246));\npaint.set_style(skia_safe::PaintStyle::Fill);\ncanvas.draw_rect(Rect::from_xywh(50.0, 50.0, 200.0, 100.0), &paint);\n\n\u002F\u002F Круг с обводкой\npaint.set_style(skia_safe::PaintStyle::Stroke);\npaint.set_stroke_width(2.0);\ncanvas.draw_circle((300.0, 100.0), 50.0, &paint);\n\n\u002F\u002F Произвольный путь Безье\nlet mut path = skia_safe::Path::new();\npath.move_to((400.0, 50.0));\npath.cubic_to((450.0, 20.0), (500.0, 80.0), (550.0, 50.0));\npath.line_to((550.0, 150.0));\npath.close();\npaint.set_style(skia_safe::PaintStyle::Fill);\ncanvas.draw_path(&path, &paint);\n",[192,1685,1686,1702,1721,1737,1741,1746,1778,1803,1862,1866,1871,1894,1914,1956,1960,1965,1989,2018,2086,2114,2124,2146],{"__ignoreMap":316},[320,1687,1688,1690,1692,1694,1696,1698,1700],{"class":322,"line":323},[320,1689,966],{"class":422},[320,1691,537],{"class":340},[320,1693,344],{"class":387},[320,1695,1039],{"class":340},[320,1697,187],{"class":387},[320,1699,547],{"class":409},[320,1701,442],{"class":326},[320,1703,1704,1706,1708,1710,1712,1715,1717,1719],{"class":322,"line":337},[320,1705,966],{"class":422},[320,1707,426],{"class":422},[320,1709,570],{"class":340},[320,1711,344],{"class":387},[320,1713,1714],{"class":330}," Paint",[320,1716,388],{"class":387},[320,1718,584],{"class":409},[320,1720,442],{"class":326},[320,1722,1723,1725,1727,1730,1732,1735],{"class":322,"line":403},[320,1724,710],{"class":340},[320,1726,187],{"class":387},[320,1728,1729],{"class":409},"set_anti_alias",[320,1731,477],{"class":326},[320,1733,1734],{"class":668},"true",[320,1736,524],{"class":326},[320,1738,1739],{"class":322,"line":419},[320,1740,400],{"emptyLinePlaceholder":399},[320,1742,1743],{"class":322,"line":445},[320,1744,1745],{"class":453},"\u002F\u002F Залитый прямоугольник\n",[320,1747,1748,1750,1752,1754,1756,1758,1760,1762,1764,1767,1769,1771,1773,1776],{"class":322,"line":450},[320,1749,710],{"class":340},[320,1751,187],{"class":387},[320,1753,597],{"class":409},[320,1755,477],{"class":326},[320,1757,607],{"class":330},[320,1759,388],{"class":387},[320,1761,612],{"class":409},[320,1763,477],{"class":326},[320,1765,1766],{"class":510},"59",[320,1768,489],{"class":326},[320,1770,815],{"class":510},[320,1772,489],{"class":326},[320,1774,1775],{"class":510}," 246",[320,1777,630],{"class":326},[320,1779,1780,1782,1784,1787,1789,1791,1793,1796,1798,1801],{"class":322,"line":457},[320,1781,710],{"class":340},[320,1783,187],{"class":387},[320,1785,1786],{"class":409},"set_style",[320,1788,477],{"class":326},[320,1790,602],{"class":330},[320,1792,388],{"class":387},[320,1794,1795],{"class":330},"PaintStyle",[320,1797,388],{"class":387},[320,1799,1800],{"class":330},"Fill",[320,1802,524],{"class":326},[320,1804,1805,1807,1809,1811,1813,1815,1817,1819,1821,1824,1826,1828,1830,1833,1835,1837,1839,1842,1844,1846,1848,1850,1852,1854,1856,1858,1860],{"class":322,"line":498},[320,1806,547],{"class":340},[320,1808,187],{"class":387},[320,1810,641],{"class":409},[320,1812,477],{"class":326},[320,1814,655],{"class":330},[320,1816,388],{"class":387},[320,1818,660],{"class":409},[320,1820,477],{"class":326},[320,1822,1823],{"class":510},"50",[320,1825,187],{"class":668},[320,1827,671],{"class":510},[320,1829,489],{"class":326},[320,1831,1832],{"class":510}," 50",[320,1834,187],{"class":668},[320,1836,671],{"class":510},[320,1838,489],{"class":326},[320,1840,1841],{"class":510}," 200",[320,1843,187],{"class":668},[320,1845,671],{"class":510},[320,1847,489],{"class":326},[320,1849,694],{"class":510},[320,1851,187],{"class":668},[320,1853,671],{"class":510},[320,1855,519],{"class":326},[320,1857,824],{"class":387},[320,1859,710],{"class":340},[320,1861,524],{"class":326},[320,1863,1864],{"class":322,"line":527},[320,1865,400],{"emptyLinePlaceholder":399},[320,1867,1868],{"class":322,"line":532},[320,1869,1870],{"class":453},"\u002F\u002F Круг с обводкой\n",[320,1872,1873,1875,1877,1879,1881,1883,1885,1887,1889,1892],{"class":322,"line":552},[320,1874,710],{"class":340},[320,1876,187],{"class":387},[320,1878,1786],{"class":409},[320,1880,477],{"class":326},[320,1882,602],{"class":330},[320,1884,388],{"class":387},[320,1886,1795],{"class":330},[320,1888,388],{"class":387},[320,1890,1891],{"class":330},"Stroke",[320,1893,524],{"class":326},[320,1895,1896,1898,1900,1903,1905,1908,1910,1912],{"class":322,"line":557},[320,1897,710],{"class":340},[320,1899,187],{"class":387},[320,1901,1902],{"class":409},"set_stroke_width",[320,1904,477],{"class":326},[320,1906,1907],{"class":510},"2",[320,1909,187],{"class":668},[320,1911,671],{"class":510},[320,1913,524],{"class":326},[320,1915,1916,1918,1920,1923,1925,1928,1930,1932,1934,1936,1938,1940,1942,1944,1946,1948,1950,1952,1954],{"class":322,"line":563},[320,1917,547],{"class":340},[320,1919,187],{"class":387},[320,1921,1922],{"class":409},"draw_circle",[320,1924,507],{"class":326},[320,1926,1927],{"class":510},"300",[320,1929,187],{"class":668},[320,1931,671],{"class":510},[320,1933,489],{"class":326},[320,1935,694],{"class":510},[320,1937,187],{"class":668},[320,1939,671],{"class":510},[320,1941,519],{"class":326},[320,1943,1832],{"class":510},[320,1945,187],{"class":668},[320,1947,671],{"class":510},[320,1949,489],{"class":326},[320,1951,824],{"class":387},[320,1953,710],{"class":340},[320,1955,524],{"class":326},[320,1957,1958],{"class":322,"line":589},[320,1959,400],{"emptyLinePlaceholder":399},[320,1961,1962],{"class":322,"line":633},[320,1963,1964],{"class":453},"\u002F\u002F Произвольный путь Безье\n",[320,1966,1967,1969,1971,1974,1976,1978,1980,1983,1985,1987],{"class":322,"line":647},[320,1968,966],{"class":422},[320,1970,426],{"class":422},[320,1972,1973],{"class":340}," path ",[320,1975,344],{"class":387},[320,1977,384],{"class":330},[320,1979,388],{"class":387},[320,1981,1982],{"class":330},"Path",[320,1984,388],{"class":387},[320,1986,439],{"class":409},[320,1988,442],{"class":326},[320,1990,1991,1994,1996,1999,2001,2004,2006,2008,2010,2012,2014,2016],{"class":322,"line":704},[320,1992,1993],{"class":340},"path",[320,1995,187],{"class":387},[320,1997,1998],{"class":409},"move_to",[320,2000,507],{"class":326},[320,2002,2003],{"class":510},"400",[320,2005,187],{"class":668},[320,2007,671],{"class":510},[320,2009,489],{"class":326},[320,2011,1832],{"class":510},[320,2013,187],{"class":668},[320,2015,671],{"class":510},[320,2017,630],{"class":326},[320,2019,2020,2022,2024,2027,2029,2032,2034,2036,2038,2041,2043,2045,2047,2049,2052,2054,2056,2058,2061,2063,2065,2067,2069,2072,2074,2076,2078,2080,2082,2084],{"class":322,"line":716},[320,2021,1993],{"class":340},[320,2023,187],{"class":387},[320,2025,2026],{"class":409},"cubic_to",[320,2028,507],{"class":326},[320,2030,2031],{"class":510},"450",[320,2033,187],{"class":668},[320,2035,671],{"class":510},[320,2037,489],{"class":326},[320,2039,2040],{"class":510}," 20",[320,2042,187],{"class":668},[320,2044,671],{"class":510},[320,2046,519],{"class":326},[320,2048,803],{"class":326},[320,2050,2051],{"class":510},"500",[320,2053,187],{"class":668},[320,2055,671],{"class":510},[320,2057,489],{"class":326},[320,2059,2060],{"class":510}," 80",[320,2062,187],{"class":668},[320,2064,671],{"class":510},[320,2066,519],{"class":326},[320,2068,803],{"class":326},[320,2070,2071],{"class":510},"550",[320,2073,187],{"class":668},[320,2075,671],{"class":510},[320,2077,489],{"class":326},[320,2079,1832],{"class":510},[320,2081,187],{"class":668},[320,2083,671],{"class":510},[320,2085,630],{"class":326},[320,2087,2088,2090,2092,2095,2097,2099,2101,2103,2105,2108,2110,2112],{"class":322,"line":722},[320,2089,1993],{"class":340},[320,2091,187],{"class":387},[320,2093,2094],{"class":409},"line_to",[320,2096,507],{"class":326},[320,2098,2071],{"class":510},[320,2100,187],{"class":668},[320,2102,671],{"class":510},[320,2104,489],{"class":326},[320,2106,2107],{"class":510}," 150",[320,2109,187],{"class":668},[320,2111,671],{"class":510},[320,2113,630],{"class":326},[320,2115,2116,2118,2120,2122],{"class":322,"line":727},[320,2117,1993],{"class":340},[320,2119,187],{"class":387},[320,2121,864],{"class":409},[320,2123,442],{"class":326},[320,2125,2126,2128,2130,2132,2134,2136,2138,2140,2142,2144],{"class":322,"line":733},[320,2127,710],{"class":340},[320,2129,187],{"class":387},[320,2131,1786],{"class":409},[320,2133,477],{"class":326},[320,2135,602],{"class":330},[320,2137,388],{"class":387},[320,2139,1795],{"class":330},[320,2141,388],{"class":387},[320,2143,1800],{"class":330},[320,2145,524],{"class":326},[320,2147,2148,2150,2152,2155,2157,2159,2161,2163,2165,2167],{"class":322,"line":756},[320,2149,547],{"class":340},[320,2151,187],{"class":387},[320,2153,2154],{"class":409},"draw_path",[320,2156,477],{"class":326},[320,2158,480],{"class":387},[320,2160,1993],{"class":340},[320,2162,489],{"class":326},[320,2164,824],{"class":387},[320,2166,710],{"class":340},[320,2168,524],{"class":326},[1677,2170,2172],{"id":2171},"рендеринг-текста-с-выделяемым-выводом","Рендеринг текста с выделяемым выводом",[311,2174,2176],{"className":372,"code":2175,"language":126,"meta":316,"style":316},"let typeface = skia_safe::Typeface::from_name(\"Helvetica\", skia_safe::FontStyle::bold())\n    .unwrap_or_else(skia_safe::Typeface::default);\nlet font = skia_safe::Font::from_typeface(&typeface, 24.0);\n\nlet mut paint = Paint::default();\npaint.set_color(Color::BLACK);\ncanvas.draw_str(\"Заголовок раздела\", (72.0, 72.0), &font, &paint);\n\n\u002F\u002F Текст поменьше для основного содержимого\nlet body_font = skia_safe::Font::from_typeface(&typeface, 12.0);\ncanvas.draw_str(\"Основной текст.\", (72.0, 100.0), &body_font, &paint);\n",[192,2177,2178,2225,2247,2284,2288,2306,2325,2374,2378,2383,2419],{"__ignoreMap":316},[320,2179,2180,2182,2185,2187,2189,2191,2194,2196,2199,2201,2203,2206,2208,2210,2212,2214,2217,2219,2222],{"class":322,"line":323},[320,2181,966],{"class":422},[320,2183,2184],{"class":340}," typeface ",[320,2186,344],{"class":387},[320,2188,384],{"class":330},[320,2190,388],{"class":387},[320,2192,2193],{"class":330},"Typeface",[320,2195,388],{"class":387},[320,2197,2198],{"class":409},"from_name",[320,2200,477],{"class":326},[320,2202,793],{"class":347},[320,2204,2205],{"class":351},"Helvetica",[320,2207,793],{"class":347},[320,2209,489],{"class":326},[320,2211,384],{"class":330},[320,2213,388],{"class":387},[320,2215,2216],{"class":330},"FontStyle",[320,2218,388],{"class":387},[320,2220,2221],{"class":409},"bold",[320,2223,2224],{"class":326},"())\n",[320,2226,2227,2230,2233,2235,2237,2239,2241,2243,2245],{"class":322,"line":337},[320,2228,2229],{"class":387},"    .",[320,2231,2232],{"class":409},"unwrap_or_else",[320,2234,477],{"class":326},[320,2236,602],{"class":330},[320,2238,388],{"class":387},[320,2240,2193],{"class":330},[320,2242,388],{"class":387},[320,2244,584],{"class":340},[320,2246,524],{"class":326},[320,2248,2249,2251,2253,2255,2257,2259,2261,2263,2266,2268,2270,2273,2275,2278,2280,2282],{"class":322,"line":403},[320,2250,966],{"class":422},[320,2252,738],{"class":340},[320,2254,344],{"class":387},[320,2256,384],{"class":330},[320,2258,388],{"class":387},[320,2260,747],{"class":330},[320,2262,388],{"class":387},[320,2264,2265],{"class":409},"from_typeface",[320,2267,477],{"class":326},[320,2269,480],{"class":387},[320,2271,2272],{"class":340},"typeface",[320,2274,489],{"class":326},[320,2276,2277],{"class":510}," 24",[320,2279,187],{"class":668},[320,2281,671],{"class":510},[320,2283,524],{"class":326},[320,2285,2286],{"class":322,"line":419},[320,2287,400],{"emptyLinePlaceholder":399},[320,2289,2290,2292,2294,2296,2298,2300,2302,2304],{"class":322,"line":445},[320,2291,966],{"class":422},[320,2293,426],{"class":422},[320,2295,570],{"class":340},[320,2297,344],{"class":387},[320,2299,1714],{"class":330},[320,2301,388],{"class":387},[320,2303,584],{"class":409},[320,2305,442],{"class":326},[320,2307,2308,2310,2312,2314,2316,2318,2320,2323],{"class":322,"line":450},[320,2309,710],{"class":340},[320,2311,187],{"class":387},[320,2313,597],{"class":409},[320,2315,477],{"class":326},[320,2317,607],{"class":330},[320,2319,388],{"class":387},[320,2321,2322],{"class":775},"BLACK",[320,2324,524],{"class":326},[320,2326,2327,2329,2331,2333,2335,2337,2340,2342,2344,2346,2348,2350,2352,2354,2356,2358,2360,2362,2364,2366,2368,2370,2372],{"class":322,"line":457},[320,2328,547],{"class":340},[320,2330,187],{"class":387},[320,2332,788],{"class":409},[320,2334,477],{"class":326},[320,2336,793],{"class":347},[320,2338,2339],{"class":351},"Заголовок раздела",[320,2341,793],{"class":347},[320,2343,489],{"class":326},[320,2345,803],{"class":326},[320,2347,665],{"class":510},[320,2349,187],{"class":668},[320,2351,671],{"class":510},[320,2353,489],{"class":326},[320,2355,676],{"class":510},[320,2357,187],{"class":668},[320,2359,671],{"class":510},[320,2361,519],{"class":326},[320,2363,824],{"class":387},[320,2365,827],{"class":340},[320,2367,489],{"class":326},[320,2369,824],{"class":387},[320,2371,710],{"class":340},[320,2373,524],{"class":326},[320,2375,2376],{"class":322,"line":498},[320,2377,400],{"emptyLinePlaceholder":399},[320,2379,2380],{"class":322,"line":527},[320,2381,2382],{"class":453},"\u002F\u002F Текст поменьше для основного содержимого\n",[320,2384,2385,2387,2390,2392,2394,2396,2398,2400,2402,2404,2406,2408,2410,2413,2415,2417],{"class":322,"line":532},[320,2386,966],{"class":422},[320,2388,2389],{"class":340}," body_font ",[320,2391,344],{"class":387},[320,2393,384],{"class":330},[320,2395,388],{"class":387},[320,2397,747],{"class":330},[320,2399,388],{"class":387},[320,2401,2265],{"class":409},[320,2403,477],{"class":326},[320,2405,480],{"class":387},[320,2407,2272],{"class":340},[320,2409,489],{"class":326},[320,2411,2412],{"class":510}," 12",[320,2414,187],{"class":668},[320,2416,671],{"class":510},[320,2418,524],{"class":326},[320,2420,2421,2423,2425,2427,2429,2431,2434,2436,2438,2440,2442,2444,2446,2448,2450,2452,2454,2456,2458,2461,2463,2465,2467],{"class":322,"line":552},[320,2422,547],{"class":340},[320,2424,187],{"class":387},[320,2426,788],{"class":409},[320,2428,477],{"class":326},[320,2430,793],{"class":347},[320,2432,2433],{"class":351},"Основной текст.",[320,2435,793],{"class":347},[320,2437,489],{"class":326},[320,2439,803],{"class":326},[320,2441,665],{"class":510},[320,2443,187],{"class":668},[320,2445,671],{"class":510},[320,2447,489],{"class":326},[320,2449,694],{"class":510},[320,2451,187],{"class":668},[320,2453,671],{"class":510},[320,2455,519],{"class":326},[320,2457,824],{"class":387},[320,2459,2460],{"class":340},"body_font",[320,2462,489],{"class":326},[320,2464,824],{"class":387},[320,2466,710],{"class":340},[320,2468,524],{"class":326},[175,2470,2471],{},"Текст в итоговом PDF выделяем и доступен для поиска — это не растеризованные изображения символов.",[1677,2473,2475],{"id":2474},"аффинные-преобразования","Аффинные преобразования",[311,2477,2479],{"className":372,"code":2478,"language":126,"meta":316,"style":316},"canvas.save();\ncanvas.translate((200.0, 300.0));\ncanvas.rotate(45.0, None);\ncanvas.draw_rect(Rect::from_xywh(-50.0, -25.0, 100.0, 50.0), &paint);\ncanvas.restore();\n",[192,2480,2481,2492,2521,2545,2608],{"__ignoreMap":316},[320,2482,2483,2485,2487,2490],{"class":322,"line":323},[320,2484,547],{"class":340},[320,2486,187],{"class":387},[320,2488,2489],{"class":409},"save",[320,2491,442],{"class":326},[320,2493,2494,2496,2498,2501,2503,2506,2508,2510,2512,2515,2517,2519],{"class":322,"line":337},[320,2495,547],{"class":340},[320,2497,187],{"class":387},[320,2499,2500],{"class":409},"translate",[320,2502,507],{"class":326},[320,2504,2505],{"class":510},"200",[320,2507,187],{"class":668},[320,2509,671],{"class":510},[320,2511,489],{"class":326},[320,2513,2514],{"class":510}," 300",[320,2516,187],{"class":668},[320,2518,671],{"class":510},[320,2520,630],{"class":326},[320,2522,2523,2525,2527,2530,2532,2535,2537,2539,2541,2543],{"class":322,"line":403},[320,2524,547],{"class":340},[320,2526,187],{"class":387},[320,2528,2529],{"class":409},"rotate",[320,2531,477],{"class":326},[320,2533,2534],{"class":510},"45",[320,2536,187],{"class":668},[320,2538,671],{"class":510},[320,2540,489],{"class":326},[320,2542,492],{"class":330},[320,2544,524],{"class":326},[320,2546,2547,2549,2551,2553,2555,2557,2559,2561,2563,2566,2568,2570,2572,2574,2577,2580,2582,2584,2586,2588,2590,2592,2594,2596,2598,2600,2602,2604,2606],{"class":322,"line":419},[320,2548,547],{"class":340},[320,2550,187],{"class":387},[320,2552,641],{"class":409},[320,2554,477],{"class":326},[320,2556,655],{"class":330},[320,2558,388],{"class":387},[320,2560,660],{"class":409},[320,2562,477],{"class":326},[320,2564,2565],{"class":387},"-",[320,2567,1823],{"class":510},[320,2569,187],{"class":668},[320,2571,671],{"class":510},[320,2573,489],{"class":326},[320,2575,2576],{"class":387}," -",[320,2578,2579],{"class":510},"25",[320,2581,187],{"class":668},[320,2583,671],{"class":510},[320,2585,489],{"class":326},[320,2587,694],{"class":510},[320,2589,187],{"class":668},[320,2591,671],{"class":510},[320,2593,489],{"class":326},[320,2595,1832],{"class":510},[320,2597,187],{"class":668},[320,2599,671],{"class":510},[320,2601,519],{"class":326},[320,2603,824],{"class":387},[320,2605,710],{"class":340},[320,2607,524],{"class":326},[320,2609,2610,2612,2614,2617],{"class":322,"line":445},[320,2611,547],{"class":340},[320,2613,187],{"class":387},[320,2615,2616],{"class":409},"restore",[320,2618,442],{"class":326},[175,2620,2621],{},"Преобразования можно комбинировать, и они напрямую транслируются в матрицу преобразования PDF — без всякой растеризации.",[197,2623,2625],{"id":2624},"встраивание-изображений-в-pdf-сгенерированные-на-rust","Встраивание изображений в PDF, сгенерированные на Rust",[175,2627,2628],{},"Именно на встраивании изображений многие библиотеки без нужды раздувают размер файла. Skia подходит к этому разумно:",[311,2630,2632],{"className":372,"code":2631,"language":126,"meta":316,"style":316},"let image_data = std::fs::read(\"photo.jpg\").unwrap();\nlet data = skia_safe::Data::new_copy(&image_data);\nlet image = skia_safe::Image::from_encoded(data).expect(\"Не удалось декодировать изображение\");\n\ncanvas.draw_image(&image, (72.0, 200.0), None);\n",[192,2633,2634,2672,2702,2746,2750],{"__ignoreMap":316},[320,2635,2636,2638,2641,2643,2646,2648,2650,2652,2655,2657,2659,2662,2664,2666,2668,2670],{"class":322,"line":323},[320,2637,966],{"class":422},[320,2639,2640],{"class":340}," image_data ",[320,2642,344],{"class":387},[320,2644,2645],{"class":330}," std",[320,2647,388],{"class":387},[320,2649,882],{"class":330},[320,2651,388],{"class":387},[320,2653,2654],{"class":409},"read",[320,2656,477],{"class":326},[320,2658,793],{"class":347},[320,2660,2661],{"class":351},"photo.jpg",[320,2663,793],{"class":347},[320,2665,906],{"class":326},[320,2667,187],{"class":387},[320,2669,911],{"class":409},[320,2671,442],{"class":326},[320,2673,2674,2676,2679,2681,2683,2685,2688,2690,2693,2695,2697,2700],{"class":322,"line":337},[320,2675,966],{"class":422},[320,2677,2678],{"class":340}," data ",[320,2680,344],{"class":387},[320,2682,384],{"class":330},[320,2684,388],{"class":387},[320,2686,2687],{"class":330},"Data",[320,2689,388],{"class":387},[320,2691,2692],{"class":409},"new_copy",[320,2694,477],{"class":326},[320,2696,480],{"class":387},[320,2698,2699],{"class":340},"image_data",[320,2701,524],{"class":326},[320,2703,2704,2706,2709,2711,2713,2715,2718,2720,2723,2725,2728,2730,2732,2735,2737,2739,2742,2744],{"class":322,"line":403},[320,2705,966],{"class":422},[320,2707,2708],{"class":340}," image ",[320,2710,344],{"class":387},[320,2712,384],{"class":330},[320,2714,388],{"class":387},[320,2716,2717],{"class":330},"Image",[320,2719,388],{"class":387},[320,2721,2722],{"class":409},"from_encoded",[320,2724,477],{"class":326},[320,2726,2727],{"class":340},"data",[320,2729,906],{"class":326},[320,2731,187],{"class":387},[320,2733,2734],{"class":409},"expect",[320,2736,477],{"class":326},[320,2738,793],{"class":347},[320,2740,2741],{"class":351},"Не удалось декодировать изображение",[320,2743,793],{"class":347},[320,2745,524],{"class":326},[320,2747,2748],{"class":322,"line":419},[320,2749,400],{"emptyLinePlaceholder":399},[320,2751,2752,2754,2756,2759,2761,2763,2766,2768,2770,2772,2774,2776,2778,2780,2782,2784,2786,2788],{"class":322,"line":445},[320,2753,547],{"class":340},[320,2755,187],{"class":387},[320,2757,2758],{"class":409},"draw_image",[320,2760,477],{"class":326},[320,2762,480],{"class":387},[320,2764,2765],{"class":340},"image",[320,2767,489],{"class":326},[320,2769,803],{"class":326},[320,2771,665],{"class":510},[320,2773,187],{"class":668},[320,2775,671],{"class":510},[320,2777,489],{"class":326},[320,2779,1841],{"class":510},[320,2781,187],{"class":668},[320,2783,671],{"class":510},[320,2785,519],{"class":326},[320,2787,492],{"class":330},[320,2789,524],{"class":326},[175,2791,2792],{},"Когда поддержка JPEG-кодирования настроена в метаданных, Skia передаёт JPEG-данные напрямую в PDF без перекодирования — сохраняя качество и минимальный размер файла. Без этого изображения сжимаются deflate-алгоритмом, что может значительно увеличить размер файла для фотографий.",[175,2794,2795],{},"Управление через метаданные:",[311,2797,2799],{"className":372,"code":2798,"language":126,"meta":316,"style":316},"let metadata = Metadata {\n    encoding_quality: 85, \u002F\u002F Качество JPEG (101 = без потерь\u002Fdeflate)\n    ..Default::default()\n};\n",[192,2800,2801,2813,2828,2840],{"__ignoreMap":316},[320,2802,2803,2805,2807,2809,2811],{"class":322,"line":323},[320,2804,966],{"class":422},[320,2806,1155],{"class":340},[320,2808,344],{"class":387},[320,2810,1141],{"class":330},[320,2812,416],{"class":326},[320,2814,2815,2818,2820,2823,2825],{"class":322,"line":337},[320,2816,2817],{"class":340},"    encoding_quality",[320,2819,1113],{"class":387},[320,2821,2822],{"class":510}," 85",[320,2824,489],{"class":326},[320,2826,2827],{"class":453}," \u002F\u002F Качество JPEG (101 = без потерь\u002Fdeflate)\n",[320,2829,2830,2832,2834,2836,2838],{"class":322,"line":403},[320,2831,1248],{"class":387},[320,2833,1251],{"class":330},[320,2835,388],{"class":387},[320,2837,584],{"class":409},[320,2839,1258],{"class":326},[320,2841,2842],{"class":322,"line":419},[320,2843,1144],{"class":326},[197,2845,2847],{"id":2846},"сравнение-rust-библиотек-для-генерации-pdf-skia-safe-vs-printpdf-vs-krilla","Сравнение Rust-библиотек для генерации PDF: skia-safe vs printpdf vs krilla",[175,2849,2850],{},"Экосистема Rust предлагает несколько подходов к генерации PDF. Сравнение:",[2852,2853,2854,2885],"table",{},[2855,2856,2857],"thead",{},[2858,2859,2860,2864,2867,2870,2873,2876,2879,2882],"tr",{},[2861,2862,2863],"th",{},"Библиотека",[2861,2865,2866],{},"Тип",[2861,2868,2869],{},"Вектор",[2861,2871,2872],{},"Подмножества шрифтов",[2861,2874,2875],{},"PDF\u002FA",[2861,2877,2878],{},"Теговый PDF",[2861,2880,2881],{},"Движок вёрстки",[2861,2883,2884],{},"Зрелость",[2886,2887,2888,2914,2939,2963,2987],"tbody",{},[2858,2889,2890,2895,2898,2901,2904,2906,2908,2911],{},[2891,2892,2893],"td",{},[179,2894,194],{},[2891,2896,2897],{},"Обёртка Skia (C++ FFI)",[2891,2899,2900],{},"Да",[2891,2902,2903],{},"Да (HarfBuzz)",[2891,2905,2900],{},[2891,2907,2900],{},[2891,2909,2910],{},"Нет (canvas API)",[2891,2912,2913],{},"20+ лет (Skia)",[2858,2915,2916,2921,2924,2926,2929,2932,2934,2936],{},[2891,2917,2918],{},[179,2919,2920],{},"printpdf",[2891,2922,2923],{},"Чистый Rust",[2891,2925,2900],{},[2891,2927,2928],{},"Ограничено",[2891,2930,2931],{},"Нет",[2891,2933,2931],{},[2891,2935,2931],{},[2891,2937,2938],{},"Стабильный",[2858,2940,2941,2946,2949,2951,2953,2955,2957,2960],{},[2891,2942,2943],{},[179,2944,2945],{},"genpdf",[2891,2947,2948],{},"Чистый Rust (на printpdf)",[2891,2950,2900],{},[2891,2952,2928],{},[2891,2954,2931],{},[2891,2956,2931],{},[2891,2958,2959],{},"Базовый",[2891,2961,2962],{},"Не поддерживается",[2858,2964,2965,2970,2973,2975,2978,2980,2982,2984],{},[2891,2966,2967],{},[179,2968,2969],{},"krilla",[2891,2971,2972],{},"Чистый Rust (на pdf-writer)",[2891,2974,2900],{},[2891,2976,2977],{},"Да (OpenType)",[2891,2979,2900],{},[2891,2981,2900],{},[2891,2983,2931],{},[2891,2985,2986],{},"Новее",[2858,2988,2989,2994,2997,2999,3001,3003,3005,3008],{},[2891,2990,2991],{},[179,2992,2993],{},"Headless Chrome",[2891,2995,2996],{},"Браузерный runtime",[2891,2998,2900],{},[2891,3000,2900],{},[2891,3002,2931],{},[2891,3004,2931],{},[2891,3006,3007],{},"Полный HTML\u002FCSS",[2891,3009,3010],{},"Зрелый",[175,3012,3013,3015],{},[179,3014,194],{}," даёт полноценный 2D-графический API с более чем 20-летним опытом разработки в Google. Векторный вывод, подмножества шрифтов, теговые PDF, поддержка PDF\u002FA. Компромисс — зависимость от C++, хотя с предсобранными бинарниками на практике она почти не мешает.",[175,3017,3018,3020],{},[179,3019,2920],{}," — чистый Rust без C-зависимостей. Хорошо работает для простых документов, но требует ручного позиционирования каждого элемента. Нет движка вёрстки, ограниченная поддержка типографики.",[175,3022,3023,3025],{},[179,3024,2945],{}," строит API вёрстки более высокого уровня поверх printpdf. Чистый Rust и проще в использовании для простых документов, но проект не поддерживается уже несколько лет.",[175,3027,3028,3030],{},[179,3029,2969],{}," — более новая библиотека на чистом Rust с современным API, отличной поддержкой OpenType и функциями вроде градиентов и режимов наложения. Хороший выбор, если вам важно обойтись без C-зависимостей, хотя она проверена меньше, чем Skia.",[175,3032,3033,3036],{},[179,3034,3035],{},"Headless Chrome \u002F Puppeteer"," даёт полный рендеринг HTML\u002FCSS, но требует поставки браузерного runtime. Тяжеловесно, но полезно, когда нужно рендерить сложные веб-макеты.",[175,3038,3039],{},"Выбор зависит от ваших ограничений. Если вам нужен точный контроль над графикой и типографикой, проверенная надёжность и вас устраивает C++-зависимость — Skia будет самым мощным вариантом. Если нужно решение на чистом Rust — стоит оценить krilla или printpdf.",[197,3041,3043],{"id":3042},"известные-ограничения-pdf-бэкенда-skia","Известные ограничения PDF-бэкенда Skia",[175,3045,3046],{},"PDF-бэкенд Skia покрывает почти всё, но у него есть границы, о которых стоит знать:",[175,3048,3049,3052,3053,236,3056,236,3059,3062,3063,3066],{},[179,3050,3051],{},"Поддержка режимов наложения."," Большинство стандартных PDF-режимов наложения работают (Multiply, Screen, Overlay и т.д.), но некоторые специфичные для Skia режимы, такие как ",[192,3054,3055],{},"SrcATop",[192,3057,3058],{},"DstATop",[192,3060,3061],{},"Xor"," и ",[192,3064,3065],{},"Plus",", не имеют PDF-эквивалента. Если вы их используете, результат может отличаться от того, что вы видите на экране.",[175,3068,3069,3072,3073,3076],{},[179,3070,3071],{},"Растеризация как запасной вариант."," Функции без нативного PDF-представления — такие как перспективные преобразования текста — растеризуются с DPI, указанным в метаданных (",[192,3074,3075],{},"raster_dpi",", по умолчанию 72). Для печатного качества поднимите его до 300 и выше — ценой большего размера файлов.",[175,3078,3079,3082,3083,3086],{},[179,3080,3081],{},"Сборка из исходников."," Если вам нужно настроить возможности Skia сверх того, что дают предсобранные бинарники, сборка из исходников занимает 20–40 минут. Фича ",[192,3084,3085],{},"binary-cache"," (включена по умолчанию) позволяет этого избежать для стандартных конфигураций.",[197,3088,3090],{"id":3089},"продакшен-архитектура-для-генерации-pdf-на-rust","Продакшен-архитектура для генерации PDF на Rust",[175,3092,3093],{},"Для продакшен-систем, генерирующих множество документов — счетов, отчётов, сертификатов — чистая архитектура разделяет контент и рендеринг:",[311,3095,3097],{"className":372,"code":3096,"language":126,"meta":316,"style":316},"struct DocumentContent {\n    title: String,\n    sections: Vec\u003CSection>,\n    footer: String,\n}\n\nstruct Section {\n    heading: String,\n    body: String,\n    charts: Vec\u003CChartData>,\n}\n\nfn render_document(content: &DocumentContent) -> Vec\u003Cu8> {\n    let mut output = Vec::new();\n\n    let metadata = Metadata {\n        title: content.title.clone(),\n        creator: \"My Document Engine\".to_string(),\n        ..Default::default()\n    };\n\n    let mut doc = pdf::new_document(&mut output, Some(&metadata));\n\n    for section in &content.sections {\n        let mut page = doc.begin_page((595, 842), None); \u002F\u002F A4\n        render_section(page.canvas(), section);\n        doc = page.end_page();\n    }\n\n    doc.close();\n    output\n}\n",[192,3098,3099,3109,3120,3138,3149,3153,3157,3166,3177,3188,3204,3208,3212,3248,3266,3270,3282,3304,3324,3337,3342,3346,3383,3387,3411,3447,3468,3483,3488,3492,3504,3510],{"__ignoreMap":316},[320,3100,3101,3104,3107],{"class":322,"line":323},[320,3102,3103],{"class":422},"struct",[320,3105,3106],{"class":330}," DocumentContent",[320,3108,416],{"class":326},[320,3110,3111,3113,3115,3118],{"class":322,"line":337},[320,3112,1166],{"class":340},[320,3114,1113],{"class":387},[320,3116,3117],{"class":330}," String",[320,3119,713],{"class":326},[320,3121,3122,3125,3127,3129,3132,3135],{"class":322,"line":403},[320,3123,3124],{"class":340},"    sections",[320,3126,1113],{"class":387},[320,3128,434],{"class":330},[320,3130,3131],{"class":326},"\u003C",[320,3133,3134],{"class":330},"Section",[320,3136,3137],{"class":326},">,\n",[320,3139,3140,3143,3145,3147],{"class":322,"line":419},[320,3141,3142],{"class":340},"    footer",[320,3144,1113],{"class":387},[320,3146,3117],{"class":330},[320,3148,713],{"class":326},[320,3150,3151],{"class":322,"line":445},[320,3152,919],{"class":326},[320,3154,3155],{"class":322,"line":450},[320,3156,400],{"emptyLinePlaceholder":399},[320,3158,3159,3161,3164],{"class":322,"line":457},[320,3160,3103],{"class":422},[320,3162,3163],{"class":330}," Section",[320,3165,416],{"class":326},[320,3167,3168,3171,3173,3175],{"class":322,"line":498},[320,3169,3170],{"class":340},"    heading",[320,3172,1113],{"class":387},[320,3174,3117],{"class":330},[320,3176,713],{"class":326},[320,3178,3179,3182,3184,3186],{"class":322,"line":527},[320,3180,3181],{"class":340},"    body",[320,3183,1113],{"class":387},[320,3185,3117],{"class":330},[320,3187,713],{"class":326},[320,3189,3190,3193,3195,3197,3199,3202],{"class":322,"line":532},[320,3191,3192],{"class":340},"    charts",[320,3194,1113],{"class":387},[320,3196,434],{"class":330},[320,3198,3131],{"class":326},[320,3200,3201],{"class":330},"ChartData",[320,3203,3137],{"class":326},[320,3205,3206],{"class":322,"line":552},[320,3207,919],{"class":326},[320,3209,3210],{"class":322,"line":557},[320,3211,400],{"emptyLinePlaceholder":399},[320,3213,3214,3216,3219,3221,3224,3226,3228,3231,3233,3236,3238,3240,3243,3246],{"class":322,"line":563},[320,3215,406],{"class":380},[320,3217,3218],{"class":409}," render_document",[320,3220,477],{"class":326},[320,3222,3223],{"class":340},"content",[320,3225,1113],{"class":387},[320,3227,824],{"class":387},[320,3229,3230],{"class":330},"DocumentContent",[320,3232,906],{"class":326},[320,3234,3235],{"class":387}," ->",[320,3237,434],{"class":330},[320,3239,3131],{"class":326},[320,3241,3242],{"class":330},"u8",[320,3244,3245],{"class":326},">",[320,3247,416],{"class":326},[320,3249,3250,3252,3254,3256,3258,3260,3262,3264],{"class":322,"line":589},[320,3251,423],{"class":422},[320,3253,426],{"class":422},[320,3255,429],{"class":340},[320,3257,344],{"class":387},[320,3259,434],{"class":330},[320,3261,388],{"class":387},[320,3263,439],{"class":409},[320,3265,442],{"class":326},[320,3267,3268],{"class":322,"line":633},[320,3269,400],{"emptyLinePlaceholder":399},[320,3271,3272,3274,3276,3278,3280],{"class":322,"line":647},[320,3273,423],{"class":422},[320,3275,1155],{"class":340},[320,3277,344],{"class":387},[320,3279,1141],{"class":330},[320,3281,416],{"class":326},[320,3283,3284,3287,3289,3292,3294,3297,3299,3302],{"class":322,"line":704},[320,3285,3286],{"class":340},"        title",[320,3288,1113],{"class":387},[320,3290,3291],{"class":340}," content",[320,3293,187],{"class":387},[320,3295,3296],{"class":340},"title",[320,3298,187],{"class":387},[320,3300,3301],{"class":409},"clone",[320,3303,1183],{"class":326},[320,3305,3306,3309,3311,3313,3316,3318,3320,3322],{"class":322,"line":716},[320,3307,3308],{"class":340},"        creator",[320,3310,1113],{"class":387},[320,3312,348],{"class":347},[320,3314,3315],{"class":351},"My Document Engine",[320,3317,793],{"class":347},[320,3319,187],{"class":387},[320,3321,1180],{"class":409},[320,3323,1183],{"class":326},[320,3325,3326,3329,3331,3333,3335],{"class":322,"line":722},[320,3327,3328],{"class":387},"        ..",[320,3330,1251],{"class":330},[320,3332,388],{"class":387},[320,3334,584],{"class":409},[320,3336,1258],{"class":326},[320,3338,3339],{"class":322,"line":727},[320,3340,3341],{"class":326},"    };\n",[320,3343,3344],{"class":322,"line":733},[320,3345,400],{"emptyLinePlaceholder":399},[320,3347,3348,3350,3352,3355,3357,3359,3361,3363,3365,3367,3369,3371,3373,3375,3377,3379,3381],{"class":322,"line":756},[320,3349,423],{"class":422},[320,3351,426],{"class":422},[320,3353,3354],{"class":340}," doc ",[320,3356,344],{"class":387},[320,3358,469],{"class":330},[320,3360,388],{"class":387},[320,3362,474],{"class":409},[320,3364,477],{"class":326},[320,3366,480],{"class":387},[320,3368,483],{"class":422},[320,3370,486],{"class":340},[320,3372,489],{"class":326},[320,3374,1311],{"class":330},[320,3376,477],{"class":326},[320,3378,480],{"class":387},[320,3380,1318],{"class":340},[320,3382,630],{"class":326},[320,3384,3385],{"class":322,"line":781},[320,3386,400],{"emptyLinePlaceholder":399},[320,3388,3389,3393,3396,3399,3401,3403,3405,3408],{"class":322,"line":838},[320,3390,3392],{"class":3391},"sVHd0","    for",[320,3394,3395],{"class":340}," section ",[320,3397,3398],{"class":380},"in",[320,3400,824],{"class":387},[320,3402,3223],{"class":340},[320,3404,187],{"class":387},[320,3406,3407],{"class":340},"sections ",[320,3409,3410],{"class":326},"{\n",[320,3412,3413,3416,3418,3420,3422,3425,3427,3429,3431,3433,3435,3437,3439,3441,3444],{"class":322,"line":843},[320,3414,3415],{"class":422},"        let",[320,3417,426],{"class":422},[320,3419,1006],{"class":340},[320,3421,344],{"class":387},[320,3423,3424],{"class":340}," doc",[320,3426,187],{"class":387},[320,3428,504],{"class":409},[320,3430,507],{"class":326},[320,3432,1462],{"class":510},[320,3434,489],{"class":326},[320,3436,1467],{"class":510},[320,3438,519],{"class":326},[320,3440,492],{"class":330},[320,3442,3443],{"class":326},");",[320,3445,3446],{"class":453}," \u002F\u002F A4\n",[320,3448,3449,3452,3454,3456,3458,3460,3463,3466],{"class":322,"line":849},[320,3450,3451],{"class":409},"        render_section",[320,3453,477],{"class":326},[320,3455,1483],{"class":340},[320,3457,187],{"class":387},[320,3459,547],{"class":409},[320,3461,3462],{"class":326},"(),",[320,3464,3465],{"class":340}," section",[320,3467,524],{"class":326},[320,3469,3470,3473,3475,3477,3479,3481],{"class":322,"line":869},[320,3471,3472],{"class":340},"        doc ",[320,3474,344],{"class":387},[320,3476,1039],{"class":340},[320,3478,187],{"class":387},[320,3480,857],{"class":409},[320,3482,442],{"class":326},[320,3484,3485],{"class":322,"line":874},[320,3486,3487],{"class":326},"    }\n",[320,3489,3490],{"class":322,"line":916},[320,3491,400],{"emptyLinePlaceholder":399},[320,3493,3495,3498,3500,3502],{"class":322,"line":3494},30,[320,3496,3497],{"class":340},"    doc",[320,3499,187],{"class":387},[320,3501,864],{"class":409},[320,3503,442],{"class":326},[320,3505,3507],{"class":322,"line":3506},31,[320,3508,3509],{"class":340},"    output\n",[320,3511,3513],{"class":322,"line":3512},32,[320,3514,919],{"class":326},[175,3516,3517,3518,3520],{},"Такое разделение означает, что ваша бизнес-логика никогда не касается PDF API напрямую. Контент поступает на вход, байты — на выход. Вы можете тестировать рендеринг независимо, заменить PDF-бэкенд без изменения бизнес-логики и распараллелить генерацию документов по потокам — поскольку каждый ",[192,3519,942],{}," владеет своим выходным буфером.",[197,3522,3524],{"id":3523},"с-чего-начать-генерацию-pdf-на-rust-через-skia","С чего начать генерацию PDF на Rust через Skia",[175,3526,3527,3528,3530,3531,3534],{},"Добавьте ",[192,3529,194],{}," в ваш ",[192,3532,3533],{},"Cargo.toml",", напишите несколько строк кода для рисования и откройте полученный PDF. Кривая обучения пологая, если вы использовали любой 2D-графический API — Canvas, Cairo, Core Graphics или даже HTML5 Canvas. Концепции переносятся напрямую.",[175,3536,219,3537,3539,3540,3544],{},[192,3538,194],{}," находится на версии 0.93 с более чем ",[211,3541,3543],{"href":213,"rel":3542},[215],"2 миллионами загрузок на crates.io",". Кеш предсобранных бинарников означает, что первая сборка быстрая, а паттерн typestate гарантирует, что ваш первый PDF будет корректным.",[175,3546,3547],{},"Для приложений, которым нужен надёжный, качественный PDF-вывод — будь то счета, отчёты, билеты, сертификаты или любой документ, где точность вёрстки имеет значение — Skia через Rust даёт вам тот же движок рендеринга, который работает на миллиардах устройств, с гарантиями безопасности, которыми славится Rust.",[175,3549,3550,3551,3555],{},"Именно этот подход мы использовали при создании ",[211,3552,3554],{"href":3553},"\u002Fopen-source\u002Fdxpdf","dxpdf"," — нашего open-source конвертера DOCX в PDF, написанного на Rust и использующего Skia. Он конвертирует документы Word в высококачественные PDF за ~115 мс без необходимости установки Microsoft Office или LibreOffice.",[197,3557,3559],{"id":3558},"часто-задаваемые-вопросы","Часто задаваемые вопросы",[1677,3561,3563],{"id":3562},"какая-rust-библиотека-лучше-всего-подходит-для-генерации-pdf","Какая Rust-библиотека лучше всего подходит для генерации PDF?",[175,3565,3566,3567,3569,3570,3062,3572,3574],{},"Зависит от ваших требований. ",[179,3568,194],{}," — наиболее функционально полный вариант, предлагающий векторный вывод, подмножества шрифтов, теговые PDF и поддержку PDF\u002FA через движок Google Skia. Для чистого Rust без C-зависимостей ",[179,3571,2969],{},[179,3573,2920],{}," — сильные альтернативы. Для рендеринга HTML\u002FCSS в PDF headless Chrome остаётся наиболее надёжным подходом.",[1677,3576,3578],{"id":3577},"skia-создаёт-векторные-или-растеризованные-pdf","Skia создаёт векторные или растеризованные PDF?",[175,3580,225,3581,3583],{},[179,3582,228],{},". Текст выделяем и доступен для поиска, фигуры остаются чёткими при любом масштабе, а операции рисования транслируются в нативные PDF-примитивы. Растеризация происходит только как запасной вариант для функций, не имеющих нативного PDF-представления, таких как перспективные трансформации.",[1677,3585,3587],{"id":3586},"можно-ли-создавать-доступные-и-pdfa-совместимые-документы-с-skia-safe","Можно ли создавать доступные и PDF\u002FA-совместимые документы с skia-safe?",[175,3589,3590,3591,3594,3595,3597],{},"Да. Skia поддерживает деревья структурных элементов PDF и идентификаторы узлов для создания теговых, доступных PDF, соответствующих стандартам PDF\u002FUA. Для архивного соответствия установка ",[192,3592,3593],{},"pdf_a: true"," в структуре ",[192,3596,1112],{}," создаёт вывод, соответствующий PDF\u002FA-2b.",[1677,3599,3601],{"id":3600},"сколько-времени-занимает-сборка-skia-safe","Сколько времени занимает сборка skia-safe?",[175,3603,3604,3605,3607,3608,3610],{},"С включённой по умолчанию фичей ",[192,3606,3085],{}," первая сборка занимает ",[179,3609,254],{},", поскольку для вашей платформы скачиваются предсобранные бинарники Skia. Без кеша (сборка Skia из C++-исходников) — рассчитывайте на 20–40 минут.",[1677,3612,3614],{"id":3613},"skia-safe-это-тот-же-движок-который-chrome-использует-для-печати-в-pdf","skia-safe — это тот же движок, который Chrome использует для печати в PDF?",[175,3616,3617,3618,3620],{},"Да. Когда вы используете функцию Chrome «Сохранить как PDF» или «Печать в PDF», браузер рендерит страницу через PDF-бэкенд Skia — тот же движок, который ",[192,3619,194],{}," предоставляет для Rust. Этот же движок рендеринга Android использует для отрисовки интерфейса, а Flutter — для кроссплатформенных приложений.",[1677,3622,3624],{"id":3623},"как-skia-safe-работает-со-шрифтами-в-pdf","Как skia-safe работает со шрифтами в PDF?",[175,3626,3627,3628,3631],{},"Skia встраивает шрифты напрямую в PDF. С интеграцией HarfBuzz (включается через фичу ",[192,3629,3630],{},"textlayout",") выполняется построение подмножеств шрифтов — удаление неиспользуемых глифов для поддержания компактного размера файлов. Шрифты TrueType используют кодирование по идентификаторам глифов для точного рендеринга.",[3633,3634,3635],"style",{},"html pre.shiki code .sP7_E, html code.shiki .sP7_E{--shiki-light:#39ADB5;--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .sbgvK, html code.shiki .sbgvK{--shiki-light:#E2931D;--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .su5hD, html code.shiki .su5hD{--shiki-light:#90A4AE;--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .sjJ54, html code.shiki .sjJ54{--shiki-light:#39ADB5;--shiki-default:#032F62;--shiki-dark:#9ECBFF}html pre.shiki code .s_sjI, html code.shiki .s_sjI{--shiki-light:#91B859;--shiki-default:#032F62;--shiki-dark:#9ECBFF}html .light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html.light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .sw1J6, html code.shiki .sw1J6{--shiki-light:#F76D47;--shiki-default:#D73A49;--shiki-dark:#F97583}html pre.shiki code .smGrS, html code.shiki .smGrS{--shiki-light:#39ADB5;--shiki-default:#D73A49;--shiki-dark:#F97583}html pre.shiki code .s3--O, html code.shiki .s3--O{--shiki-light:#E2931D;--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .sGLFI, html code.shiki .sGLFI{--shiki-light:#6182B8;--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .sbsja, html code.shiki .sbsja{--shiki-light:#9C3EDA;--shiki-default:#D73A49;--shiki-dark:#F97583}html pre.shiki code .sutJx, html code.shiki .sutJx{--shiki-light:#90A4AE;--shiki-light-font-style:italic;--shiki-default:#6A737D;--shiki-default-font-style:inherit;--shiki-dark:#6A737D;--shiki-dark-font-style:inherit}html pre.shiki code .srdBf, html code.shiki .srdBf{--shiki-light:#F76D47;--shiki-default:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .s39Yj, html code.shiki .s39Yj{--shiki-light:#39ADB5;--shiki-default:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .s_hVV, html code.shiki .s_hVV{--shiki-light:#90A4AE;--shiki-default:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .sVHd0, html code.shiki .sVHd0{--shiki-light:#39ADB5;--shiki-light-font-style:italic;--shiki-default:#D73A49;--shiki-default-font-style:inherit;--shiki-dark:#F97583;--shiki-dark-font-style:inherit}",{"title":316,"searchDepth":337,"depth":337,"links":3637},[3638,3639,3640,3641,3642,3643,3644,3645,3650,3651,3652,3653,3654,3655],{"id":199,"depth":337,"text":200},{"id":258,"depth":337,"text":259},{"id":300,"depth":337,"text":301},{"id":365,"depth":337,"text":366},{"id":935,"depth":337,"text":936},{"id":1105,"depth":337,"text":1106},{"id":1374,"depth":337,"text":1375},{"id":1668,"depth":337,"text":1669,"children":3646},[3647,3648,3649],{"id":1679,"depth":403,"text":1680},{"id":2171,"depth":403,"text":2172},{"id":2474,"depth":403,"text":2475},{"id":2624,"depth":337,"text":2625},{"id":2846,"depth":337,"text":2847},{"id":3042,"depth":337,"text":3043},{"id":3089,"depth":337,"text":3090},{"id":3523,"depth":337,"text":3524},{"id":3558,"depth":337,"text":3559,"children":3656},[3657,3658,3659,3660,3661,3662],{"id":3562,"depth":403,"text":3563},{"id":3577,"depth":403,"text":3578},{"id":3586,"depth":403,"text":3587},{"id":3600,"depth":403,"text":3601},{"id":3613,"depth":403,"text":3614},{"id":3623,"depth":403,"text":3624},"Программная генерация PDF — одна из тех задач, которые кажутся простыми, пока дело не доходит до реализации. Большинство библиотек либо дают низкоуровневый поток байтов, с которым приходится бороться, либо шаблонизатор высокого уровня, который ломается, как только требуется точный контроль над вёрсткой, типографикой или графикой. Но есть золотая середина — тот же графический движок, который рендерит каждую веб-страницу при печати в PDF в Chrome, каждый экран Android и каждый кадр приложения на Flutter: Google Skia.","md",{"type":2765,"src":3666},"\u002Fblog\u002Fskia-rust-pdf-rendering-hero.webp",{},"Как создавать векторные PDF на Rust с помощью крейта skia-safe — обёрток для Google Skia. Примеры кода, сравнение библиотек и продакшен-паттерны.","Генерация PDF на Rust с помощью Skia","Движок, которым Chrome печатает в PDF, — под управлением из Rust. Как получать настоящие векторные PDF через skia-safe: код и продакшен-паттерны.","\u002Fblog\u002Fskia-rust-pdf-rendering",{"title":169,"description":3663},"skia-rust-pdf-rendering","blog\u002Fskia-rust-pdf-rendering","Узнайте, как создавать качественные векторные PDF-документы на Rust с помощью крейта skia-safe — безопасных Rust-обёрток для Google Skia, движка 2D-рендеринга, который стоит за Chrome, Android и Flutter. Примеры кода, сравнение Rust PDF-библиотек и архитектурные паттерны для продакшена.","2026-03-21T15:28:51Z","software-engineering",null,"vo23FktTrM_IJ3W1y17sbU_hKoS1VVVG6FHme_V8BnM",[3681,3738,3785,3835,3924,3976],{"id":3682,"bio":3683,"expertise":3688,"extension":3704,"links":3705,"meta":3709,"metaDescription":3710,"name":3715,"ogDescription":3718,"photo":3723,"role":3724,"seniority":3729,"skills":3730,"slug":3731,"specialization":3732,"stem":3731,"__hash__":3737},"team_members\u002Fdima.yaml",{"en":3684,"ru":3685,"es":3686,"nl":3687},"Dima has been building with [Flutter](\u002Ftechnologies\u002Fflutter) since 2021, and specializes in :term[state management]{slug=\"state-management\"} and app architecture.\n\nHe has hands-on experience with real-time communication protocols — :term{slug=\"webrtc\"} for audio and video, and :term{slug=\"xmpp\"} for messaging — which makes him comfortable with the network-heavy, stateful features many teams struggle to get right.\n","Дима работает с [Flutter](\u002Ftechnologies\u002Fflutter) с 2021 года и специализируется на :term[state management]{slug=\"state-management\"} и архитектуре приложений.\n\nУ него есть практический опыт с протоколами реального времени — :term{slug=\"webrtc\"} для аудио и видео и :term{slug=\"xmpp\"} для обмена сообщениями, — поэтому ему близки сетевые stateful-фичи, которые многим командам даются с трудом.\n","Dima trabaja con [Flutter](\u002Ftechnologies\u002Fflutter) desde 2021 y se especializa en :term[gestión de estado]{slug=\"state-management\"} y arquitectura de aplicaciones.\n\nTiene experiencia práctica con protocolos de comunicación en tiempo real —:term{slug=\"webrtc\"} para audio y vídeo, y :term{slug=\"xmpp\"} para mensajería—, lo que le hace sentirse cómodo con esas funcionalidades con mucho estado y mucha red que a muchos equipos se les atragantan.\n","Dima werkt sinds 2021 met [Flutter](\u002Ftechnologies\u002Fflutter) en is gespecialiseerd in :term[state management]{slug=\"state-management\"} en applicatiearchitectuur.\n\nHij heeft praktijkervaring met protocollen voor realtime communicatie — :term{slug=\"webrtc\"} voor audio en video, en :term{slug=\"xmpp\"} voor berichten — waardoor hij zich thuis voelt bij de netwerkzware functies met veel staat waar veel teams moeite mee hebben.\n",[3689,3692,3697,3699],{"en":3690,"ru":3690,"es":3691,"nl":3690},"State management","Gestión de estado",{"en":3693,"ru":3694,"es":3695,"nl":3696},"Application architecture","Архитектура приложений","Arquitectura de aplicaciones","Applicatiearchitectuur",{"en":3698,"ru":3698,"es":3698,"nl":3698},"WebRTC",{"en":3700,"ru":3701,"es":3702,"nl":3703},"XMPP messaging","Обмен сообщениями по XMPP","Mensajería XMPP","Berichten via XMPP","yaml",[3706],{"type":3707,"address":3708},"email","konopatov@nerdy.pro",{},{"en":3711,"ru":3712,"es":3713,"nl":3714},"Lead Flutter developer at Nerdy Production, building real-time apps since 2021 — state management, app architecture, WebRTC and XMPP.","Ведущий Flutter-разработчик Nerdy Production: приложения реального времени с 2021 года — state management, архитектура, WebRTC и XMPP.","Lead de desarrollo Flutter en Nerdy Production, construyendo apps en tiempo real desde 2021: gestión de estado, arquitectura de aplicaciones, WebRTC y XMPP.","Lead Flutter-developer bij Nerdy Production, bouwt sinds 2021 realtime-apps — state management, applicatiearchitectuur, WebRTC en XMPP.",{"en":3716,"ru":3717,"es":3716,"nl":3716},"Dima","Дима",{"en":3719,"ru":3720,"es":3721,"nl":3722},"Lead Flutter developer at Nerdy Production — real-time apps since 2021, and the state management that keeps them from falling over.","Ведущий Flutter-разработчик Nerdy Production: приложения реального времени с 2021 года и state management, на котором они держатся.","Lead de desarrollo Flutter en Nerdy Production: apps en tiempo real desde 2021 y la gestión de estado que evita que se caigan.","Lead Flutter-developer bij Nerdy Production — realtime-apps sinds 2021, en het state management dat ze overeind houdt.","\u002Fteam\u002Fdima.webp",{"en":3725,"ru":3726,"es":3727,"nl":3728},"Lead Flutter Developer","Ведущий Flutter-разработчик","Lead de desarrollo Flutter","Lead Flutter-developer","lead",[41,18,155,160,90,111,150,95],"dima",{"en":3733,"ru":3734,"es":3735,"nl":3736},"Real-time apps, state management, and architecture","Приложения реального времени, state management и архитектура","Apps en tiempo real, gestión de estado y arquitectura","Realtime-apps, state management en architectuur","1TuxUt3zjdVkQUvMf9dBITG872C0exrdZyyoKw1beiE",{"id":3739,"bio":3740,"expertise":3745,"extension":3704,"links":3678,"meta":3756,"metaDescription":3757,"name":3762,"ogDescription":3765,"photo":3770,"role":3771,"seniority":3776,"skills":3777,"slug":3778,"specialization":3779,"stem":3778,"__hash__":3784},"team_members\u002Fmasha.yaml",{"en":3741,"ru":3742,"es":3743,"nl":3744},"Masha builds [Flutter](\u002Ftechnologies\u002Fflutter) apps where design and copy are treated as one job.\n\nShe specializes in UI\u002FUX — turning product requirements into clean, usable interfaces — and in the writing inside the app, from onboarding flows to the microcopy that makes a screen make sense. The result is apps that feel considered, not just functional.\n","Маша делает приложения на [Flutter](\u002Ftechnologies\u002Fflutter), где дизайн и текст — одна задача.\n\nОна специализируется на UI\u002FUX, превращая продуктовые требования в чистые и удобные интерфейсы, и на текстах внутри приложения — от онбординга до микрокопирайта, который делает экран понятным. В итоге приложения получаются продуманными, а не просто рабочими.\n","Masha crea apps en [Flutter](\u002Ftechnologies\u002Fflutter) donde el diseño y el texto se tratan como un mismo trabajo.\n\nSe especializa en UI\u002FUX —convertir requisitos de producto en interfaces limpias y usables— y en la escritura dentro de la app, desde los flujos de onboarding hasta el microcopy que hace que una pantalla se entienda. El resultado son apps que se sienten pensadas, no solo funcionales.\n","Masha bouwt [Flutter](\u002Ftechnologies\u002Fflutter)-apps waarin ontwerp en tekst als één taak worden behandeld.\n\nZe is gespecialiseerd in UI\u002FUX — productwensen omzetten in heldere, bruikbare interfaces — en in de teksten binnen de app, van onboarding tot de microteksten die een scherm begrijpelijk maken. Het resultaat zijn apps die doordacht aanvoelen en niet alleen werken.\n",[3746,3751],{"en":3747,"ru":3748,"es":3749,"nl":3750},"UI\u002FUX design","UI\u002FUX-дизайн","Diseño UI\u002FUX","UI\u002FUX-ontwerp",{"en":3752,"ru":3753,"es":3754,"nl":3755},"Product copywriting","Продуктовый копирайтинг","Redacción de producto","Productteksten",{},{"en":3758,"ru":3759,"es":3760,"nl":3761},"Flutter developer at Nerdy Production working where design meets copy — UI\u002FUX, onboarding flows, and the microcopy that makes a screen make sense.","Flutter-разработчик Nerdy Production на стыке дизайна и текста: UI\u002FUX, онбординг и микрокопирайт, который делает экран понятным.","Desarrolladora Flutter en Nerdy Production, donde el diseño se encuentra con el texto: UI\u002FUX, onboarding y el microcopy que hace que una pantalla se entienda.","Flutter-developer bij Nerdy Production op het snijvlak van ontwerp en tekst — UI\u002FUX, onboarding, en de microteksten die een scherm begrijpelijk maken.",{"en":3763,"ru":3764,"es":3763,"nl":3763},"Masha","Маша",{"en":3766,"ru":3767,"es":3768,"nl":3769},"Flutter developer at Nerdy Production working where design meets copy — onboarding flows and the microcopy that makes a screen make sense.","Flutter-разработчик Nerdy Production на стыке дизайна и текста: онбординг и микрокопирайт, который делает экран понятным.","Desarrolladora Flutter en Nerdy Production, donde el diseño se encuentra con el texto: onboarding y el microcopy que hace entender una pantalla.","Flutter-developer bij Nerdy Production op het snijvlak van ontwerp en tekst — onboarding en de microteksten die een scherm begrijpelijk maken.","\u002Fteam\u002Fmasha.webp",{"en":3772,"ru":3773,"es":3774,"nl":3775},"Flutter Developer","Flutter-разработчик","Desarrolladora Flutter","Flutter-developer","middle",[41,18],"masha",{"en":3780,"ru":3781,"es":3782,"nl":3783},"UI\u002FUX and copywriting for Flutter apps","UI\u002FUX и копирайтинг для Flutter-приложений","UI\u002FUX y redacción para apps Flutter","UI\u002FUX en teksten voor Flutter-apps","z3c64iWR39RIrn8wE0elSy00IfCITYgNkbgp_G_sslk",{"id":3786,"bio":3787,"expertise":3792,"extension":3704,"links":3803,"meta":3806,"metaDescription":3807,"name":3812,"ogDescription":3815,"photo":3820,"role":3821,"seniority":3826,"skills":3827,"slug":3828,"specialization":3829,"stem":3828,"__hash__":3834},"team_members\u002Fmaxim.yaml",{"en":3788,"ru":3789,"es":3790,"nl":3791},"Maxim is a polyglot engineer who moves comfortably across [Go](\u002Ftechnologies\u002Fgo), [Flutter](\u002Ftechnologies\u002Fflutter), [Python](\u002Ftechnologies\u002Fpython), and [TypeScript](\u002Ftechnologies\u002Ftypescript), which lets him own a feature from the backend to the screen.\n\nHe came up at Ozon, one of Russia's largest marketplaces, where scale makes reliability non-negotiable — and it shows in his work: testing is a first-class part of how he builds, not an afterthought bolted on at the end.\n","Максим — полиглот-инженер, свободно работающий с [Go](\u002Ftechnologies\u002Fgo), [Flutter](\u002Ftechnologies\u002Fflutter), [Python](\u002Ftechnologies\u002Fpython) и [TypeScript](\u002Ftechnologies\u002Ftypescript), что позволяет ему вести фичу от бэкенда до экрана.\n\nОн вырос в Ozon, одном из крупнейших маркетплейсов России, где масштаб делает надёжность обязательной, — и это видно в его работе: тестирование для него первоклассная часть разработки, а не то, что прикручивают в конце.\n","Maxim es un ingeniero políglota que se mueve con soltura entre [Go](\u002Ftechnologies\u002Fgo), [Flutter](\u002Ftechnologies\u002Fflutter), [Python](\u002Ftechnologies\u002Fpython) y [TypeScript](\u002Ftechnologies\u002Ftypescript), lo que le permite hacerse cargo de una funcionalidad desde el backend hasta la pantalla.\n\nSe formó en Ozon, uno de los mayores marketplaces de Rusia, donde la escala hace que la fiabilidad no sea negociable, y se le nota: el testing es para él una parte de primera clase de cómo construye, no un añadido al final.\n","Maxim is een polyglotte engineer die zich moeiteloos beweegt tussen [Go](\u002Ftechnologies\u002Fgo), [Flutter](\u002Ftechnologies\u002Fflutter), [Python](\u002Ftechnologies\u002Fpython) en [TypeScript](\u002Ftechnologies\u002Ftypescript), waardoor hij een functie van de backend tot het scherm kan dragen.\n\nHij is opgegroeid bij Ozon, een van de grootste marktplaatsen van Rusland, waar schaal betrouwbaarheid onderhandelbaar maakt noch toestaat — en dat zie je terug in zijn werk: testen is bij hem een volwaardig onderdeel van hoe hij bouwt, geen bijzaak die er aan het eind bij komt.\n",[3793,3798],{"en":3794,"ru":3795,"es":3796,"nl":3797},"Automated testing","Автоматизированное тестирование","Testing automatizado","Geautomatiseerd testen",{"en":3799,"ru":3800,"es":3801,"nl":3802},"High-load backend systems","Высоконагруженные бэкенд-системы","Sistemas backend de alta carga","Backendsystemen met hoge belasting",[3804],{"type":3707,"address":3805},"maxim@nerdy.pro",{},{"en":3808,"ru":3809,"es":3810,"nl":3811},"Senior engineer at Nerdy Production across Go, Flutter, Python and TypeScript — from backend to screen, with automated testing built in.","Старший инженер Nerdy Production: Go, Flutter, Python и TypeScript — от бэкенда до экрана, с автотестами как частью разработки.","Ingeniero senior en Nerdy Production con Go, Flutter, Python y TypeScript: del backend a la pantalla, con testing automatizado incorporado.","Senior engineer bij Nerdy Production met Go, Flutter, Python en TypeScript — van backend tot scherm, met geautomatiseerd testen ingebouwd.",{"en":3813,"ru":3814,"es":3813,"nl":3813},"Maxim","Максим",{"en":3816,"ru":3817,"es":3818,"nl":3819},"Senior engineer at Nerdy Production working from backend to screen — Go, Flutter, Python and TypeScript, with the tests written as he goes.","Старший инженер Nerdy Production, работающий от бэкенда до экрана: Go, Flutter, Python и TypeScript — и тесты, которые пишутся по ходу дела.","Ingeniero senior en Nerdy Production, del backend a la pantalla: Go, Flutter, Python y TypeScript, con los tests escritos sobre la marcha.","Senior engineer bij Nerdy Production, van backend tot scherm — Go, Flutter, Python en TypeScript, met de tests die hij onderweg schrijft.","\u002Fteam\u002Fmax.webp",{"en":3822,"ru":3823,"es":3824,"nl":3825},"Senior Software Engineer","Старший инженер-программист","Ingeniero de software senior","Senior software engineer","senior",[41,18,51,106,155,13,150],"maxim",{"en":3830,"ru":3831,"es":3832,"nl":3833},"Backend and Flutter engineering with a testing focus","Бэкенд и Flutter с фокусом на тестирование","Ingeniería backend y Flutter con foco en testing","Backend- en Flutter-engineering met focus op testen","a-8Kx1i-PQXIGU92_Rimqr3bPEur6ZwMfPKQWr2uk4I",{"id":3836,"bio":3837,"expertise":3842,"extension":3704,"links":3884,"meta":3896,"metaDescription":3897,"name":3902,"ogDescription":3905,"photo":3910,"role":3911,"seniority":3916,"skills":3917,"slug":170,"specialization":3918,"stem":170,"__hash__":3923},"team_members\u002Fnixan.yaml",{"en":3838,"ru":3839,"es":3840,"nl":3841},"Ilya founded Nerdy Production and leads its engineering. He has been building software since 2010 and shipping production Flutter since 2018.\n\nBefore that he was CTO of QIWI, one of Russia's largest payment platforms, where he ran roughly 12 engineering teams spanning web products down to card processing, :term{slug=\"pci-dss\"} scope, and contactless payments — including building contactless card payments on Android via :term[Host Card Emulation]{slug=\"host-card-emulation\"} over ISO\u002FIEC 14443, with EMV Contactless (Visa PayWave) on top.\n\nHe was also a principal developer at Yandex, where he worked on Yandex.Auto — taking native Android deep into the vehicle, with heavy CAN-bus integration through a custom CAN shield — and a principal at Evotor, whose point-of-sale devices run on a forked :term{slug=\"aosp\"}, giving him a low-level view of Android most app developers never touch.\n\nToday he leads delivery on the agency's flagship apps — from the chart-heavy fintech UI of [ExtraETF](\u002Fportfolio\u002Fextraetf) to the fully custom design system of [Arcana](\u002Fportfolio\u002Farcana). He writes most of the essays on this blog and maintains the agency's open-source work, including the [dxpdf](\u002Fopen-source\u002Fdxpdf) DOCX-to-PDF engine.\n\nHe works across [Flutter](\u002Ftechnologies\u002Fflutter), native iOS and Android, [Go](\u002Ftechnologies\u002Fgo), [Rust](\u002Ftechnologies\u002Frust), [TypeScript](\u002Ftechnologies\u002Ftypescript), [Kotlin](\u002Ftechnologies\u002Fkotlin), [Kubernetes](\u002Ftechnologies\u002Fkubernetes), and [Docker](\u002Ftechnologies\u002Fdocker), with a focus on app architecture, cross-platform delivery, and building teams that ship.\n","Илья основал Nerdy Production и руководит инженерной командой. Он занимается разработкой с 2010 года и выпускает продакшн-приложения на Flutter с 2018-го.\n\nДо этого он был CTO QIWI — одной из крупнейших платёжных платформ России, — где руководил примерно 12 инженерными командами: от веб-продуктов до карточного процессинга, зоны :term{slug=\"pci-dss\"} и бесконтактных платежей, включая бесконтактную оплату картой на Android через :term[Host Card Emulation]{slug=\"host-card-emulation\"} поверх ISO\u002FIEC 14443, с платёжным протоколом EMV Contactless (Visa PayWave).\n\nОн также был принципал-разработчиком в Яндексе, где работал над Яндекс.Авто, уводя нативный Android глубоко в автомобиль, с серьёзной интеграцией по шине CAN через собственный CAN-шилд, и принципалом в Эвоторе, чьи кассовые устройства работают на форке :term{slug=\"aosp\"}, что дало ему низкоуровневый взгляд на Android, недоступный большинству прикладных разработчиков.\n\nСейчас он ведёт поставку флагманских приложений агентства — от насыщенного графиками финтех-интерфейса [ExtraETF](\u002Fportfolio\u002Fextraetf) до полностью кастомной дизайн-системы [Arcana](\u002Fportfolio\u002Farcana). Он пишет большую часть материалов этого блога и поддерживает open-source агентства, включая движок [dxpdf](\u002Fopen-source\u002Fdxpdf) для конвертации DOCX в PDF.\n\nРаботает с [Flutter](\u002Ftechnologies\u002Fflutter), нативными iOS и Android, [Go](\u002Ftechnologies\u002Fgo), [Rust](\u002Ftechnologies\u002Frust), [TypeScript](\u002Ftechnologies\u002Ftypescript), [Kotlin](\u002Ftechnologies\u002Fkotlin), [Kubernetes](\u002Ftechnologies\u002Fkubernetes) и [Docker](\u002Ftechnologies\u002Fdocker); его фокус — архитектура приложений, кросс-платформенная поставка и построение команд, которые доводят продукт до релиза.\n","Ilya fundó Nerdy Production y dirige su ingeniería. Lleva construyendo software desde 2010 y entregando Flutter en producción desde 2018.\n\nAntes fue CTO de QIWI, una de las mayores plataformas de pago de Rusia, donde dirigió alrededor de 12 equipos de ingeniería que abarcaban desde productos web hasta procesamiento de tarjetas, alcance :term{slug=\"pci-dss\"} y pagos contactless, incluida la construcción de pagos contactless con tarjeta en Android mediante :term[Host Card Emulation]{slug=\"host-card-emulation\"} sobre ISO\u002FIEC 14443, con EMV Contactless (Visa PayWave) por encima.\n\nTambién fue principal developer en Yandex, donde trabajó en Yandex.Auto —llevando Android nativo hasta el interior del vehículo, con una integración intensiva por bus CAN a través de un CAN shield propio— y principal en Evotor, cuyos terminales de punto de venta funcionan sobre un fork de :term{slug=\"aosp\"}, lo que le dio una visión de bajo nivel de Android que la mayoría de desarrolladores de apps nunca llega a tocar.\n\nHoy lidera la entrega de las apps insignia de la agencia, desde la interfaz fintech cargada de gráficos de [ExtraETF](\u002Fportfolio\u002Fextraetf) hasta el sistema de diseño totalmente a medida de [Arcana](\u002Fportfolio\u002Farcana). Escribe la mayoría de los artículos de este blog y mantiene el trabajo de código abierto de la agencia, incluido el motor de conversión de DOCX a PDF [dxpdf](\u002Fopen-source\u002Fdxpdf).\n\nTrabaja con [Flutter](\u002Ftechnologies\u002Fflutter), iOS y Android nativos, [Go](\u002Ftechnologies\u002Fgo), [Rust](\u002Ftechnologies\u002Frust), [TypeScript](\u002Ftechnologies\u002Ftypescript), [Kotlin](\u002Ftechnologies\u002Fkotlin), [Kubernetes](\u002Ftechnologies\u002Fkubernetes) y [Docker](\u002Ftechnologies\u002Fdocker), con foco en arquitectura de aplicaciones, entrega multiplataforma y la construcción de equipos que entregan.\n","Ilya richtte Nerdy Production op en leidt de engineering. Hij bouwt software sinds 2010 en levert sinds 2018 Flutter in productie.\n\nDaarvoor was hij CTO van QIWI, een van de grootste betaalplatforms van Rusland, waar hij zo'n 12 engineeringteams aanstuurde die reikten van webproducten tot kaartverwerking, :term{slug=\"pci-dss\"}-scope en contactloos betalen — waaronder het bouwen van contactloze kaartbetalingen op Android via :term[Host Card Emulation]{slug=\"host-card-emulation\"} over ISO\u002FIEC 14443, met EMV Contactless (Visa PayWave) daarbovenop.\n\nHij was ook principal developer bij Yandex, waar hij aan Yandex.Auto werkte — native Android diep de auto in brengen, met zware integratie over de CAN-bus via een eigen CAN-shield — en principal bij Evotor, waarvan de kassa-apparaten op een fork van :term{slug=\"aosp\"} draaien, wat hem een blik op Android op laag niveau gaf die de meeste app-ontwikkelaars nooit krijgen.\n\nVandaag leidt hij de oplevering van de vlaggenschipapps van het bureau — van de grafiekzware fintech-UI van [ExtraETF](\u002Fportfolio\u002Fextraetf) tot het volledig eigen designsysteem van [Arcana](\u002Fportfolio\u002Farcana). Hij schrijft de meeste artikelen op deze blog en onderhoudt het opensourcewerk van het bureau, waaronder de DOCX-naar-PDF-motor [dxpdf](\u002Fopen-source\u002Fdxpdf).\n\nHij werkt met [Flutter](\u002Ftechnologies\u002Fflutter), native iOS en Android, [Go](\u002Ftechnologies\u002Fgo), [Rust](\u002Ftechnologies\u002Frust), [TypeScript](\u002Ftechnologies\u002Ftypescript), [Kotlin](\u002Ftechnologies\u002Fkotlin), [Kubernetes](\u002Ftechnologies\u002Fkubernetes) en [Docker](\u002Ftechnologies\u002Fdocker), met de nadruk op applicatiearchitectuur, cross-platform oplevering en het bouwen van teams die opleveren.\n",[3843,3848,3853,3858,3863,3868,3873,3878,3880,3882],{"en":3844,"ru":3845,"es":3846,"nl":3847},"iOS development","Разработка под iOS","Desarrollo iOS","iOS-ontwikkeling",{"en":3849,"ru":3850,"es":3851,"nl":3852},"Software architecture","Архитектура ПО","Arquitectura de software","Softwarearchitectuur",{"en":3854,"ru":3855,"es":3856,"nl":3857},"Engineering team leadership","Руководство инженерными командами","Liderazgo de equipos de ingeniería","Leidinggeven aan engineeringteams",{"en":3859,"ru":3860,"es":3861,"nl":3862},"Payment systems","Платёжные системы","Sistemas de pago","Betaalsystemen",{"en":3864,"ru":3865,"es":3866,"nl":3867},"Card processing","Карточный процессинг","Procesamiento de tarjetas","Kaartverwerking",{"en":3869,"ru":3870,"es":3871,"nl":3872},"PCI-DSS compliance","Соответствие PCI-DSS","Cumplimiento de PCI-DSS","Naleving van PCI-DSS",{"en":3874,"ru":3875,"es":3876,"nl":3877},"NFC and contactless payments","NFC и бесконтактные платежи","NFC y pagos contactless","NFC en contactloos betalen",{"en":3879,"ru":3879,"es":3879,"nl":3879},"Host Card Emulation",{"en":3881,"ru":3881,"es":3881,"nl":3881},"EMV Contactless",{"en":3883,"ru":3883,"es":3883,"nl":3883},"AOSP",[3885,3888,3891,3894],{"type":3886,"url":3887},"github","https:\u002F\u002Fgithub.com\u002Fthenixan",{"type":3889,"url":3890},"linkedin","https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fthenixan\u002F",{"type":3892,"url":3893},"telegram","https:\u002F\u002Ft.me\u002Fthenixan",{"type":3707,"address":3895},"nixan@nerdy.pro",{},{"en":3898,"ru":3899,"es":3900,"nl":3901},"Founder and lead developer at Nerdy Production, former CTO of QIWI. Flutter architecture, payments infrastructure, and engineering teams that ship.","Основатель и ведущий разработчик Nerdy Production, экс-CTO QIWI. Архитектура на Flutter, платёжная инфраструктура и команды, которые доводят до релиза.","Fundador y lead developer en Nerdy Production, ex-CTO de QIWI. Arquitectura Flutter, infraestructura de pagos y equipos de ingeniería que entregan.","Oprichter en lead developer bij Nerdy Production, oud-CTO van QIWI. Flutter-architectuur, betaalinfrastructuur en teams die opleveren.",{"en":3903,"ru":3904,"es":3903,"nl":3903},"Ilya Nixan","Илья Никсан",{"en":3906,"ru":3907,"es":3908,"nl":3909},"Founder and lead developer at Nerdy Production, former CTO of QIWI — Flutter architecture, payments infrastructure, and teams that ship.","Основатель и ведущий разработчик Nerdy Production, экс-CTO QIWI: архитектура на Flutter, платёжная инфраструктура и команды, которые доводят до релиза.","Fundador y lead developer en Nerdy Production, ex-CTO de QIWI: arquitectura Flutter, infraestructura de pagos y equipos que entregan.","Oprichter en lead developer bij Nerdy Production, oud-CTO van QIWI — Flutter-architectuur, betaalinfrastructuur en teams die opleveren.","\u002Fteam\u002Fnixan.webp",{"en":3912,"ru":3913,"es":3914,"nl":3915},"Founder & Lead Developer","Основатель и ведущий разработчик","Fundador y lead developer","Oprichter & lead developer","founder_lead",[41,18,71,141,76,146,51,126,121,155,160,90,13,150,136,7,29,80],{"en":3919,"ru":3920,"es":3921,"nl":3922},"Flutter architecture and leading delivery teams","Архитектура на Flutter и руководство командами поставки","Arquitectura Flutter y liderazgo de equipos de entrega","Flutter-architectuur en het leiden van opleverteams","BmeQZ7JyhRFIx8527WO2t1-WklP8AAvevAxiN9U4TEs",{"id":3925,"bio":3926,"expertise":3931,"extension":3704,"links":3943,"meta":3948,"metaDescription":3949,"name":3954,"ogDescription":3957,"photo":3962,"role":3963,"seniority":3826,"skills":3968,"slug":3969,"specialization":3970,"stem":3969,"__hash__":3975},"team_members\u002Froma.yaml",{"en":3927,"ru":3928,"es":3929,"nl":3930},"Roman works across native iOS and Android and [Flutter](\u002Ftechnologies\u002Fflutter), and pairs that mobile depth with [Python](\u002Ftechnologies\u002Fpython) and applied AI.\n\nMuch of his background is in ERP and CRM development — complex, data-heavy business systems where getting the domain model right matters more than the UI — which gives him a pragmatic eye for how an app fits the process behind it.\n","Роман работает с нативными iOS и Android и с [Flutter](\u002Ftechnologies\u002Fflutter), дополняя мобильную экспертизу [Python](\u002Ftechnologies\u002Fpython) и прикладным AI.\n\nЗначительная часть его опыта — разработка ERP и CRM, сложных систем с большим объёмом данных, где правильная доменная модель важнее интерфейса, — что даёт ему прагматичный взгляд на то, как приложение встраивается в процесс за ним.\n","Roman trabaja con iOS y Android nativos y con [Flutter](\u002Ftechnologies\u002Fflutter), y combina esa profundidad móvil con [Python](\u002Ftechnologies\u002Fpython) e IA aplicada.\n\nBuena parte de su trayectoria está en el desarrollo de ERP y CRM —sistemas de negocio complejos y con mucho dato, donde acertar con el modelo de dominio importa más que la interfaz—, lo que le da una mirada pragmática sobre cómo encaja una app en el proceso que hay detrás.\n","Roman werkt met native iOS en Android en met [Flutter](\u002Ftechnologies\u002Fflutter), en combineert die mobiele diepgang met [Python](\u002Ftechnologies\u002Fpython) en toegepaste AI.\n\nEen groot deel van zijn achtergrond ligt in ERP- en CRM-ontwikkeling — complexe, datazware bedrijfssystemen waarin het domeinmodel goed krijgen zwaarder weegt dan de interface — wat hem een pragmatische blik geeft op hoe een app in het proces erachter past.\n",[3932,3933,3938],{"en":3844,"ru":3845,"es":3846,"nl":3847},{"en":3934,"ru":3935,"es":3936,"nl":3937},"Applied AI","Прикладной AI","IA aplicada","Toegepaste AI",{"en":3939,"ru":3940,"es":3941,"nl":3942},"ERP and CRM systems","ERP- и CRM-системы","Sistemas ERP y CRM","ERP- en CRM-systemen",[3944,3946],{"type":3889,"url":3945},"https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fromanbatler\u002F",{"type":3707,"address":3947},"roma@nerdy.pro",{},{"en":3950,"ru":3951,"es":3952,"nl":3953},"Senior mobile developer at Nerdy Production — native iOS and Android, Flutter, applied AI, and a background in data-heavy ERP and CRM systems.","Старший мобильный разработчик Nerdy Production: нативные iOS и Android, Flutter, прикладной AI и опыт в ERP- и CRM-системах.","Desarrollador móvil senior en Nerdy Production: iOS y Android nativos, Flutter, IA aplicada y experiencia en sistemas ERP y CRM con gran volumen de datos.","Senior mobiele developer bij Nerdy Production — native iOS en Android, Flutter, toegepaste AI, en een achtergrond in datazware ERP- en CRM-systemen.",{"en":3955,"ru":3956,"es":3955,"nl":3955},"Roman","Рома",{"en":3958,"ru":3959,"es":3960,"nl":3961},"Senior mobile developer at Nerdy Production — native iOS and Android, Flutter, applied AI, and years of data-heavy ERP and CRM behind him.","Старший мобильный разработчик Nerdy Production: нативные iOS и Android, Flutter, прикладной AI и годы работы с нагруженными ERP и CRM.","Desarrollador móvil senior en Nerdy Production: iOS y Android nativos, Flutter, IA aplicada y años de ERP y CRM con mucha carga de datos.","Senior mobiele developer bij Nerdy Production — native iOS en Android, Flutter, toegepaste AI, en jaren datazware ERP en CRM achter zich.","\u002Fteam\u002Froma.webp",{"en":3964,"ru":3965,"es":3966,"nl":3967},"Senior Mobile Developer","Старший мобильный разработчик","Desarrollador móvil senior","Senior mobiele developer",[7,41,18,141,146,106],"roma",{"en":3971,"ru":3972,"es":3973,"nl":3974},"Mobile, AI, and ERP\u002FCRM systems","Мобильная разработка, AI и системы ERP\u002FCRM","Móvil, IA y sistemas ERP\u002FCRM","Mobiel, AI en ERP\u002FCRM-systemen","caPSPO2BX35G9lEjNb6p3U4uF_V7yh8cexCe6l5hm8Y",{"id":3977,"bio":3978,"expertise":3983,"extension":3704,"links":3678,"meta":3994,"metaDescription":3995,"name":4000,"ogDescription":4003,"photo":4008,"role":4009,"seniority":3826,"skills":4014,"slug":4015,"specialization":4016,"stem":4015,"__hash__":4021},"team_members\u002Fxsox.yaml",{"en":3979,"ru":3980,"es":3981,"nl":3982},"Eugene builds the systems that apps depend on. His background is in backend engineering — designing and running the APIs, data models, and services behind a product, primarily in [Python](\u002Ftechnologies\u002Fpython) and [Django](\u002Ftechnologies\u002Fdjango).\n\nHe also works in [Flutter](\u002Ftechnologies\u002Fflutter), so he can reason about a feature from the database to the device and build the backend so the app that consumes it stays simple.\n","Женя строит системы, на которые опираются приложения. Его основной опыт — бэкенд-инженерия: проектирование и эксплуатация API, моделей данных и сервисов за продуктом, в первую очередь на [Python](\u002Ftechnologies\u002Fpython) и [Django](\u002Ftechnologies\u002Fdjango).\n\nОн также работает с [Flutter](\u002Ftechnologies\u002Fflutter), поэтому может рассуждать о фиче от базы данных до устройства и строить бэкенд так, чтобы потребляющее его приложение оставалось простым.\n","Eugene construye los sistemas de los que dependen las apps. Su trayectoria está en la ingeniería backend: diseñar y operar las APIs, los modelos de datos y los servicios que hay detrás de un producto, principalmente en [Python](\u002Ftechnologies\u002Fpython) y [Django](\u002Ftechnologies\u002Fdjango).\n\nTambién trabaja con [Flutter](\u002Ftechnologies\u002Fflutter), así que puede razonar sobre una funcionalidad desde la base de datos hasta el dispositivo y construir el backend de forma que la app que lo consume siga siendo simple.\n","Eugene bouwt de systemen waar apps op leunen. Zijn achtergrond ligt in backend-engineering: de API's, datamodellen en services achter een product ontwerpen en draaien, vooral in [Python](\u002Ftechnologies\u002Fpython) en [Django](\u002Ftechnologies\u002Fdjango).\n\nHij werkt daarnaast met [Flutter](\u002Ftechnologies\u002Fflutter), zodat hij over een functie kan nadenken van de database tot het toestel en de backend zo kan bouwen dat de app die hem verbruikt eenvoudig blijft.\n",[3984,3989],{"en":3985,"ru":3986,"es":3987,"nl":3988},"API design","Проектирование API","Diseño de APIs","API-ontwerp",{"en":3990,"ru":3991,"es":3992,"nl":3993},"Data modelling","Моделирование данных","Modelado de datos","Datamodellering",{},{"en":3996,"ru":3997,"es":3998,"nl":3999},"Senior backend developer at Nerdy Production building the APIs, data models and services behind our apps, primarily in Python and Django.","Старший бэкенд-разработчик Nerdy Production: API, модели данных и сервисы за нашими приложениями, в первую очередь на Python и Django.","Desarrollador backend senior en Nerdy Production: las APIs, modelos de datos y servicios detrás de nuestras apps, sobre todo en Python y Django.","Senior backenddeveloper bij Nerdy Production die de API's, datamodellen en services achter onze apps bouwt, vooral in Python en Django.",{"en":4001,"ru":4002,"es":4001,"nl":4001},"Eugene Xsox","Женя Xsox",{"en":4004,"ru":4005,"es":4006,"nl":4007},"Senior backend developer at Nerdy Production — the APIs, data models and services our apps run on, mostly in Python and Django.","Старший бэкенд-разработчик Nerdy Production: API, модели данных и сервисы, на которых работают наши приложения, в основном на Python и Django.","Desarrollador backend senior en Nerdy Production: las APIs, modelos de datos y servicios sobre los que corren nuestras apps, en Python y Django.","Senior backenddeveloper bij Nerdy Production — de API's, datamodellen en services waarop onze apps draaien, vooral in Python en Django.","\u002Fteam\u002Fxsox.webp",{"en":4010,"ru":4011,"es":4012,"nl":4013},"Senior Backend Developer","Старший бэкенд-разработчик","Desarrollador backend senior","Senior backenddeveloper",[106,24,41,18,155,160,90,150],"xsox",{"en":4017,"ru":4018,"es":4019,"nl":4020},"Python\u002FDjango backends, plus Flutter","Бэкенды на Python\u002FDjango и Flutter","Backends en Python\u002FDjango, además de Flutter","Backends in Python\u002FDjango, plus Flutter","w_QAKKq_Mh_dtMvAxFpAJBC10PW1I7zgVOxELwRHwO8",[4023,4032,4039,4044,4054],{"id":4024,"extension":3704,"meta":4025,"name":4026,"slug":4030,"stem":4030,"__hash__":4031},"blog_topics\u002Fcybersecurity.yaml",{},{"en":4027,"ru":4028,"es":4029,"nl":4027},"Cybersecurity","Кибербезопасность","Ciberseguridad","cybersecurity","kt1rdsMy5W4b9MvGHm6mZVCr0inHidEuqaosPYmmk4E",{"id":4033,"extension":3704,"meta":4034,"name":4035,"slug":4037,"stem":4037,"__hash__":4038},"blog_topics\u002Fdevops.yaml",{},{"en":4036,"ru":4036,"es":4036,"nl":4036},"DevOps","devops","D3mxlZuFHKCGeszKZuWCiQmHmP5EiCvzg1qEAIQhnyU",{"id":4040,"extension":3704,"meta":4041,"name":4042,"slug":41,"stem":41,"__hash__":4043},"blog_topics\u002Fflutter.yaml",{},{"en":39,"ru":39,"es":39,"nl":39},"OOeALAmwFuEqByuJvTGk4g15FIHENPfYMdpfZh_HTAE",{"id":4045,"extension":3704,"meta":4046,"name":4047,"slug":4052,"stem":4052,"__hash__":4053},"blog_topics\u002Ffor-founders.yaml",{},{"en":4048,"ru":4049,"es":4050,"nl":4051},"For Founders","Для основателей","Para fundadores","Voor oprichters","for-founders","lp2u6Ol2qF4DGxpB83HxbbGzWioT-e40mJhD4lr28-U",{"id":4055,"extension":3704,"meta":4056,"name":4057,"slug":3677,"stem":3677,"__hash__":4062},"blog_topics\u002Fsoftware-engineering.yaml",{},{"en":4058,"ru":4059,"es":4060,"nl":4061},"Software Engineering","Разработка ПО","Ingeniería de software","Software-engineering","aZMb3aUDptoFQk_cEE6l5H4wrYcgZuBYUubmKqOAaaU",{"data":4064,"body":4065},{},{"type":4066,"children":4067},"root",[4068,4075,4093,4104,4131],{"type":4069,"tag":175,"props":4070,"children":4071},"element",{},[4072],{"type":4073,"value":4074},"text","Илья основал Nerdy Production и руководит инженерной командой. Он занимается разработкой с 2010 года и выпускает продакшн-приложения на Flutter с 2018-го.",{"type":4069,"tag":175,"props":4076,"children":4077},{},[4078,4080,4084,4086,4091],{"type":4073,"value":4079},"До этого он был CTO QIWI — одной из крупнейших платёжных платформ России, — где руководил примерно 12 инженерными командами: от веб-продуктов до карточного процессинга, зоны ",{"type":4069,"tag":183,"props":4081,"children":4083},{"slug":4082},"pci-dss",[],{"type":4073,"value":4085}," и бесконтактных платежей, включая бесконтактную оплату картой на Android через ",{"type":4069,"tag":183,"props":4087,"children":4089},{"slug":4088},"host-card-emulation",[4090],{"type":4073,"value":3879},{"type":4073,"value":4092}," поверх ISO\u002FIEC 14443, с платёжным протоколом EMV Contactless (Visa PayWave).",{"type":4069,"tag":175,"props":4094,"children":4095},{},[4096,4098,4102],{"type":4073,"value":4097},"Он также был принципал-разработчиком в Яндексе, где работал над Яндекс.Авто, уводя нативный Android глубоко в автомобиль, с серьёзной интеграцией по шине CAN через собственный CAN-шилд, и принципалом в Эвоторе, чьи кассовые устройства работают на форке ",{"type":4069,"tag":183,"props":4099,"children":4101},{"slug":4100},"aosp",[],{"type":4073,"value":4103},", что дало ему низкоуровневый взгляд на Android, недоступный большинству прикладных разработчиков.",{"type":4069,"tag":175,"props":4105,"children":4106},{},[4107,4109,4115,4117,4123,4125,4129],{"type":4073,"value":4108},"Сейчас он ведёт поставку флагманских приложений агентства — от насыщенного графиками финтех-интерфейса ",{"type":4069,"tag":211,"props":4110,"children":4112},{"href":4111},"\u002Fportfolio\u002Fextraetf",[4113],{"type":4073,"value":4114},"ExtraETF",{"type":4073,"value":4116}," до полностью кастомной дизайн-системы ",{"type":4069,"tag":211,"props":4118,"children":4120},{"href":4119},"\u002Fportfolio\u002Farcana",[4121],{"type":4073,"value":4122},"Arcana",{"type":4073,"value":4124},". Он пишет большую часть материалов этого блога и поддерживает open-source агентства, включая движок ",{"type":4069,"tag":211,"props":4126,"children":4127},{"href":3553},[4128],{"type":4073,"value":3554},{"type":4073,"value":4130}," для конвертации DOCX в PDF.",{"type":4069,"tag":175,"props":4132,"children":4133},{},[4134,4136,4141,4143,4148,4149,4154,4155,4160,4161,4166,4167,4172,4173,4178],{"type":4073,"value":4135},"Работает с ",{"type":4069,"tag":211,"props":4137,"children":4139},{"href":4138},"\u002Ftechnologies\u002Fflutter",[4140],{"type":4073,"value":39},{"type":4073,"value":4142},", нативными iOS и Android, ",{"type":4069,"tag":211,"props":4144,"children":4146},{"href":4145},"\u002Ftechnologies\u002Fgo",[4147],{"type":4073,"value":49},{"type":4073,"value":236},{"type":4069,"tag":211,"props":4150,"children":4152},{"href":4151},"\u002Ftechnologies\u002Frust",[4153],{"type":4073,"value":124},{"type":4073,"value":236},{"type":4069,"tag":211,"props":4156,"children":4158},{"href":4157},"\u002Ftechnologies\u002Ftypescript",[4159],{"type":4073,"value":153},{"type":4073,"value":236},{"type":4069,"tag":211,"props":4162,"children":4164},{"href":4163},"\u002Ftechnologies\u002Fkotlin",[4165],{"type":4073,"value":69},{"type":4073,"value":236},{"type":4069,"tag":211,"props":4168,"children":4170},{"href":4169},"\u002Ftechnologies\u002Fkubernetes",[4171],{"type":4073,"value":78},{"type":4073,"value":3062},{"type":4069,"tag":211,"props":4174,"children":4176},{"href":4175},"\u002Ftechnologies\u002Fdocker",[4177],{"type":4073,"value":27},{"type":4073,"value":4179},"; его фокус — архитектура приложений, кросс-платформенная поставка и построение команд, которые доводят продукт до релиза.",[4181,4193,4206,4221,4235,4250,4266,4279,4292,4308,4321,4332,4342,4355,4365,4375,4384,4398,4411,4418,4432,4447,4460,4473,4486,4497,4508,4519,4532,4542,4553,4565,4579,4590,4603,4612,4623,4636,4649,4662,4675,4687,4699,4711,4723,4735,4746,4760,4771,4783,4794,4807,4819],{"slug":4100,"term":3883,"definition":4182,"category":4183,"aliases":4184,"links":4186,"related":4190,"readMore":-1,"target":4191,"hasArticle":4192},"Android Open Source Project — Android без надстройки Google, который может форкнуть кто угодно. На его форках работают кассовые терминалы, киоски и автомобильные системы, а работа на этом уровне открывает части ОС, которых прикладной разработчик не видит.","platform",[4185],"Android Open Source Project",[4187],{"kind":4188,"url":4189},"website","https:\u002F\u002Fsource.android.com\u002F",[4088],"\u002Fglossary#aosp",false,{"slug":4194,"term":4195,"definition":4196,"category":4183,"aliases":4197,"links":4198,"related":4202,"readMore":-1,"target":4205,"hasArticle":4192},"app-clips","App Clips","Функция Apple, позволяющая запустить небольшую часть iOS-приложения — до 15 МБ — не устанавливая его целиком. Вызывается по QR-коду, NFC-метке или ссылке: для случаев, когда первое действие пользователя не должно требовать похода в App Store.",[],[4199],{"kind":4200,"url":4201},"documentation","https:\u002F\u002Fdeveloper.apple.com\u002Fapp-clips\u002F",[4203,4204],"deep-linking","install-referrer","\u002Fglossary#app-clips",{"slug":4207,"term":4208,"definition":4209,"category":4183,"aliases":4210,"links":4211,"related":4217,"readMore":-1,"target":4220,"hasArticle":4192},"bigquery","BigQuery","Аналитическое хранилище Google Cloud. Вы запускаете SQL по миллиардам строк, и они сканируются за секунды, а данные при этом лежат отдельно от машин, выполняющих запросы. Именно это снимает отчётность и исследование данных с базы, обслуживающей живой трафик.",[],[4212,4214],{"kind":4188,"url":4213},"https:\u002F\u002Fcloud.google.com\u002Fbigquery",{"kind":4215,"url":4216},"wikipedia","https:\u002F\u002Fen.wikipedia.org\u002Fwiki\u002FBigQuery",[4218,4219],"object-storage","elasticsearch","\u002Fglossary#bigquery",{"slug":4222,"term":4223,"definition":4224,"category":4225,"aliases":4226,"links":4230,"related":4233,"readMore":-1,"target":4234,"hasArticle":4192},"ci-cd","CI\u002FCD","Автоматизация, которая собирает, тестирует и выкладывает каждое изменение без ручного запуска команд. В мобильной разработке это то, что превращает релиз в нажатие кнопки вместо полудня ожидания, пока освободится нужный человек.","practice",[4227,4228,4229],"CI","непрерывная интеграция","непрерывная поставка",[4231],{"kind":4215,"url":4232},"https:\u002F\u002Fen.wikipedia.org\u002Fwiki\u002FCI\u002FCD",[],"\u002Fglossary#ci-cd",{"slug":4236,"term":4237,"definition":4238,"category":4239,"aliases":4240,"links":4243,"related":4246,"readMore":-1,"target":4249,"hasArticle":4192},"crud","CRUD","Create, read, update, delete — четыре операции, стоящие почти за каждой формой и админкой. Собирательное название для рутинной половины приложения, которая управляет данными, в отличие от частей с реальной предметной логикой.","architecture",[4241,4242],"Create, Read, Update, Delete","создание, чтение, обновление, удаление",[4244],{"kind":4215,"url":4245},"https:\u002F\u002Fen.wikipedia.org\u002Fwiki\u002FCreate,_read,_update_and_delete",[4247,4248],"rest","graphql","\u002Fglossary#crud",{"slug":4251,"term":4252,"definition":4253,"category":4225,"aliases":4254,"links":4258,"related":4263,"readMore":-1,"target":4265,"hasArticle":4192},"dev-container","Dev Container","Среда разработки, описанная один раз в devcontainer.json — ОС, инструменты и версии рантаймов, которые нужны проекту, — и открываемая одинаково внутри контейнера в редакторе любого участника, вместо инструкции по установке, которую каждый понимает по-своему.",[4255,4256,4257],"devcontainer.json","Dev Containers","VS Code Dev Containers",[4259,4261],{"kind":4188,"url":4260},"https:\u002F\u002Fcontainers.dev\u002F",{"kind":4200,"url":4262},"https:\u002F\u002Fcode.visualstudio.com\u002Fdocs\u002Fdevcontainers\u002Fcontainers",[4264],"container-registry","\u002Fglossary#dev-container",{"slug":4219,"term":4267,"definition":4268,"category":4183,"aliases":4269,"links":4272,"related":4277,"readMore":-1,"target":4278,"hasArticle":4192},"Elasticsearch","Поисковый и аналитический движок, который индексирует записи, чтобы их можно было интерактивно фильтровать и искать, а не сканировать. К нему обращаются, когда вопрос звучит как «покажи вот эти сессии с шестью фильтрами», а не «просуммируй колонку».",[4270,4271],"Elastic","ELK",[4273,4275],{"kind":4188,"url":4274},"https:\u002F\u002Fwww.elastic.co\u002Felasticsearch",{"kind":4215,"url":4276},"https:\u002F\u002Fen.wikipedia.org\u002Fwiki\u002FElasticsearch",[4207],"\u002Fglossary#elasticsearch",{"slug":4280,"term":4281,"definition":4282,"category":4239,"aliases":4283,"links":4286,"related":4287,"readMore":-1,"target":4291,"hasArticle":4192},"fan-out","Fan-out","Чтение источника один раз и доставка каждого обновления всем подписанным на него клиентам. Наивная версия пишет подписчикам в цикле и встаёт, как только один из сокетов начинает тормозить; рабочая — держит отдельный буфер на каждого клиента и отключает тех, кто не успевает.",[4284,4285],"веерная рассылка","фан-аут",[],[4288,4289,4290],"websocket","pub-sub","server-sent-events","\u002Fglossary#fan-out",{"slug":4293,"term":4294,"definition":4295,"category":4225,"aliases":4296,"links":4299,"related":4304,"readMore":-1,"target":4307,"hasArticle":4192},"gdpr","GDPR","Регламент ЕС о персональных данных людей в ЕС: законное основание для сбора, настоящее согласие на трекинг, право увидеть и удалить свои данные. Он следует за вашими пользователями, а не за вашими серверами: где зарегистрирована компания, значения не имеет.",[4297,4298],"Общий регламент по защите данных","General Data Protection Regulation",[4300,4302],{"kind":4188,"url":4301},"https:\u002F\u002Fgdpr.eu\u002F",{"kind":4215,"url":4303},"https:\u002F\u002Fen.wikipedia.org\u002Fwiki\u002FGeneral_Data_Protection_Regulation",[4082,4305,4306],"soc-2","hipaa","\u002Fglossary#gdpr",{"slug":4309,"term":4310,"definition":4311,"category":4225,"aliases":4312,"links":4316,"related":4319,"readMore":-1,"target":4320,"hasArticle":4192},"golden-test","Golden-тест","Тест, который отрисовывает виджет и сравнивает результат попиксельно с эталонным изображением. Во Flutter это самый дешёвый способ узнать, не сломал ли редизайн пустое состояние на 320pt, в тёмной теме, при 200% масштабе текста.",[4313,4314,4315],"golden test","золотой тест","скриншотный тест",[4317],{"kind":4200,"url":4318},"https:\u002F\u002Fapi.flutter.dev\u002Fflutter\u002Fflutter_test\u002FmatchesGoldenFile.html",[4222],"\u002Fglossary#golden-test",{"slug":4248,"term":4322,"definition":4323,"category":4239,"aliases":4324,"links":4325,"related":4330,"readMore":-1,"target":4331,"hasArticle":4192},"GraphQL","Язык запросов к API, где клиент сам перечисляет нужные поля и получает один ответ ровно такой формы. Он снимает избыточную выдачу, к которой дрейфуют REST-эндпоинты, и добавляет собственный режим отказа: неограниченный запрос, обходящий всю модель данных.",[],[4326,4328],{"kind":4188,"url":4327},"https:\u002F\u002Fgraphql.org\u002F",{"kind":4215,"url":4329},"https:\u002F\u002Fen.wikipedia.org\u002Fwiki\u002FGraphQL",[4247],"\u002Fglossary#graphql",{"slug":4306,"term":4333,"definition":4334,"category":4225,"aliases":4335,"links":4337,"related":4340,"readMore":-1,"target":4341,"hasArticle":4192},"HIPAA","Закон США о защищённой медицинской информации: как её можно хранить, передавать, логировать и раскрывать. Как и PCI-DSS, это архитектурное ограничение, которое выбирают в начале, а не документ, который дописывают перед запуском.",[4336],"Health Insurance Portability and Accountability Act",[4338],{"kind":4215,"url":4339},"https:\u002F\u002Fen.wikipedia.org\u002Fwiki\u002FHealth_Insurance_Portability_and_Accountability_Act",[4082,4293,4305],"\u002Fglossary#hipaa",{"slug":4343,"term":4344,"definition":4345,"category":4239,"aliases":4346,"links":4349,"related":4352,"readMore":-1,"target":4354,"hasArticle":4192},"headless-cms","Headless CMS","Система управления контентом с админкой и API, но без собственного фронтенда. Редакторы публикуют в одном месте, а сайт или приложение отрисовывает контент само — слой представления принадлежит вам, а не вендору CMS.",[4347,4348],"безголовая CMS","контентное API",[4350],{"kind":4215,"url":4351},"https:\u002F\u002Fen.wikipedia.org\u002Fwiki\u002FHeadless_content_management_system",[4353,4247],"server-side-rendering","\u002Fglossary#headless-cms",{"slug":4088,"term":3879,"definition":4356,"category":4183,"aliases":4357,"links":4360,"related":4363,"readMore":-1,"target":4364,"hasArticle":4192},"Режим, в котором Android-телефон программно эмулирует бесконтактную карту по NFC, без аппаратного защищённого элемента. Так платит приложение-кошелёк на терминале: телефон говорит с ним по тому же протоколу EMV Contactless, что и пластиковая карта.",[4358,3881,4359],"HCE","бесконтактные платежи",[4361],{"kind":4200,"url":4362},"https:\u002F\u002Fdeveloper.android.com\u002Fdevelop\u002Fconnectivity\u002Fnfc\u002Fhce",[4082,4100],"\u002Fglossary#host-card-emulation",{"slug":4366,"term":4367,"definition":4368,"category":4183,"aliases":4369,"links":4370,"related":4373,"readMore":-1,"target":4374,"hasArticle":4192},"impeller","Impeller","Движок рендеринга, на котором Flutter работает сегодня: по умолчанию на iOS с 2023 года и на Android с 2024-го. Он компилирует шейдеры заранее, а не во время первой анимации, что убрало главную видимую проблему Flutter в проде — джанк на компиляции шейдеров.",[],[4371],{"kind":4200,"url":4372},"https:\u002F\u002Fdocs.flutter.dev\u002Fperf\u002Fimpeller",[185],"\u002Fglossary#impeller",{"slug":4204,"term":4376,"definition":4377,"category":4183,"aliases":4378,"links":4379,"related":4382,"readMore":-1,"target":4383,"hasArticle":4192},"Install Referrer","API Google Play, который передаёт только что установленному Android-приложению параметры кампании из ссылки, приведшей к установке. Android-половина отложенного диплинкинга и надёжный способ понять, откуда пришёл пользователь.",[],[4380],{"kind":4200,"url":4381},"https:\u002F\u002Fdeveloper.android.com\u002Fgoogle\u002Fplay\u002Finstallreferrer",[4203,4194],"\u002Fglossary#install-referrer",{"slug":4385,"term":4386,"definition":4387,"category":4388,"aliases":4389,"links":4391,"related":4396,"readMore":-1,"target":4397,"hasArticle":4192},"jwt","JWT","Подписанный токен, несущий свои данные внутри: сервер понимает, кому принадлежит запрос, не обращаясь к хранилищу сессий. Стандарт мобильной аутентификации. Подпись доказывает, что токен не меняли, но не скрывает его содержимое.","protocol",[4390],"JSON Web Token",[4392,4394],{"kind":4188,"url":4393},"https:\u002F\u002Fjwt.io\u002F",{"kind":4215,"url":4395},"https:\u002F\u002Fen.wikipedia.org\u002Fwiki\u002FJSON_Web_Token",[4082],"\u002Fglossary#jwt",{"slug":4399,"term":4400,"definition":4401,"category":4225,"aliases":4402,"links":4406,"related":4409,"readMore":-1,"target":4410,"hasArticle":4192},"kyc","KYC","Know Your Customer — проверки личности, которые регулируемый финансовый продукт проводит, прежде чем разрешить переводить деньги: съёмка документов, liveness, санкционный и антиотмывочный скрининг. На онбординг это влияет сильнее любого дизайн-решения.",[4403,4404,4405],"Know Your Customer","AML","KYC\u002FAML",[4407],{"kind":4215,"url":4408},"https:\u002F\u002Fen.wikipedia.org\u002Fwiki\u002FKnow_your_customer",[4082,4293],"\u002Fglossary#kyc",{"slug":76,"term":74,"definition":4412,"category":4183,"aliases":4413,"links":4415,"related":4416,"readMore":4417,"target":4417,"hasArticle":4192},"Переиспользование бизнес-логики на Kotlin между Android, iOS и сервером, при этом интерфейс на каждой платформе остаётся нативным. Альтернатива Flutter, когда UI обязан быть нативным, а правила за ним — нет.",[4414],"KMP",[],[],"\u002Ftechnologies\u002Fkmp",{"slug":4419,"term":4420,"definition":4421,"category":4422,"aliases":4423,"links":4426,"related":4429,"readMore":-1,"target":4431,"hasArticle":4192},"mvp","MVP","Минимальная версия продукта, которую можно показать реальным пользователям и которая при этом отвечает на вопрос, ради которого его делали. Решение об объёме, а не о качестве: MVP всё равно обязан работать.","business",[4424,4425],"минимально жизнеспособный продукт","minimum viable product",[4427],{"kind":4215,"url":4428},"https:\u002F\u002Fen.wikipedia.org\u002Fwiki\u002FMinimum_viable_product",[4430],"white-label","\u002Fglossary#mvp",{"slug":4433,"term":4434,"definition":4435,"category":4388,"aliases":4436,"links":4440,"related":4445,"readMore":-1,"target":4446,"hasArticle":4192},"oauth","OAuth","Стандарт, на котором держатся «Вход через Apple», Google и остальные: пользователь авторизует ваше приложение у провайдера, которому уже доверяет, а приложение получает токен вместо пароля. Никто не заводит новый пароль, и вы его не храните.",[4437,4438,4439],"OAuth 2.0","вход через соцсети","Sign in with Apple",[4441,4443],{"kind":4188,"url":4442},"https:\u002F\u002Foauth.net\u002F2\u002F",{"kind":4215,"url":4444},"https:\u002F\u002Fen.wikipedia.org\u002Fwiki\u002FOAuth",[4385],"\u002Fglossary#oauth",{"slug":4082,"term":4448,"definition":4449,"category":4225,"aliases":4450,"links":4453,"related":4458,"readMore":-1,"target":4459,"hasArticle":4192},"PCI-DSS","Стандарт безопасности карточной индустрии, обязательный для всех, кто хранит, обрабатывает или передаёт данные карт. Большинство приложений намеренно остаются вне зоны его действия, отдавая ввод карты сертифицированному платёжному провайдеру.",[4451,4452],"PCI DSS","Payment Card Industry Data Security Standard",[4454,4456],{"kind":4188,"url":4455},"https:\u002F\u002Fwww.pcisecuritystandards.org\u002F",{"kind":4215,"url":4457},"https:\u002F\u002Fen.wikipedia.org\u002Fwiki\u002FPayment_Card_Industry_Data_Security_Standard",[4385],"\u002Fglossary#pci-dss",{"slug":4461,"term":4462,"definition":4463,"category":4422,"aliases":4464,"links":4467,"related":4470,"readMore":-1,"target":4472,"hasArticle":4192},"product-market-fit","Product-market fit","Момент, когда продукт доказуемо нашёл своих людей: они им пользуются, возвращаются и платят. До него разработка отвечает на вопрос, после — на спрос.",[4465,4466],"PMF","соответствие продукта рынку",[4468],{"kind":4215,"url":4469},"https:\u002F\u002Fen.wikipedia.org\u002Fwiki\u002FProduct-market_fit",[4419,4471],"time-to-market","\u002Fglossary#product-market-fit",{"slug":4474,"term":4475,"definition":4476,"category":4225,"aliases":4477,"links":4480,"related":4483,"readMore":-1,"target":4485,"hasArticle":4192},"prompt-injection","Prompt injection","Атака, при которой текст от пользователя языковая модель читает как инструкции, а не как данные, и уходит в обход собственных правил. Родственник SQL-инъекции в эпоху LLM: возникает везде, где пользовательский ввод склеивается в промпт.",[4478,4479],"инъекция промпта","инъекция в промпт",[4481],{"kind":4215,"url":4482},"https:\u002F\u002Fen.wikipedia.org\u002Fwiki\u002FPrompt_injection",[4484],"rate-limiting","\u002Fglossary#prompt-injection",{"slug":4289,"term":4487,"definition":4488,"category":4239,"aliases":4489,"links":4492,"related":4495,"readMore":-1,"target":4496,"hasArticle":4192},"Pub\u002FSub","Шаблон обмена сообщениями: производитель публикует событие, а любое число потребителей независимо читает его через стоящий между ними брокер. Производитель никогда их не ждёт — так путь запроса остаётся быстрым, а более медленная работа выполняется в фоне.",[4490,4491],"публикация\u002Fподписка","Google Cloud Pub\u002FSub",[4493],{"kind":4215,"url":4494},"https:\u002F\u002Fen.wikipedia.org\u002Fwiki\u002FPublish%E2%80%93subscribe_pattern",[],"\u002Fglossary#pub-sub",{"slug":4247,"term":4498,"definition":4499,"category":4239,"aliases":4500,"links":4503,"related":4506,"readMore":-1,"target":4507,"hasArticle":4192},"REST","Общепринятый стиль HTTP-API: URL называет ресурс, а HTTP-метод говорит, что с ним сделать. Способ по умолчанию, которым приложение общается с бэкендом, и то, что ожидает найти большинство сторонних интеграций.",[4501,4502],"REST API","Representational State Transfer",[4504],{"kind":4215,"url":4505},"https:\u002F\u002Fen.wikipedia.org\u002Fwiki\u002FREST",[4288,4290],"\u002Fglossary#rest",{"slug":4305,"term":4509,"definition":4510,"category":4225,"aliases":4511,"links":4514,"related":4517,"readMore":-1,"target":4518,"hasArticle":4192},"SOC 2","Отчёт внешнего аудитора о том, как компания обращается с данными клиентов — безопасность, доступность, конфиденциальность, — а не сертификат, который можно купить. Корпоративные заказчики его требуют, а архитектуру он ограничивает задолго до самого аудита.",[4512,4513],"SOC2","System and Organization Controls",[4515],{"kind":4215,"url":4516},"https:\u002F\u002Fen.wikipedia.org\u002Fwiki\u002FSystem_and_Organization_Controls",[4082,4293,4306],"\u002Fglossary#soc-2",{"slug":4520,"term":4521,"definition":4522,"category":4422,"aliases":4523,"links":4526,"related":4529,"readMore":-1,"target":4531,"hasArticle":4192},"saas","SaaS","Софт, который продают как постоянную подписку на размещённый у поставщика продукт, а не как разовую лицензию для установки у себя. Поставщик держит серверы, непрерывно выкатывает обновления и берёт плату за пользователя или за объём.",[4524,4525],"Software as a Service","ПО как услуга",[4527],{"kind":4215,"url":4528},"https:\u002F\u002Fen.wikipedia.org\u002Fwiki\u002FSoftware_as_a_service",[4530,4430],"multi-tenancy","\u002Fglossary#saas",{"slug":4290,"term":4533,"definition":4534,"category":4388,"aliases":4535,"links":4537,"related":4540,"readMore":-1,"target":4541,"hasArticle":4192},"Server-Sent Events","Односторонний поток от сервера к клиенту поверх обычного HTTP-соединения. Проще, чем WebSocket, и достаточен там, где только серверу есть что сказать: прогресс операции, ответ AI, приходящий токен за токеном.",[4536],"SSE",[4538],{"kind":4215,"url":4539},"https:\u002F\u002Fen.wikipedia.org\u002Fwiki\u002FServer-sent_events",[4288,4247],"\u002Fglossary#server-sent-events",{"slug":185,"term":186,"definition":4543,"category":4183,"aliases":4544,"links":4546,"related":4551,"readMore":-1,"target":4552,"hasArticle":4192},"Открытая 2D-графическая библиотека Google, которая рисует Chrome, Android и — до появления Impeller — каждый кадр Flutter. Она умеет выводить не только на экран, но и в PDF: именно это делает «печать в PDF» в Chrome.",[4545,194],"Skia Graphics Engine",[4547,4549],{"kind":4188,"url":4548},"https:\u002F\u002Fskia.org\u002F",{"kind":4215,"url":4550},"https:\u002F\u002Fen.wikipedia.org\u002Fwiki\u002FSkia_Graphics_Engine",[4366],"\u002Fglossary#skia",{"slug":4554,"term":4555,"definition":4556,"category":4422,"aliases":4557,"links":4561,"related":4562,"readMore":-1,"target":4564,"hasArticle":4192},"staff-augmentation","Staff augmentation","Модель найма, при которой инженеры внешнего партнёра входят в вашу команду и работают под вашим управлением — в вашем репозитории, ваших спринтах, вашем процессе, — а не сдают отдельный проект. Вы покупаете ресурс; код и контекст остаются у вас.",[4558,4559,4560],"аутстаффинг","расширение команды","выделенные разработчики",[],[4563,4471],"total-cost-of-ownership","\u002Fglossary#staff-augmentation",{"slug":4566,"term":4567,"definition":4568,"category":4388,"aliases":4569,"links":4573,"related":4576,"readMore":-1,"target":4578,"hasArticle":4192},"tls","TLS","Слой шифрования под HTTPS. Он подтверждает, что сервер именно тот, о ком говорит его сертификат, согласует свежий ключ на сессию и шифрует всё дальнейшее — сеть между вами видит шифртекст и не может незаметно его изменить.",[4570,4571,4572],"SSL","HTTPS","Transport Layer Security",[4574],{"kind":4215,"url":4575},"https:\u002F\u002Fen.wikipedia.org\u002Fwiki\u002FTransport_Layer_Security",[4577,4385],"end-to-end-encryption","\u002Fglossary#tls",{"slug":4471,"term":4580,"definition":4581,"category":4422,"aliases":4582,"links":4585,"related":4588,"readMore":-1,"target":4589,"hasArticle":4192},"Time to market","Время от решения строить продукт до момента, когда им пользуются настоящие люди. Большинство споров о стеке и объёме работ — на самом деле споры об этом числе: каждая сэкономленная неделя — это неделя выручки, обратной связи и позиции на рынке.",[4583,4584],"TTM","время выхода на рынок",[4586],{"kind":4215,"url":4587},"https:\u002F\u002Fen.wikipedia.org\u002Fwiki\u002FTime_to_market",[4419,4563],"\u002Fglossary#time-to-market",{"slug":4591,"term":3698,"definition":4592,"category":4388,"aliases":4593,"links":4595,"related":4600,"readMore":-1,"target":4602,"hasArticle":4192},"webrtc","Браузерный и мобильный стандарт для передачи аудио, видео и данных напрямую между двумя устройствами; серверы участвуют только в том, чтобы их познакомить. На нём строят видеозвонок внутри приложения, когда не берут готовый SDK.",[4594],"Web Real-Time Communication",[4596,4598],{"kind":4188,"url":4597},"https:\u002F\u002Fwebrtc.org\u002F",{"kind":4215,"url":4599},"https:\u002F\u002Fen.wikipedia.org\u002Fwiki\u002FWebRTC",[4601,4288],"xmpp","\u002Fglossary#webrtc",{"slug":4288,"term":4604,"definition":4605,"category":4388,"aliases":4606,"links":4607,"related":4610,"readMore":-1,"target":4611,"hasArticle":4192},"WebSocket","Протокол, который держит одно соединение между клиентом и сервером открытым, чтобы любая сторона могла отправить данные в любой момент, а не клиент спрашивал снова и снова. На нём работают живые котировки, чаты и статусы присутствия.",[],[4608],{"kind":4215,"url":4609},"https:\u002F\u002Fen.wikipedia.org\u002Fwiki\u002FWebSocket",[4290,4247],"\u002Fglossary#websocket",{"slug":4430,"term":4613,"definition":4614,"category":4422,"aliases":4615,"links":4618,"related":4621,"readMore":4622,"target":4622,"hasArticle":399},"White-label","Один продукт, выпускаемый под многими брендами. White-label платформа собирает каждому клиенту готовое к публикации приложение со своим названием, оформлением и контентом из общей кодовой базы, а не форкает проект под заказчика.",[4616,4617],"белый лейбл","мультитенантное приложение",[4619],{"kind":4215,"url":4620},"https:\u002F\u002Fen.wikipedia.org\u002Fwiki\u002FWhite-label_product",[4419],"\u002Fglossary\u002Fwhite-label",{"slug":4601,"term":4624,"definition":4625,"category":4388,"aliases":4626,"links":4629,"related":4634,"readMore":-1,"target":4635,"hasArticle":4192},"XMPP","Открытый федеративный протокол обмена сообщениями и давняя альтернатива тому, чтобы писать чат-бэкенд самому или арендовать чужой. Он покрывает статусы присутствия, индикаторы набора и передачу файлов, а зрелые клиентские библиотеки есть под любую платформу.",[4627,4628],"Jabber","Extensible Messaging and Presence Protocol",[4630,4632],{"kind":4188,"url":4631},"https:\u002F\u002Fxmpp.org\u002F",{"kind":4215,"url":4633},"https:\u002F\u002Fen.wikipedia.org\u002Fwiki\u002FXMPP",[4591,4288],"\u002Fglossary#xmpp",{"slug":4637,"term":4638,"definition":4639,"category":4422,"aliases":4640,"links":4644,"related":4647,"readMore":-1,"target":4648,"hasArticle":4192},"in-app-purchase","Встроенные покупки","Продажа цифровых товаров и подписок через биллинг Apple и Google, который оба стора требуют для цифрового контента и с которого берут комиссию. Сложность никогда не в самой покупке, а в её восстановлении на новом устройстве и в честном учёте прав доступа.",[4641,4642,4643],"in-app purchase","IAP","внутренние покупки",[4645],{"kind":4200,"url":4646},"https:\u002F\u002Fdeveloper.apple.com\u002Fin-app-purchase\u002F",[4461],"\u002Fglossary#in-app-purchase",{"slug":4203,"term":4650,"definition":4651,"category":4183,"aliases":4652,"links":4657,"related":4660,"readMore":4661,"target":4661,"hasArticle":399},"Диплинкинг","Ссылка, которая открывает конкретный экран внутри установленного приложения, а не его главную или веб-страницу. Отложенный диплинкинг переживает установку: нажатие, прошедшее через магазин приложений, всё равно приводит на нужный экран.",[4653,4654,4655,4656],"deep linking","отложенный диплинкинг","Universal Links","Android App Links",[4658],{"kind":4215,"url":4659},"https:\u002F\u002Fen.wikipedia.org\u002Fwiki\u002FMobile_deep_linking",[4194,4204],"\u002Fglossary\u002Fdeep-linking",{"slug":4663,"term":4664,"definition":4665,"category":4239,"aliases":4666,"links":4670,"related":4673,"readMore":-1,"target":4674,"hasArticle":4192},"multi-arch-image","Мультиархитектурный образ","Один тег образа, который разрешается в разные бинарники для каждой архитектуры процессора — обычно это пара linux\u002Famd64 и linux\u002Farm64, — так что один и тот же docker pull работает без изменений и на серверах Intel\u002FAMD, и на ноутбуках с Apple Silicon.",[4667,4668,4669],"мультиарх-сборка","multi-arch build","linux\u002Famd64 + linux\u002Farm64",[4671],{"kind":4200,"url":4672},"https:\u002F\u002Fdocs.docker.com\u002Fbuild\u002Fbuilding\u002Fmulti-platform\u002F",[4264],"\u002Fglossary#multi-arch-image",{"slug":4530,"term":4676,"definition":4677,"category":4239,"aliases":4678,"links":4681,"related":4684,"readMore":-1,"target":4686,"hasArticle":4192},"Мультитенантность","Один развёрнутый бэкенд обслуживает много клиентов, и каждый видит только свои данные, конфигурацию и включённые функции, потому что контекст тенанта определяется для каждого запроса. Именно это делает линейку брендированных приложений одним продуктом, а не набором форков.",[4530,4679,4680],"мультиарендность","тенант",[4682],{"kind":4215,"url":4683},"https:\u002F\u002Fen.wikipedia.org\u002Fwiki\u002FMultitenancy",[4430,4685],"feature-flags","\u002Fglossary#multi-tenancy",{"slug":4218,"term":4688,"definition":4689,"category":4239,"aliases":4690,"links":4694,"related":4697,"readMore":-1,"target":4698,"hasArticle":4192},"Объектное хранилище","Хранилище, которое держит файл целиком под ключом, а не в дереве файловой системы, — Amazon S3 и множество сервисов, совместимых с его API. Дешёвое, практически безграничное, привычное место для сырых событий, бэкапов и медиа: пишется один раз, читается редко, хранится вечно.",[4691,4692,4693],"S3","S3-совместимое хранилище","блоб-хранилище",[4695],{"kind":4215,"url":4696},"https:\u002F\u002Fen.wikipedia.org\u002Fwiki\u002FObject_storage",[4207],"\u002Fglossary#object-storage",{"slug":4484,"term":4700,"definition":4701,"category":4225,"aliases":4702,"links":4706,"related":4709,"readMore":-1,"target":4710,"hasArticle":4192},"Ограничение частоты запросов","Предел на число запросов от одного клиента за заданный интервал. Именно он не даёт одному увлечённому пользователю, скраперу или боту потратить месячный бюджет платного API за вечер, и стоять он должен на вашей стороне интеграции.",[4703,4704,4705],"rate limiting","рейт-лимит","троттлинг",[4707],{"kind":4215,"url":4708},"https:\u002F\u002Fen.wikipedia.org\u002Fwiki\u002FRate_limiting",[4474],"\u002Fglossary#rate-limiting",{"slug":4712,"term":4713,"definition":4714,"category":4183,"aliases":4715,"links":4718,"related":4721,"readMore":-1,"target":4722,"hasArticle":4192},"platform-channels","Платформенные каналы","Мост, через который Flutter-приложение вызывает нативный код iOS и Android — Keychain и Keystore, биометрию, платёжные шторки, любой SDK без Dart-пакета. Работа рутинная, но настоящая, и первое место, где споткнётся инженер, не выходивший за пределы Dart.",[4716,4717],"platform channels","method channel",[4719],{"kind":4200,"url":4720},"https:\u002F\u002Fdocs.flutter.dev\u002Fplatform-integration\u002Fplatform-channels",[76],"\u002Fglossary#platform-channels",{"slug":4724,"term":4725,"definition":4726,"category":4388,"aliases":4727,"links":4730,"related":4733,"readMore":-1,"target":4734,"hasArticle":4192},"post-quantum-cryptography","Постквантовая криптография","Алгоритмы шифрования, рассчитанные на устойчивость к будущему квантовому компьютеру; стандарты уже финализированы NIST. Мигрировать нужно до появления железа: трафик, записанный сегодня, расшифруют, когда такая машина появится.",[4728,4729],"PQC","постквантовое шифрование",[4731],{"kind":4215,"url":4732},"https:\u002F\u002Fen.wikipedia.org\u002Fwiki\u002FPost-quantum_cryptography",[4566,4577],"\u002Fglossary#post-quantum-cryptography",{"slug":4736,"term":4737,"definition":4738,"category":4225,"aliases":4739,"links":4743,"related":4744,"readMore":-1,"target":4745,"hasArticle":4192},"staged-rollout","Поэтапная раскатка","Релиз сначала на небольшой процент пользователей и расширение только тогда, когда доля сессий без падений держится. Плохая сборка, пойманная на десяти процентах, — испорченный вечер; она же на ста процентах — испорченная неделя.",[4740,4741,4742],"staged rollout","phased release","канареечный релиз",[],[4685,4222],"\u002Fglossary#staged-rollout",{"slug":4264,"term":4747,"definition":4748,"category":4239,"aliases":4749,"links":4755,"related":4758,"readMore":-1,"target":4759,"hasArticle":4192},"Реестр контейнеров","Хранилище образов контейнеров, адресуемых по имени и тегу, — Docker Hub, GitHub Container Registry или собственный реестр облачного провайдера. Публикация сборки в реестр превращает «у меня работает» в образ, который любой может скачать и запустить без изменений.",[4750,4751,4752,4753,4754],"реестр образов","Docker registry","GHCR","ghcr.io","container registry",[4756],{"kind":4200,"url":4757},"https:\u002F\u002Fdocs.github.com\u002Fen\u002Fpackages\u002Fworking-with-a-github-packages-registry\u002Fworking-with-the-container-registry",[4251,4663],"\u002Fglossary#container-registry",{"slug":4353,"term":4761,"definition":4762,"category":4239,"aliases":4763,"links":4766,"related":4769,"readMore":-1,"target":4770,"hasArticle":4192},"Серверный рендеринг","Сборка страницы в готовый HTML на сервере, чтобы первый же ответ содержал контент, заголовки, метатеги и структурированные данные. Краулеры, превью ссылок и медленные устройства читают это, не выполняя JavaScript.",[4764,4765],"SSR","server-side rendering",[4767],{"kind":4215,"url":4768},"https:\u002F\u002Fen.wikipedia.org\u002Fwiki\u002FServer-side_scripting",[4343],"\u002Fglossary#server-side-rendering",{"slug":4577,"term":4772,"definition":4773,"category":4239,"aliases":4774,"links":4778,"related":4781,"readMore":-1,"target":4782,"hasArticle":4192},"Сквозное шифрование","Шифрование, которое накладывается на устройстве отправителя и снимается только на устройстве получателя, так что сервис, передающий сообщение, не может его прочитать — ни по решению суда, ни после взлома. Оно защищает содержимое, но никогда — метаданные.",[4775,4776,4777],"E2EE","end-to-end encryption","оконечное шифрование",[4779],{"kind":4215,"url":4780},"https:\u002F\u002Fen.wikipedia.org\u002Fwiki\u002FEnd-to-end_encryption",[4566,4385],"\u002Fglossary#end-to-end-encryption",{"slug":4563,"term":4784,"definition":4785,"category":4422,"aliases":4786,"links":4789,"related":4792,"readMore":-1,"target":4793,"hasArticle":4192},"Совокупная стоимость владения","Во что продукт обходится за всю жизнь, а не только на этапе разработки: поддержка, обновления, ежегодные миграции на новые ОС и правила сторов, вторая команда, чтобы две кодовые базы не расходились. Обычно больше сметы на разработку и почти никогда в неё не входит.",[4787,4788],"TCO","total cost of ownership",[4790],{"kind":4215,"url":4791},"https:\u002F\u002Fen.wikipedia.org\u002Fwiki\u002FTotal_cost_of_ownership",[4471],"\u002Fglossary#total-cost-of-ownership",{"slug":4795,"term":4796,"definition":4797,"category":4239,"aliases":4798,"links":4802,"related":4805,"readMore":-1,"target":4806,"hasArticle":4192},"state-management","Управление состоянием","То, как приложение решает, где живёт значение, кому позволено его менять и какие части экрана перерисовываются, когда оно меняется. Во Flutter выбор между Riverpod, BLoC и Provider — одно из первых архитектурных решений и одно из самых трудных для пересмотра.",[4799,4800,4801],"state management","BLoC","Riverpod",[4803],{"kind":4200,"url":4804},"https:\u002F\u002Fdocs.flutter.dev\u002Fdata-and-backend\u002Fstate-mgmt\u002Foptions",[4309],"\u002Fglossary#state-management",{"slug":4685,"term":4808,"definition":4809,"category":4225,"aliases":4810,"links":4814,"related":4817,"readMore":-1,"target":4818,"hasArticle":4192},"Фиче-флаги","Переключатели, которые включают и выключают функциональность из конфигурации, а не из релиза. Один бинарник ведёт себя по-разному для разных брендов, рынков и пользователей, а рискованную фичу можно погасить, не отправляя новую сборку на ревью.",[4811,4812,4813],"feature flags","фиче-флаги","feature toggle",[4815],{"kind":4215,"url":4816},"https:\u002F\u002Fen.wikipedia.org\u002Fwiki\u002FFeature_toggle",[4736,4430,4530],"\u002Fglossary#feature-flags",{"slug":4820,"term":4821,"definition":4822,"category":4239,"aliases":4823,"links":4827,"related":4830,"readMore":-1,"target":4831,"hasArticle":4192},"floating-point","Числа с плавающей точкой","Двоичный формат IEEE 754, стоящий за double и float. Он не хранит 0.1 точно, поэтому 0.1 + 0.2 даёт 0.30000000000000004 — незаметно в графике и фатально в деньгах, которым место в целых минорных единицах или в decimal-типе.",[4824,4825,4826],"IEEE 754","double","плавающая запятая",[4828],{"kind":4215,"url":4829},"https:\u002F\u002Fen.wikipedia.org\u002Fwiki\u002FIEEE_754",[],"\u002Fglossary#floating-point"]