diff --git a/src/libs/karm-base/map.h b/src/libs/karm-base/map.h index 074c7b5e76..d59a0ea407 100644 --- a/src/libs/karm-base/map.h +++ b/src/libs/karm-base/map.h @@ -103,7 +103,7 @@ struct Map { } auto iter() { - return mutIter(_els); + return ::iter(_els); } auto iter() const { diff --git a/src/libs/karm-base/union.h b/src/libs/karm-base/union.h index 2ec0ef7166..b1c92dd43b 100644 --- a/src/libs/karm-base/union.h +++ b/src/libs/karm-base/union.h @@ -180,9 +180,7 @@ struct Union { return false; } - std::partial_ordering operator<=>(Union const &other) const - requires(Meta::Comparable && ...) - { + std::partial_ordering operator<=>(Union const &other) const { if (_index == other._index) return visit( [&](T const &ptr) @@ -194,9 +192,7 @@ struct Union { return std::partial_ordering::unordered; } - bool operator==(Union const &other) const - requires(Meta::Equatable && ...) - { + bool operator==(Union const &other) const { if (_index == other._index) return visit( [&](T const &ptr) { diff --git a/src/libs/karm-io/emit.h b/src/libs/karm-io/emit.h index 5b795b02e6..0982e2d390 100644 --- a/src/libs/karm-io/emit.h +++ b/src/libs/karm-io/emit.h @@ -113,7 +113,7 @@ struct Emit : public Io::TextWriterBase<> { Res flush() override { try$(_error); if (_newline) - _tryWrapper(_writer.writeRune('\n')); + try$(_insertNewline()); return Ok(_total); } }; diff --git a/src/libs/karm-print/pdf/cli/main.cpp b/src/libs/karm-print/pdf/cli/main.cpp index 67e2d4b7b0..c9469adb90 100644 --- a/src/libs/karm-print/pdf/cli/main.cpp +++ b/src/libs/karm-print/pdf/cli/main.cpp @@ -1,6 +1,95 @@ #include #include +#include "../objects.h" + Async::Task<> entryPointAsync(Sys::Context &) { + Pdf::Ref ref; + + Pdf::File file; + + file.header = "PDF-1.7"s; + + auto catalogRef = ref.alloc(); + auto pagesRef = ref.alloc(); + auto pageRef = ref.alloc(); + auto fontRef = ref.alloc(); + auto contentRef = ref.alloc(); + + // Catalog + + Pdf::Dict catalog; + catalog.put("Type"s, Pdf::Name{"Catalog"s}); + catalog.put("Pages"s, pagesRef); + + file.add(catalogRef, std::move(catalog)); + + // Pages + + Pdf::Dict pages; + pages.put("Type"s, Pdf::Name{"Pages"s}); + + Pdf::Array mediaBox; + mediaBox.pushBack(isize{0}); + mediaBox.pushBack(isize{0}); + mediaBox.pushBack(isize{200}); + mediaBox.pushBack(isize{200}); + + pages.put("MediaBox"s, mediaBox); + pages.put("Count"s, isize{1}); + pages.put("Kids"s, Pdf::Array{pageRef}); + + file.add(pagesRef, std::move(pages)); + + // Page + + Pdf::Dict page; + page.put("Type"s, Pdf::Name{"Page"s}); + page.put("Parent"s, pagesRef); + + Pdf::Dict resources; + Pdf::Dict fonts = {}; + fonts.put("F1"s, fontRef); + resources.put("Font"s, fonts); + page.put("Resources"s, resources); + + page.put("Contents"s, contentRef); + + file.add(pageRef, std::move(page)); + + // Font + + Pdf::Dict font; + font.put("Type"s, Pdf::Name{"Font"s}); + font.put("Subtype"s, Pdf::Name{"Type1"s}); + font.put("BaseFont"s, Pdf::Name{"Times-Roman"s}); + + file.add(fontRef, std::move(font)); + + // Content + + Io::BufferWriter pageStream; + Io::BEmit pageEmit{pageStream}; + pageEmit.writeStr( + R"(BT +70 50 TD +/F1 12 Tf +(Hello, world!) Tj +ET)"s + ); + + Pdf::Stream content; + content.dict.put("Length"s, (isize)pageStream.bytes().len()); + content.data = pageStream.take(); + + file.add(contentRef, std::move(content)); + + file.trailer.put("Root"s, catalogRef); + file.trailer.put("Size"s, (isize)file.body.len() + 1); + + Io::Emit out{Sys::out()}; + Pdf::write(out, file); + co_try$(Sys::out().flush()); + co_return Ok(); } diff --git a/src/libs/karm-print/pdf/graphic.cpp b/src/libs/karm-print/pdf/graphic.cpp new file mode 100644 index 0000000000..b994059aa6 --- /dev/null +++ b/src/libs/karm-print/pdf/graphic.cpp @@ -0,0 +1,172 @@ +#include "graphic.h" + +namespace Pdf { + +// Graphics state operators + +void Graphic::save() { + e("q"); +} +void Graphic::restore() { + e("Q"); +} +void Graphic::transform(Math::Trans2f t) { + e(t.xx); + e(' '); + e(t.xy); + e(' '); + e(t.yx); + e(' '); + e(t.yy); + e(' '); + e(t.ox); + e(' '); + e(t.oy); + e(" cm"); +} +void Graphic::lineWidth(f64 w) { + e("{} w", w); +} +void Graphic::lineCap(LineCap cap) { + e("{} J", toUnderlyingType(cap)); +} +void Graphic::lineJoin(LineJoin join) { + e("{} j", toUnderlyingType(join)); +} +void Graphic::miterLimit(f64 m) { + e("{} M", m); +} +void Graphic::dash(Slice const &d, f64 o) { + e("["); + for (usize i = 0; i < d.len(); ++i) { + if (i > 0) { + e(' '); + } + e(d[i]); + } + e("] "); + e(o); + e(" d"); +} + +// Path construction operators + +void Graphic::moveTo(Math::Vec2f p) { + e("{} {} m", p.x, p.y); +} + +void Graphic::lineTo(Math::Vec2f p) { + e("{} {} l", p.x, p.y); +} + +void Graphic::curveTo(Math::Vec2f c1, Math::Vec2f c2, Math::Vec2f p) { + e("{} {} {} {} {} {} c", c1.x, c1.y, c2.x, c2.y, p.x, p.y); +} + +void Graphic::closePath() { + e("h"); +} + +void Graphic::rectangle(Math::Rectf r) { + e("{} {} {} {} re", r.x, r.y, r.width, r.height); +} + +// Path painting operators + +void Graphic::stroke() { + e("S"); +} + +void Graphic::closeStroke() { + e("s"); +} + +void Graphic::fill(FillRule rule) { + if (rule == FillRule::NONZERO) { + e("f"); + } else { + e("f*"); + } +} + +void Graphic::fillStroke(FillRule rule) { + if (rule == FillRule::NONZERO) { + e("B"); + } else { + e("B*"); + } +} + +void Graphic::closeFillStroke(FillRule rule) { + if (rule == FillRule::NONZERO) { + e("b"); + } else { + e("b*"); + } +} + +void Graphic::endPath() { + e("n"); +} + +// Clipping path operators + +void Graphic::clip(FillRule rule) { + if (rule == FillRule::NONZERO) { + e("W"); + } else { + e("W*"); + } +} + +// Text object operators + +void Graphic::beginText() { + e("BT"); +} + +void Graphic::endText() { + e("ET"); +} + +// Text state operators + +void Graphic::charSpacing(f64 s) { + e("{} Tc", s); +} + +void Graphic::wordSpacing(f64 s) { + e("{} Tw", s); +} + +void Graphic::horizScaling(f64 s) { + e("{} Tz", s); +} + +void Graphic::textLeading(f64 l) { + e("{} TL", l); +} + +void Graphic::fontSize(Name const &font, f64 size) { + e("/{} {} Tf", font.str(), size); +} + +void Graphic::textRenderMode(TextRenderMode mode) { + e("{} Tr", toUnderlyingType(mode)); +} + +void Graphic::rise(f64 r) { + e("{} Ts", r); +} + +void Graphic::moveText(Math::Vec2f p) { + e("{} {} Td", p.x, p.y); +} + +void Graphic::moveTextSet(Math::Vec2f p) { + e("{} {} Tm", p.x, p.y); +} + +// Text-positioning operators + +} // namespace Pdf diff --git a/src/libs/karm-print/pdf/graphic.h b/src/libs/karm-print/pdf/graphic.h index 5c994fe68e..ef92100e86 100644 --- a/src/libs/karm-print/pdf/graphic.h +++ b/src/libs/karm-print/pdf/graphic.h @@ -39,179 +39,81 @@ enum struct TextRenderMode { struct Graphic { Io::Emit &e; - // Graphics state operators - - void save() { - e("q"); - } - - void restore() { - e("Q"); - } - - void transform(Math::Trans2f t) { - e(t.xx); - e(' '); - e(t.xy); - e(' '); - e(t.yx); - e(' '); - e(t.yy); - e(' '); - e(t.ox); - e(' '); - e(t.oy); - e(" cm"); - } - - void lineWidth(f64 w) { - e("{} w", w); - } - - void lineCap(LineCap cap) { - e("{} J", toUnderlyingType(cap)); - } - - void lineJoin(LineJoin join) { - e("{} j", toUnderlyingType(join)); - } - - void miterLimit(f64 m) { - e("{} M", m); - } - - void dash(Slice const &d, f64 o) { - e("["); - for (usize i = 0; i < d.len(); ++i) { - if (i > 0) { - e(' '); - } - e(d[i]); - } - e("] "); - e(o); - e(" d"); - } - - // Path construction operators - - void moveTo(Math::Vec2f p) { - e("{} {} m", p.x, p.y); - } - - void lineTo(Math::Vec2f p) { - e("{} {} l", p.x, p.y); - } - - void curveTo(Math::Vec2f c1, Math::Vec2f c2, Math::Vec2f p) { - e("{} {} {} {} {} {} c", c1.x, c1.y, c2.x, c2.y, p.x, p.y); - } - - void closePath() { - e("h"); - } - - void rectangle(Math::Rectf r) { - e("{} {} {} {} re", r.x, r.y, r.width, r.height); - } - - // Path painting operators - - void stroke() { - e("S"); - } - - void closeStroke() { - e("s"); - } - - void fill(FillRule rule = FillRule::NONZERO) { - if (rule == FillRule::NONZERO) { - e("f"); - } else { - e("f*"); - } - } - - void fillStroke(FillRule rule = FillRule::NONZERO) { - if (rule == FillRule::NONZERO) { - e("B"); - } else { - e("B*"); - } - } - - void closeFillStroke(FillRule rule = FillRule::NONZERO) { - if (rule == FillRule::NONZERO) { - e("b"); - } else { - e("b*"); - } - } - - void endPath() { - e("n"); - } - - // Clipping path operators - - void clip(FillRule rule = FillRule::NONZERO) { - if (rule == FillRule::NONZERO) { - e("W"); - } else { - e("W*"); - } - } - - // Text object operators - - void beginText() { - e("BT"); - } - - void endText() { - e("ET"); - } - - // Text state operators - - void charSpacing(f64 s) { - e("{} Tc", s); - } - - void wordSpacing(f64 s) { - e("{} Tw", s); - } - - void horizScaling(f64 s) { - e("{} Tz", s); - } - - void textLeading(f64 l) { - e("{} TL", l); - } - - void fontSize(Name const &font, f64 size) { - e("/{} {} Tf", font.value, size); - } - - void textRenderMode(TextRenderMode mode) { - e("{} Tr", toUnderlyingType(mode)); - } - - void rise(f64 r) { - e("{} Ts", r); - } - - // Text-positioning operators - - void moveText(Math::Vec2f p) { - e("{} {} Td", p.x, p.y); - } - - void moveTextSet(Math::Vec2f p) { - e("{} {} Tm", p.x, p.y); - } + // MARK: Graphics state operators ------------------------------------------ + + void save(); + + void restore(); + + void transform(Math::Trans2f t); + + void lineWidth(f64 w); + + void lineCap(LineCap cap); + + void lineJoin(LineJoin join); + + void miterLimit(f64 m); + + void dash(Slice const &d, f64 o); + + // MARK: Path construction operators --------------------------------------- + + void moveTo(Math::Vec2f p); + + void lineTo(Math::Vec2f p); + + void curveTo(Math::Vec2f c1, Math::Vec2f c2, Math::Vec2f p); + + void closePath(); + + void rectangle(Math::Rectf r); + + // MARK: Path painting operators ------------------------------------------- + + void stroke(); + + void closeStroke(); + + void fill(FillRule rule = FillRule::NONZERO); + + void fillStroke(FillRule rule = FillRule::NONZERO); + + void closeFillStroke(FillRule rule = FillRule::NONZERO); + + void endPath(); + + // MARK: Clipping path operators ------------------------------------------- + + void clip(FillRule rule = FillRule::NONZERO); + + // MARK: Text object operators --------------------------------------------- + + void beginText(); + + void endText(); + + // MARK: Text state operators ---------------------------------------------- + + void charSpacing(f64 s); + + void wordSpacing(f64 s); + + void horizScaling(f64 s); + + void textLeading(f64 l); + + void fontSize(Name const &font, f64 size); + + void textRenderMode(TextRenderMode mode); + + void rise(f64 r); + + // MARK: Text-positioning operators ---------------------------------------- + + void moveText(Math::Vec2f p); + + void moveTextSet(Math::Vec2f p); }; } // namespace Pdf diff --git a/src/libs/karm-print/pdf/objects.cpp b/src/libs/karm-print/pdf/objects.cpp index 1c9e30f699..af800d35aa 100644 --- a/src/libs/karm-print/pdf/objects.cpp +++ b/src/libs/karm-print/pdf/objects.cpp @@ -8,28 +8,22 @@ void write(Io::Emit &e, Object const &o) { e("null"); }, [&](Ref const &ref) { - e(ref.num); - e(' '); - e(ref.gen); - e(" R"); + e("{} {} R", ref.num, ref.gen); }, [&](bool b) { e(b ? "true" : "false"); }, [&](isize i) { - e(i); + e("{}", i); }, [&](f64 f) { - e(f); + e("{}", f); }, [&](String const &s) { - e('('); - e(s); - e(')'); + e("({})", s); }, [&](Name const &n) { - e('/'); - e(n.value); + e("/{}", n.str()); }, [&](Array const &a) { e('['); @@ -45,7 +39,7 @@ void write(Io::Emit &e, Object const &o) { e("<<\n"); for (auto const &[k, v] : d.iter()) { e('/'); - e(k.value); + e(k); e(' '); write(e, v); e('\n'); @@ -54,11 +48,47 @@ void write(Io::Emit &e, Object const &o) { }, [&](Stream const &s) { write(e, Dict{s.dict}); - e("\nstream\n"); + e("stream\n"); + (void)e.flush(); (void)e.write(s.data); - e("\nendstream"); + e("endstream\n"); } }); } +void write(Io::Emit &e, File const &f) { + e("%{}\n", f.header); + + XRef xref; + + for (auto const &[k, v] : f.body.iter()) { + xref.add(e.total(), k.gen); + e("{} {} obj\n", k.num, k.gen); + write(e, v); + e("\nendobj\n"); + } + + auto startxref = e.total(); + + e("xref\n"); + write(e, xref); + + e("trailer\n"); + write(e, f.trailer); + + e("startxref\n"); + e("{}\n", startxref); + e("%%EOF"); +} + +void write(Io::Emit &e, XRef const &x) { + e("0 {}\n", x.entries.len() + 1); + for (usize i = 0; i < x.entries.len(); ++i) { + auto const &entry = x.entries[i]; + if (entry.used) { + e("{:010} {:05} n\n", entry.offset, entry.gen); + } + } +} + } // namespace Pdf diff --git a/src/libs/karm-print/pdf/objects.h b/src/libs/karm-print/pdf/objects.h index e9936c08e2..e57f4ee504 100644 --- a/src/libs/karm-print/pdf/objects.h +++ b/src/libs/karm-print/pdf/objects.h @@ -8,12 +8,19 @@ namespace Pdf { struct Object; struct Ref { - usize num; - usize gen; + usize num{}; + usize gen{}; + + Ref alloc() { + return {++num, gen}; + } + + bool operator==(Ref const &other) const = default; + auto operator<=>(Ref const &other) const = default; }; -struct Name { - String value; +struct Name : public String { + using String::String; }; using Array = Vec; @@ -22,7 +29,7 @@ using Dict = Map; struct Stream { Dict dict; - Bytes data; + Buf data; }; using _Object = Union< @@ -43,4 +50,33 @@ struct Object : public _Object { void write(Io::Emit &e, Object const &o); +struct File { + String header; + Map body; + Dict trailer; + + Ref add(Ref ref, Object object) { + body.put(ref, object); + return ref; + } +}; + +void write(Io::Emit &e, File const &o); + +struct XRef { + struct Entry { + usize offset; + usize gen; + bool used; + }; + + Vec entries; + + void add(usize offset, usize gen) { + entries.pushBack({offset, gen, true}); + } +}; + +void write(Io::Emit &e, XRef const &xref); + } // namespace Pdf