diff --git a/project.json b/project.json index 272c470..5b98594 100644 --- a/project.json +++ b/project.json @@ -1,8 +1,8 @@ { "$schema": "https://schemas.cute.engineering/stable/cutekit.manifest.project.v1", - "id": "odoo/o-html2pdf", + "id": "odoo/paper-muncher", "type": "project", - "description": "The vaev document generator", + "description": "An experimental document-generation tool", "extern": { "cute-engineering/ce-heap": { "git": "https://github.com/cute-engineering/ce-heap.git", diff --git a/readme.md b/readme.md index 1cace4a..395bf1d 100644 --- a/readme.md +++ b/readme.md @@ -32,7 +32,7 @@ The reign of wkhtmltopdf is over. The Paper Muncher has come. Let its name be et ```sh # on Linux or MacOS -./ck builder run o-devtools +./ck builder run paper-muncher inspector # windows support comming soon'ish ``` diff --git a/src/web/vaev-devtools/app.cpp b/src/paper-muncher/inspector.cpp similarity index 97% rename from src/web/vaev-devtools/app.cpp rename to src/paper-muncher/inspector.cpp index 02ac014..4a429c8 100644 --- a/src/web/vaev-devtools/app.cpp +++ b/src/paper-muncher/inspector.cpp @@ -14,7 +14,9 @@ #include #include -namespace Hideo::Browser { +#include "inspector.h" + +namespace PaperMuncher::Inspector { enum struct SidePanel { CLOSE, @@ -182,7 +184,7 @@ Ui::Child app(Mime::Url url, Res> dom) { Hideo::toolbar( Ui::button( [&](Ui::Node &n) { - Ui::showDialog(n, Kr::alert("Vaev"s, "Copyright © 2024, Odoo S.A."s)); + Ui::showDialog(n, Kr::alert("Paper-Muncher"s, "Copyright © 2024, Odoo S.A."s)); }, Ui::ButtonStyle::subtle(), Mdi::SURFING @@ -222,4 +224,4 @@ Ui::Child app(Mime::Url url, Res> dom) { ); } -} // namespace Hideo::Browser +} // namespace PaperMuncher::Inspector diff --git a/src/web/vaev-devtools/app.h b/src/paper-muncher/inspector.h similarity index 65% rename from src/web/vaev-devtools/app.h rename to src/paper-muncher/inspector.h index 9f284e6..3d96170 100644 --- a/src/web/vaev-devtools/app.h +++ b/src/paper-muncher/inspector.h @@ -3,8 +3,8 @@ #include #include -namespace Hideo::Browser { +namespace PaperMuncher::Inspector { Ui::Child app(Mime::Url url, Res> dom); -} // namespace Hideo::Browser +} // namespace PaperMuncher::Inspector diff --git a/src/paper-muncher/main.cpp b/src/paper-muncher/main.cpp new file mode 100644 index 0000000..44cb633 --- /dev/null +++ b/src/paper-muncher/main.cpp @@ -0,0 +1,259 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "inspector.h" + +namespace PaperMuncher { + +namespace Css { + +Res<> dumpStylesheet(Mime::Url const &url) { + auto start = Sys::now(); + auto stylesheet = try$(Vaev::Driver::fetchStylesheet(url)); + auto elapsed = Sys::now() - start; + logInfo("fetched in {}ms", elapsed.toUSecs() / 1000.0); + Sys::println("{#}", stylesheet); + return Ok(); +} + +Res<> dumpSst(Mime::Url const &url) { + auto file = try$(Sys::File::open(url)); + auto buf = try$(Io::readAllUtf8(file)); + + auto start = Sys::now(); + + Io::SScan s{buf}; + Vaev::Css::Lexer lex{s}; + Vaev::Css::Sst sst = Vaev::Css::consumeRuleList(lex, true); + + auto elapsed = Sys::now() - start; + logInfo("parsed in {}ms", elapsed.toUSecs() / 1000.0); + + Sys::println("{}", sst); + + return Ok(); +} + +Res<> dumpTokens(Mime::Url const &url) { + auto file = try$(Sys::File::open(url)); + auto buf = try$(Io::readAllUtf8(file)); + Io::SScan s{buf}; + Vaev::Css::Lexer lex{s}; + while (not lex.ended()) + Sys::println("{}", lex.next()); + return Ok(); +} + +} // namespace Css + +namespace Style { + +Res<> listProps() { + Vaev::Style::StyleProp::any([](Meta::Type) { + Sys::println("{}", T::name()); + return false; + }); + return Ok(); +} + +} // namespace Style + +namespace Markup { + +Res<> dumpDom(Mime::Url const &url) { + auto dom = try$(Vaev::Driver::fetchDocument(url)); + Sys::println("{}", dom); + return Ok(); +} + +Res<> dumpTokens(Mime::Url const &url) { + auto file = try$(Sys::File::open(url)); + auto buf = try$(Io::readAllUtf8(file)); + + Vec tokens; + + struct VecSink : public Vaev::Markup::HtmlSink { + Vec &tokens; + + VecSink(Vec &tokens) + : tokens(tokens) { + } + + void accept(Vaev::Markup::HtmlToken const &token) override { + tokens.pushBack(token); + } + }; + + VecSink sink{tokens}; + Vaev::Markup::HtmlLexer lexer{}; + lexer.bind(sink); + + for (auto r : iterRunes(buf)) + lexer.consume(r); + + for (auto &t : tokens) + Sys::println("{}", t); + + return Ok(); +} + +} // namespace Markup + +namespace Html2pdf { + +Vaev::Style::Media constructMedia(Print::PaperStock paper) { + return { + .type = Vaev::MediaType::SCREEN, + .width = Vaev::Px{paper.width}, + .height = Vaev::Px{paper.height}, + .aspectRatio = paper.width / paper.height, + .orientation = Vaev::Orientation::LANDSCAPE, + + .resolution = Vaev::Resolution::fromDpi(96), + .scan = Vaev::Scan::PROGRESSIVE, + .grid = false, + .update = Vaev::Update::NONE, + + .overflowBlock = Vaev::OverflowBlock::PAGED, + .overflowInline = Vaev::OverflowInline::NONE, + + .color = 8, + .colorIndex = 0, + .monochrome = 0, + .colorGamut = Vaev::ColorGamut::SRGB, + .pointer = Vaev::Pointer::NONE, + .hover = Vaev::Hover::NONE, + .anyPointer = Vaev::Pointer::NONE, + .anyHover = Vaev::Hover::NONE, + + .prefersReducedMotion = Vaev::ReducedMotion::REDUCE, + .prefersReducedTransparency = Vaev::ReducedTransparency::REDUCE, + .prefersContrast = Vaev::Contrast::MORE, + .forcedColors = Vaev::Colors::NONE, + .prefersColorScheme = Vaev::ColorScheme::LIGHT, + .prefersReducedData = Vaev::ReducedData::NO_PREFERENCE, + }; +} + +Res<> html2pdf(Mime::Url const &input, Mime::Url const &output) { + auto dom = try$(Vaev::Driver::fetchDocument(input)); + + auto paper = Print::A4; + auto media = constructMedia(paper); + + auto start = Sys::now(); + auto [layout, paint] = Vaev::Driver::render(*dom, media, paper); + auto elapsed = Sys::now() - start; + logInfo("render time: {}", elapsed); + + logDebug("layout tree: {}", layout); + logDebug("paint tree: {}", paint); + + Print::PdfPrinter printer{Print::A4, Print::Density::DEFAULT}; + paint->print(printer); + + auto file = try$(Sys::File::create(output)); + Io::TextEncoder<> encoder{file}; + Io::Emit e{encoder}; + printer.write(e); + try$(e.flush()); + + return Ok(); +} + +} // namespace Html2pdf + +} // namespace PaperMuncher + +Async::Task<> entryPointAsync(Sys::Context &ctx) { + auto args = Sys::useArgs(ctx); + + if (args.len() == 0) { + Sys::errln("usage: paper-muncher [OPTIONS...]\n"); + co_return Error::invalidInput(); + } + + auto verb = args[0]; + + if (verb == "css-dump-stylesheet") { + if (args.len() != 2) { + Sys::errln("usage: paper-muncher css-dump-stylesheet "); + co_return Error::invalidInput(); + } + + auto input = co_try$(Mime::parseUrlOrPath(args[1])); + co_return PaperMuncher::Css::dumpStylesheet(input); + + } else if (verb == "css-dump-sst") { + if (args.len() != 2) { + Sys::errln("usage: paper-muncher css-dump-sst "); + co_return Error::invalidInput(); + } + + auto input = co_try$(Mime::parseUrlOrPath(args[1])); + co_return PaperMuncher::Css::dumpSst(input); + } else if (verb == "css-dump-tokens") { + if (args.len() != 2) { + Sys::errln("usage: paper-muncher css-dump-tokens "); + co_return Error::invalidInput(); + } + + auto input = co_try$(Mime::parseUrlOrPath(args[1])); + co_return PaperMuncher::Css::dumpTokens(input); + } else if (verb == "style-list-props") { + co_return PaperMuncher::Style::listProps(); + } else if (verb == "markup-dump-dom") { + if (args.len() != 2) { + Sys::errln("usage: paper-muncher markup-dump-dom "); + co_return Error::invalidInput(); + } + + auto input = co_try$(Mime::parseUrlOrPath(args[1])); + co_return PaperMuncher::Markup::dumpDom(input); + } else if (verb == "markup-dump-tokens") { + if (args.len() != 2) { + Sys::errln("usage: paper-muncher markup-dump-token "); + co_return Error::invalidInput(); + } + + auto input = co_try$(Mime::parseUrlOrPath(args[1])); + co_return PaperMuncher::Markup::dumpTokens(input); + } else if (verb == "html2pdf") { + if (args.len() != 3) { + Sys::errln("usage: paper-muncher html2pdf \n"); + co_return Error::invalidInput(); + } + + auto input = co_try$(Mime::parseUrlOrPath(args[1])); + auto output = co_try$(Mime::parseUrlOrPath(args[2])); + co_return PaperMuncher::Html2pdf::html2pdf(input, output); + } else if (verb == "inspector") { + if (args.len() != 2) { + Sys::errln("usage: paper-muncher inspectpr "); + co_return Error::invalidInput(); + } + + auto input = args.len() + ? co_try$(Mime::parseUrlOrPath(args[1])) + : "about:start"_url; + + auto dom = Vaev::Driver::fetchDocument(input); + + co_return Ui::runApp( + ctx, + PaperMuncher::Inspector::app(input, dom) + ); + } else { + Sys::errln("unknown verb: {} (expected: css-dump-stylesheet, css-dump-sst, css-dump-tokens, style-list-props, markup-dump-dom, markup-dump-tokens, inspector)\n"); + co_return Error::invalidInput(); + } +} diff --git a/src/web/vaev-markup/cli/manifest.json b/src/paper-muncher/manifest.json similarity index 62% rename from src/web/vaev-markup/cli/manifest.json rename to src/paper-muncher/manifest.json index 6b14b5a..2987d4f 100644 --- a/src/web/vaev-markup/cli/manifest.json +++ b/src/paper-muncher/manifest.json @@ -1,10 +1,11 @@ { "$schema": "https://schemas.cute.engineering/stable/cutekit.manifest.component.v1", - "id": "vaev-markup.cli", + "id": "paper-muncher", "type": "exe", - "description": "Markup CLI", "requires": [ "karm-sys", - "vaev-markup" + "vaev-driver", + "vaev-view", + "hideo-base" ] } diff --git a/src/web/vaev-css/cli/main.cpp b/src/web/vaev-css/cli/main.cpp deleted file mode 100644 index d916a3f..0000000 --- a/src/web/vaev-css/cli/main.cpp +++ /dev/null @@ -1,54 +0,0 @@ -#include -#include -#include -#include -#include -#include - -Async::Task<> entryPointAsync(Sys::Context &ctx) { - auto args = Sys::useArgs(ctx); - - if (args.len() != 2) { - Sys::errln("usage: vaev-css.cli \n"); - co_return Error::invalidInput(); - } - - auto verb = args[0]; - auto url = co_try$(Mime::parseUrlOrPath(args[1])); - - if (verb == "dump-stylesheet") { - auto start = Sys::now(); - auto stylesheet = co_try$(Vaev::Driver::fetchStylesheet(url)); - auto elapsed = Sys::now() - start; - logInfo("fetched in {}ms", elapsed.toUSecs() / 1000.0); - - Sys::println("{#}", stylesheet); - co_return Ok(); - } else if (verb == "dump-sst") { - auto file = co_try$(Sys::File::open(url)); - auto buf = co_try$(Io::readAllUtf8(file)); - - auto start = Sys::now(); - - Io::SScan s{buf}; - Vaev::Css::Lexer lex{s}; - Vaev::Css::Sst sst = Vaev::Css::consumeRuleList(lex, true); - - auto elapsed = Sys::now() - start; - logInfo("parsed in {}ms", elapsed.toUSecs() / 1000.0); - - Sys::println("{}", sst); - co_return Ok(); - } else if (verb == "dump-tokens") { - auto file = co_try$(Sys::File::open(url)); - auto buf = co_try$(Io::readAllUtf8(file)); - Io::SScan s{buf}; - Vaev::Css::Lexer lex{s}; - while (not lex.ended()) - Sys::println("{}", lex.next()); - co_return Ok(); - } else { - Sys::errln("unknown verb: {} (expected: dump-stylesheet, dump-sst, dump-tokens)\n", verb); - co_return Error::invalidInput(); - } -} diff --git a/src/web/vaev-css/cli/manifest.json b/src/web/vaev-css/cli/manifest.json deleted file mode 100644 index 88634f8..0000000 --- a/src/web/vaev-css/cli/manifest.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "$schema": "https://schemas.cute.engineering/stable/cutekit.manifest.component.v1", - "id": "vaev-css.cli", - "type": "exe", - "description": "Css engine testing tool", - "requires": [ - "karm-sys", - "vaev-css", - "vaev-driver" - ] -} diff --git a/src/web/vaev-devtools/main.cpp b/src/web/vaev-devtools/main.cpp deleted file mode 100644 index 8f2ebf3..0000000 --- a/src/web/vaev-devtools/main.cpp +++ /dev/null @@ -1,19 +0,0 @@ -#include -#include -#include - -#include "app.h" - -Async::Task<> entryPointAsync(Sys::Context &ctx) { - auto args = Sys::useArgs(ctx); - auto url = args.len() - ? co_try$(Mime::parseUrlOrPath(args[0])) - : "about:start"_url; - - auto dom = Vaev::Driver::fetchDocument(url); - - co_return Ui::runApp( - ctx, - Hideo::Browser::app(url, dom) - ); -} diff --git a/src/web/vaev-devtools/manifest.json b/src/web/vaev-devtools/manifest.json deleted file mode 100644 index 49d3989..0000000 --- a/src/web/vaev-devtools/manifest.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "$schema": "https://schemas.cute.engineering/stable/cutekit.manifest.component.v1", - "id": "vaev-devtools", - "description": "Vaev's devtools", - "type": "exe", - "requires": [ - "hideo-base", - "vaev-view" - ], - "provides": [ - "o-devtools" - ] -} diff --git a/src/web/vaev-devtools/res/blank.xhtml b/src/web/vaev-devtools/res/blank.xhtml deleted file mode 100644 index c3b16ce..0000000 --- a/src/web/vaev-devtools/res/blank.xhtml +++ /dev/null @@ -1,14 +0,0 @@ - - - - - - - Document - - - - - - - diff --git a/src/web/vaev-devtools/res/start-page.xhtml b/src/web/vaev-devtools/res/start-page.xhtml deleted file mode 100644 index 33bfd41..0000000 --- a/src/web/vaev-devtools/res/start-page.xhtml +++ /dev/null @@ -1,14 +0,0 @@ - - - - - - - Start Page - - - - Hello, world! - - - diff --git a/src/web/vaev-html2pdf/main.cpp b/src/web/vaev-html2pdf/main.cpp deleted file mode 100644 index f6d6228..0000000 --- a/src/web/vaev-html2pdf/main.cpp +++ /dev/null @@ -1,79 +0,0 @@ -#include -#include -#include -#include -#include -#include - -namespace Vaev { - -Style::Media constructMedia(Print::PaperStock paper) { - return { - .type = MediaType::SCREEN, - .width = Px{paper.width}, - .height = Px{paper.height}, - .aspectRatio = paper.width / paper.height, - .orientation = Orientation::LANDSCAPE, - - .resolution = Resolution::fromDpi(96), - .scan = Scan::PROGRESSIVE, - .grid = false, - .update = Update::NONE, - - .overflowBlock = OverflowBlock::PAGED, - .overflowInline = OverflowInline::NONE, - - .color = 8, - .colorIndex = 0, - .monochrome = 0, - .colorGamut = ColorGamut::SRGB, - .pointer = Pointer::NONE, - .hover = Hover::NONE, - .anyPointer = Pointer::NONE, - .anyHover = Hover::NONE, - - .prefersReducedMotion = ReducedMotion::REDUCE, - .prefersReducedTransparency = ReducedTransparency::REDUCE, - .prefersContrast = Contrast::MORE, - .forcedColors = Colors::NONE, - .prefersColorScheme = ColorScheme::LIGHT, - .prefersReducedData = ReducedData::NO_PREFERENCE, - }; -} - -} // namespace Vaev - -Async::Task<> entryPointAsync(Sys::Context &ctx) { - auto args = Sys::useArgs(ctx); - if (args.len() != 2) { - Sys::errln("usage: html2pdf \n"); - co_return Error::invalidInput(); - } - - auto input = co_try$(Mime::parseUrlOrPath(args[0])); - auto output = co_try$(Mime::parseUrlOrPath(args[1])); - - auto dom = co_try$(Vaev::Driver::fetchDocument(input)); - - auto paper = Print::A4; - auto media = Vaev::constructMedia(paper); - - auto start = Sys::now(); - auto [layout, paint] = Vaev::Driver::render(*dom, media, paper); - auto elapsed = Sys::now() - start; - logInfo("render time: {}", elapsed); - - logDebug("layout tree: {}", layout); - logDebug("paint tree: {}", paint); - - Print::PdfPrinter printer; - paint->print(printer); - - auto file = co_try$(Sys::File::create(output)); - Io::TextEncoder<> encoder{file}; - Io::Emit e{encoder}; - printer.write(e); - co_try$(e.flush()); - - co_return Ok(); -} diff --git a/src/web/vaev-html2pdf/manifest.json b/src/web/vaev-html2pdf/manifest.json deleted file mode 100644 index 9c11702..0000000 --- a/src/web/vaev-html2pdf/manifest.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "$schema": "https://schemas.cute.engineering/stable/cutekit.manifest.component.v1", - "id": "html2pdf", - "type": "exe", - "description": "Converts HTML to PDF", - "requires": [ - "vaev-xml", - "vaev-style", - "vaev-css", - "vaev-layout", - "vaev-view", - "karm-sys" - ], - "provides": [ - "o-html2pdf" - ] -} diff --git a/src/web/vaev-markup/cli/main.cpp b/src/web/vaev-markup/cli/main.cpp deleted file mode 100644 index 0b67f7e..0000000 --- a/src/web/vaev-markup/cli/main.cpp +++ /dev/null @@ -1,78 +0,0 @@ -#include -#include -#include -#include -#include -#include -#include - -Async::Task<> entryPointAsync(Sys::Context &ctx) { - auto args = Sys::useArgs(ctx); - - if (args.len() != 2) { - Sys::errln("usage: vaev-markup.cli \n"); - co_return Error::invalidInput(); - } - - auto verb = args[0]; - auto url = co_try$(Mime::parseUrlOrPath(args[1])); - - if (verb == "html-dump-dom") { - auto file = co_try$(Sys::File::open(url)); - auto buf = co_try$(Io::readAllUtf8(file)); - auto doc = makeStrong(); - Vaev::Markup::HtmlParser parser{doc}; - parser.write(buf); - Sys::println("{}", doc); - - co_return Ok(); - } else if (verb == "html-dump-tokens") { - auto file = co_try$(Sys::File::open(url)); - auto buf = co_try$(Io::readAllUtf8(file)); - - Vec tokens; - - struct VecSink : public Vaev::Markup::HtmlSink { - Vec &tokens; - - VecSink(Vec &tokens) - : tokens(tokens) { - } - - void accept(Vaev::Markup::HtmlToken const &token) override { - tokens.pushBack(token); - } - }; - - VecSink sink{tokens}; - Vaev::Markup::HtmlLexer lexer{}; - lexer.bind(sink); - - for (auto r : iterRunes(buf)) - lexer.consume(r); - - for (auto &t : tokens) - Sys::println("{}", t); - - co_return Ok(); - } else if (verb == "xml-dump-dom") { - auto file = co_try$(Sys::File::open(url)); - auto buf = co_try$(Io::readAllUtf8(file)); - - auto start = Sys::now(); - - Vaev::Markup::XmlParser parser{}; - Io::SScan s{buf}; - auto res = parser.parse(s, Vaev::HTML); - - auto elapsed = Sys::now() - start; - logInfo("parsed in {}ms", elapsed.toUSecs() / 1000.0); - - Sys::println("{}", res); - - co_return Ok(); - } else { - Sys::errln("unknown verb: {} (expected: html-dump-dom, html-dump-tokens, xml-dump-dom)\n", verb); - co_return Error::invalidInput(); - } -} diff --git a/src/web/vaev-style/cli/main.cpp b/src/web/vaev-style/cli/main.cpp deleted file mode 100644 index bc2de88..0000000 --- a/src/web/vaev-style/cli/main.cpp +++ /dev/null @@ -1,27 +0,0 @@ -#include -#include -#include -#include -#include - -Async::Task<> entryPointAsync(Sys::Context &ctx) { - auto args = Sys::useArgs(ctx); - - if (args.len() != 1) { - Sys::errln("usage: vaev-style.cli \n"); - co_return Error::invalidInput(); - } - - auto verb = args[0]; - - if (verb == "list-props") { - Vaev::Style::StyleProp::any([](Meta::Type) { - Sys::println("{}", T::name()); - return false; - }); - co_return Ok(); - } else { - Sys::errln("unknown verb: {} (expected: list-props)\n", verb); - co_return Error::invalidInput(); - } -} diff --git a/src/web/vaev-style/cli/manifest.json b/src/web/vaev-style/cli/manifest.json deleted file mode 100644 index f4fce9a..0000000 --- a/src/web/vaev-style/cli/manifest.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "$schema": "https://schemas.cute.engineering/stable/cutekit.manifest.component.v1", - "id": "vaev-style.cli", - "type": "exe", - "description": "Style engine testing tool", - "requires": [ - "vaev-style", - "karm-sys" - ] -} diff --git a/src/web/vaev-view/manifest.json b/src/web/vaev-view/manifest.json index 03ba95f..bf8fe5f 100644 --- a/src/web/vaev-view/manifest.json +++ b/src/web/vaev-view/manifest.json @@ -2,7 +2,7 @@ "$schema": "https://schemas.cute.engineering/stable/cutekit.manifest.component.v1", "id": "vaev-view", "type": "lib", - "description": "Host Vaev web content", + "description": "Host Vaev web content withing a user interface.", "requires": [ "karm-ui", "vaev-driver"