diff --git a/.github/workflows/all.yml b/.github/workflows/all.yml index 8294ed86..197098d3 100644 --- a/.github/workflows/all.yml +++ b/.github/workflows/all.yml @@ -118,7 +118,7 @@ jobs: - name: Install updates run: pacman -Syu --noconfirm - name: Install dependencies - run: pacman -Sy --needed --noconfirm zeromq cppzmq gtkmm3 cairomm librsvg sqlite3 libgit2 curl opencascade glm podofo-0.9 libarchive python libspnav cmake meson + run: pacman -Sy --needed --noconfirm zeromq cppzmq gtkmm3 cairomm librsvg sqlite3 libgit2 curl opencascade glm podofo libarchive python libspnav cmake meson - name: Install python dependencies if: ${{ matrix.target == 'horizon.so' }} run: pacman -S --needed --noconfirm mesa python-cairo python-yaml @@ -145,7 +145,6 @@ jobs: runs-on: ubuntu-latest container: opensuse/tumbleweed needs: stylecheck - continue-on-error: true steps: - uses: actions/checkout@v3 - name: Install dependencies diff --git a/3rd_party/DejaVuSans.ttf b/3rd_party/DejaVuSans.ttf new file mode 100644 index 00000000..9fe4771d Binary files /dev/null and b/3rd_party/DejaVuSans.ttf differ diff --git a/imp.gresource.xml b/imp.gresource.xml index 2a7730af..d2e42b86 100644 --- a/imp.gresource.xml +++ b/imp.gresource.xml @@ -184,5 +184,7 @@ canvas3d/shaders/background-fragment.glsl color_presets.json + + DejaVuSans.ttf diff --git a/meson.build b/meson.build index e567118f..16da87c5 100644 --- a/meson.build +++ b/meson.build @@ -65,22 +65,32 @@ if get_option('debug') cpp_args += '-DCONNECTION_CHECK' endif -# pkg-config is useless for podofo :( +# pkg-config is useless for old podofo :( +podofo_legacy = true podofo_lib =cxx.find_library('podofo', dirs: '/usr/lib/podofo-0.9/', required: false, has_headers:['/usr/include/podofo-0.9/podofo/podofo.h'] ) if podofo_lib.found() -podofo = declare_dependency ( + podofo = declare_dependency ( dependencies: podofo_lib, -include_directories: include_directories('/usr/include/podofo-0.9') -) + include_directories: include_directories('/usr/include/podofo-0.9') + ) else podofo = dependency('libpodofo09', required:false) if podofo.found() cpp_args += '-DINC_PODOFO_WITHOUT_DIRECTORY' else podofo = dependency('libpodofo') + if podofo.version() == 'unknown' or podofo.version() < '0.10.0' + podofo_legacy = true + else + podofo_legacy = false + endif endif endif +if podofo_legacy + message('using legacy podofo') +endif + stdlibs = [] is_libstdcpp = cxx.get_define('__GLIBCXX__', prefix: '#include ') != '' if is_libstdcpp @@ -666,10 +676,6 @@ src_export = files( 'src/export_util/tree_writer.cpp', 'src/export_util/tree_writer_fs.cpp', 'src/export_util/tree_writer_archive.cpp', - 'src/export_pdf/canvas_pdf.cpp', - 'src/export_pdf/export_pdf.cpp', - 'src/export_pdf/export_pdf_board.cpp', - 'src/export_pdf/export_pdf_util.cpp', 'src/export_pnp/export_pnp.cpp', 'src/export_bom/export_bom.cpp', 'src/export_odb/odb_export.cpp', @@ -686,6 +692,18 @@ src_export = files( 'src/export_odb/structured_text_writer.cpp' ) +if podofo_legacy + src_export += 'src/export_pdf/legacy/canvas_pdf.cpp' + src_export += 'src/export_pdf/legacy/export_pdf.cpp' + src_export += 'src/export_pdf/legacy/export_pdf_board.cpp' + src_export += 'src/export_pdf/legacy/export_pdf_util.cpp' +else + src_export += 'src/export_pdf/canvas_pdf.cpp' + src_export += 'src/export_pdf/export_pdf.cpp' + src_export += 'src/export_pdf/export_pdf_board.cpp' + src_export += 'src/export_pdf/export_pdf_util.cpp' +endif + src_board_rules_check = files( 'src/rules/cache.cpp', 'src/board/board_rules_check.cpp', @@ -918,7 +936,7 @@ resources = gnome.compile_resources( 'imp.gresource.xml', dependencies: color_presets_json, c_name: 'horizon_resources', - source_dir: 'src' + source_dir: ['src', '3rd_party'] ) libhorizon = static_library('libhorizon', diff --git a/src/export_pdf/canvas_pdf.cpp b/src/export_pdf/canvas_pdf.cpp index 5221986f..c0d15efe 100644 --- a/src/export_pdf/canvas_pdf.cpp +++ b/src/export_pdf/canvas_pdf.cpp @@ -6,6 +6,7 @@ #include "common/hole.hpp" #include "canvas/appearance.hpp" #include "board/plane.hpp" +#include namespace horizon { @@ -15,11 +16,12 @@ double to_pt(double x_nm) } CanvasPDF::CanvasPDF(PoDoFo::PdfPainter &p, PoDoFo::PdfFont &f, const PDFExportSettings &s) - : Canvas::Canvas(), painter(p), font(f), settings(s), metrics(font.GetFontMetrics()) + : Canvas::Canvas(), painter(p), font(f), settings(s), metrics(font.GetMetrics()) { img_mode = true; Appearance apperarance; layer_colors = apperarance.layer_colors; + path.Reset(); } bool CanvasPDF::pdf_layer_visible(int l) const @@ -42,9 +44,9 @@ void CanvasPDF::img_line(const Coordi &p0, const Coordi &p1, const uint64_t widt { if (!pdf_layer_visible(layer)) return; - painter.Save(); - auto w = std::max(width, settings.min_line_width); - painter.SetStrokeWidth(to_pt(w)); + + auto w = std::max(width, std::max(settings.min_line_width, (uint64_t).001_mm)); + painter.GraphicsState.SetLineWidth(to_pt(w)); Coordi rp0 = p0; Coordi rp1 = p1; if (tr) { @@ -52,9 +54,8 @@ void CanvasPDF::img_line(const Coordi &p0, const Coordi &p1, const uint64_t widt rp1 = transform.transform(p1); } auto color = get_pdf_layer_color(layer); - painter.SetStrokingColor(color.r, color.g, color.b); + painter.GraphicsState.SetStrokeColor(PoDoFo::PdfColor(color.r, color.g, color.b)); painter.DrawLine(to_pt(rp0.x), to_pt(rp0.y), to_pt(rp1.x), to_pt(rp1.y)); - painter.Restore(); } void CanvasPDF::img_draw_text(const Coordf &p, float size, const std::string &rtext, int angle, bool flip, @@ -87,10 +88,11 @@ void CanvasPDF::img_draw_text(const Coordf &p, float size, const std::string &rt if (mirror) { lineskip *= -1; } - font.SetFontSize(to_pt(size) * 1.6); + + painter.TextState.SetFont(font, to_pt(size) * 1.6); while (std::getline(ss, line, '\n')) { line = TextData::trim(line); - int64_t line_width = metrics->StringWidthMM(line.c_str()) * 1000; + int64_t line_width = font.GetStringLength(line.c_str(), painter.TextState); Placement tf; tf.shift.x = p.x; @@ -128,9 +130,9 @@ void CanvasPDF::img_draw_text(const Coordf &p, float size, const std::string &rt Coordi p0(xshift, yshift); Coordi pt = tf.transform(p0); - painter.SetTransformationMatrix(cos(fangle), sin(fangle), -sin(fangle), cos(fangle), to_pt(pt.x), to_pt(pt.y)); - PoDoFo::PdfString pstr(reinterpret_cast(line.c_str())); - painter.DrawText(0, to_pt(size) / -2, pstr); + painter.GraphicsState.SetCurrentMatrix(PoDoFo::Matrix::FromCoefficients(cos(fangle), sin(fangle), -sin(fangle), + cos(fangle), to_pt(pt.x), to_pt(pt.y))); + painter.DrawText(line.c_str(), 0, to_pt(size) / -2); painter.Restore(); i_line++; @@ -139,55 +141,55 @@ void CanvasPDF::img_draw_text(const Coordf &p, float size, const std::string &rt void CanvasPDF::img_polygon(const Polygon &ipoly, bool tr) { + if (!pdf_layer_visible(ipoly.layer)) return; - painter.Save(); + auto color = get_pdf_layer_color(ipoly.layer); - painter.SetColor(color.r, color.g, color.b); - painter.SetStrokingColor(color.r, color.g, color.b); - painter.SetStrokeWidth(to_pt(settings.min_line_width)); + painter.GraphicsState.SetFillColor(PoDoFo::PdfColor(color.r, color.g, color.b)); + painter.GraphicsState.SetStrokeColor(PoDoFo::PdfColor(color.r, color.g, color.b)); + painter.GraphicsState.SetLineWidth(to_pt(settings.min_line_width)); if (ipoly.usage == nullptr) { // regular patch draw_polygon(ipoly, tr); if (fill) - painter.Fill(); + painter.DrawPath(path, PoDoFo::PdfPathDrawMode::Fill); else - painter.Stroke(); + painter.DrawPath(path, PoDoFo::PdfPathDrawMode::Stroke); } else if (auto plane = dynamic_cast(ipoly.usage.ptr)) { for (const auto &frag : plane->fragments) { - for (const auto &path : frag.paths) { + for (const auto &dpath : frag.paths) { bool first = true; - for (const auto &it : path) { + for (const auto &it : dpath) { Coordi p(it.X, it.Y); if (tr) p = transform.transform(p); if (first) - painter.MoveTo(to_pt(p.x), to_pt(p.y)); + path.MoveTo(to_pt(p.x), to_pt(p.y)); else - painter.LineTo(to_pt(p.x), to_pt(p.y)); + path.AddLineTo(to_pt(p.x), to_pt(p.y)); first = false; } - painter.ClosePath(); + path.Close(); } } if (fill) - painter.Fill(true); + painter.DrawPath(path, PoDoFo::PdfPathDrawMode::Fill); else - painter.Stroke(); + painter.DrawPath(path, PoDoFo::PdfPathDrawMode::Stroke); } - painter.Restore(); + path.Reset(); } void CanvasPDF::img_hole(const Hole &hole) { if (!pdf_layer_visible(PDFExportSettings::HOLES_LAYER)) return; - painter.Save(); auto color = get_pdf_layer_color(PDFExportSettings::HOLES_LAYER); - painter.SetColor(color.r, color.g, color.b); - painter.SetStrokingColor(color.r, color.g, color.b); - painter.SetStrokeWidth(to_pt(settings.min_line_width)); + painter.GraphicsState.SetFillColor(PoDoFo::PdfColor(color.r, color.g, color.b)); + painter.GraphicsState.SetStrokeColor(PoDoFo::PdfColor(color.r, color.g, color.b)); + painter.GraphicsState.SetLineWidth(to_pt(settings.min_line_width)); auto hole2 = hole; if (settings.set_holes_size) { @@ -195,10 +197,10 @@ void CanvasPDF::img_hole(const Hole &hole) } draw_polygon(hole2.to_polygon(), true); if (fill) - painter.Fill(true); + painter.DrawPath(path, PoDoFo::PdfPathDrawMode::Fill); else - painter.Stroke(); - painter.Restore(); + painter.DrawPath(path, PoDoFo::PdfPathDrawMode::Stroke); + path.Reset(); } // c is the arc center. @@ -206,7 +208,7 @@ void CanvasPDF::img_hole(const Hole &hole) // See "How to determine the control points of a Bézier curve that approximates a // small circular arc" by Richard ADeVeneza, Nov 2004 // https://www.tinaja.com/glib/bezcirc2.pdf -static Coordd pdf_arc_segment(PoDoFo::PdfPainter &painter, const Coordd c, const double r, double a0, double a1) +static Coordd pdf_arc_segment(PoDoFo::PdfPainterPath &path, const Coordd c, const double r, double a0, double a1) { const auto da = a0 - a1; assert(da != 0); @@ -227,11 +229,11 @@ static Coordd pdf_arc_segment(PoDoFo::PdfPainter &painter, const Coordd c, const const auto c2 = p2.rotate(theta) * r + c; const auto c3 = p3.rotate(theta) * r + c; - painter.CubicBezierTo(to_pt(c1.x), to_pt(c1.y), to_pt(c2.x), to_pt(c2.y), to_pt(c3.x), to_pt(c3.y)); + path.AddCubicBezierTo(to_pt(c1.x), to_pt(c1.y), to_pt(c2.x), to_pt(c2.y), to_pt(c3.x), to_pt(c3.y)); return c3; // end point } -static void pdf_arc(PoDoFo::PdfPainter &painter, const Coordd start, const Coordd c, const Coordd end, bool cw) +static void pdf_arc(PoDoFo::PdfPainterPath &path, const Coordd start, const Coordd c, const Coordd end, bool cw) { const auto r = (start - c).mag(); @@ -258,7 +260,7 @@ static void pdf_arc(PoDoFo::PdfPainter &painter, const Coordd start, const Coord while (std::abs(e) > 1e-6) { const auto d = (cw) ? std::max(e, da) : std::min(e, da); const auto a = a0 + d; - pdf_arc_segment(painter, c, r, a0, a); + pdf_arc_segment(path, c, r, a0, a); a0 = a; e = a1 - a0; } @@ -278,29 +280,29 @@ void CanvasPDF::draw_polygon(const Polygon &ipoly, bool tr) it_next = ipoly.vertices.cbegin(); } if (first) { - painter.MoveTo(to_pt(p.x), to_pt(p.y)); + path.MoveTo(to_pt(p.x), to_pt(p.y)); } if (it->type == Polygon::Vertex::Type::LINE) { if (!first) { - painter.LineTo(to_pt(p.x), to_pt(p.y)); + path.AddLineTo(to_pt(p.x), to_pt(p.y)); } } else if (it->type == Polygon::Vertex::Type::ARC) { Coordd end = it_next->position; Coordd c = project_onto_perp_bisector(end, it->position, it->arc_center); if (!first) - painter.LineTo(to_pt(p.x), to_pt(p.y)); + path.AddLineTo(to_pt(p.x), to_pt(p.y)); if (tr) { c = transform.transform(c); end = transform.transform(end); } - pdf_arc(painter, p, c, end, it->arc_reverse); + pdf_arc(path, p, c, end, it->arc_reverse); } first = false; } - painter.ClosePath(); + path.Close(); } void CanvasPDF::request_push() diff --git a/src/export_pdf/canvas_pdf.hpp b/src/export_pdf/canvas_pdf.hpp index ff301308..2bd143db 100644 --- a/src/export_pdf/canvas_pdf.hpp +++ b/src/export_pdf/canvas_pdf.hpp @@ -27,7 +27,7 @@ class CanvasPDF : public Canvas { PoDoFo::PdfPainter &painter; PoDoFo::PdfFont &font; const PDFExportSettings &settings; - const PoDoFo::PdfFontMetrics *metrics; + const PoDoFo::PdfFontMetrics &metrics; void img_line(const Coordi &p0, const Coordi &p1, const uint64_t width, int layer, bool tr) override; void img_polygon(const class Polygon &poly, bool tr) override; void img_draw_text(const Coordf &p, float size, const std::string &rtext, int angle, bool flip, TextOrigin origin, @@ -37,5 +37,6 @@ class CanvasPDF : public Canvas { bool pdf_layer_visible(int l) const; void draw_polygon(const Polygon &ipoly, bool tr); Color get_pdf_layer_color(int layer) const; + PoDoFo::PdfPainterPath path; }; } // namespace horizon diff --git a/src/export_pdf/export_pdf.cpp b/src/export_pdf/export_pdf.cpp index cf8514fa..aa361bba 100644 --- a/src/export_pdf/export_pdf.cpp +++ b/src/export_pdf/export_pdf.cpp @@ -7,6 +7,7 @@ #include "schematic/iinstancce_mapping_provider.hpp" #include "util/bbox_accumulator.hpp" #include "pool/part.hpp" +#include namespace horizon { @@ -53,17 +54,13 @@ class MyInstanceMappingProvider : public IInstanceMappingProvider { UUIDVec instance_path; }; -#if PODOFO_VERSION_MAJOR != 0 || PODOFO_VERSION_MINOR != 9 || PODOFO_VERSION_PATCH != 5 -#define HAVE_OUTLINE -#endif - using Callback = std::function; class PDFExporter { public: PDFExporter(const class PDFExportSettings &settings, Callback callback) - : document(settings.output_filename.c_str()), font(document.CreateFont("Helvetica")), - canvas(painter, *font, settings), cb(callback) + : document(), font(load_font()), canvas(painter, font, settings), cb(callback), + filename(settings.output_filename.c_str()) { canvas.use_layer_colors = false; } @@ -71,55 +68,62 @@ class PDFExporter { void export_pdf(const class Schematic &sch) { cb("Initializing", 0); - auto info = document.GetInfo(); - info->SetCreator("Horizon EDA"); - info->SetProducer("Horizon EDA"); + document.GetMetadata().SetCreator(PoDoFo::PdfString("Horizon EDA")); + document.GetMetadata().SetProducer(PoDoFo::PdfString("Horizon EDA")); if (sch.block->project_meta.count("author")) { - info->SetAuthor(sch.block->project_meta.at("author")); + document.GetMetadata().SetAuthor(PoDoFo::PdfString(sch.block->project_meta.at("author"))); } std::string title = "Schematic"; if (sch.block->project_meta.count("project_title")) { title = sch.block->project_meta.at("project_title"); } - info->SetTitle(title); + document.GetMetadata().SetTitle(PoDoFo::PdfString(title)); MyInstanceMappingProvider prv(sch); -#ifdef HAVE_OUTLINE - outlines = document.GetOutlines(); - PoDoFo::PdfOutlineItem *proot = nullptr; -#else - PoDoFo::PdfOutlineItem *proot = nullptr; -#endif + outlines = &document.GetOrCreateOutlines(); + + export_schematic(sch, {}, prv, nullptr); - export_schematic(sch, {}, prv, proot); for (auto &[path, number, rect] : annotations) { - auto page = document.GetPage(number); - auto annot = page->CreateAnnotation(PoDoFo::ePdfAnnotation_Link, rect); - annot->SetBorderStyle(0, 0, 0); - annot->SetDestination(first_pages.at(path)); + auto &page = document.GetPages().GetPageAt(number); + auto &annot = page.GetAnnotations().CreateAnnot(rect); + annot.SetBorderStyle(0, 0, 0); + annot.SetDestination(first_pages.at(path)); } for (auto &[url, number, rect] : datasheet_annotations) { - auto page = document.GetPage(number); - auto annot = page->CreateAnnotation(PoDoFo::ePdfAnnotation_Link, rect); - annot->SetBorderStyle(0, 0, 0); + auto &page = document.GetPages().GetPageAt(number); + auto &annot = page.GetAnnotations().CreateAnnot(rect); + annot.SetBorderStyle(0, 0, 0); - PoDoFo::PdfAction action(PoDoFo::ePdfAction_URI, &document); - action.SetURI(PoDoFo::PdfString(url)); - annot->SetAction(action); + auto action = std::make_shared(document, PoDoFo::PdfActionType::URI); + + action->SetURI(PoDoFo::PdfString(url)); + annot.SetAction(action); } - document.Close(); + + document.Save(filename); } private: - PoDoFo::PdfStreamedDocument document; + PoDoFo::PdfMemDocument document; PoDoFo::PdfPainter painter; - PoDoFo::PdfFont *font = nullptr; - std::map first_pages; - std::vector> annotations; - std::vector> datasheet_annotations; + PoDoFo::PdfFont &load_font() + { + auto bytes = Gio::Resource::lookup_data_global("/org/horizon-eda/horizon/DejaVuSans.ttf"); + gsize size; + auto data = bytes->get_data(size); + PoDoFo::bufferview buffer{reinterpret_cast(data), size}; + return document.GetFonts().GetOrCreateFontFromBuffer(buffer); + } + PoDoFo::PdfFont &font; PoDoFo::PdfOutlines *outlines = nullptr; + std::map> first_pages; + std::vector> annotations; + std::vector> datasheet_annotations; + CanvasPDF canvas; Callback cb; + std::basic_string_view filename; void export_schematic(const Schematic &sch, const UUIDVec &path, MyInstanceMappingProvider &prv, PoDoFo::PdfOutlineItem *parent) @@ -129,24 +133,28 @@ class PDFExporter { prv.set_instance_path(path); Schematic my_sch = sch; my_sch.expand(false, &prv); - bool first = true; auto sheets = my_sch.get_sheets_sorted(); + bool first = true; + for (const auto sheet : sheets) { + const auto idx = prv.get_sheet_index_for_path(sheet->uuid, path); const auto progress = (double)idx / prv.get_sheet_total(); cb("Exporting sheet " + format_m_of_n(idx, prv.get_sheet_total()), progress); - auto page = - document.CreatePage(PoDoFo::PdfRect(0, 0, to_pt(sheet->frame.width), to_pt(sheet->frame.height))); - painter.SetPage(page); - painter.SetLineCapStyle(PoDoFo::ePdfLineCapStyle_Round); - painter.SetFont(font); - painter.SetColor(0, 0, 0); - painter.SetTextRenderingMode(PoDoFo::ePdfTextRenderingMode_Invisible); + auto &page = document.GetPages().CreatePage( + PoDoFo::Rect(0, 0, to_pt(sheet->frame.width), to_pt(sheet->frame.height))); + painter.SetCanvas(page); + + painter.GraphicsState.SetLineCapStyle(PoDoFo::PdfLineCapStyle::Round); + painter.GraphicsState.SetFillColor(PoDoFo::PdfColor(0, 0, 0)); + painter.TextState.SetFont(font, 10); + painter.TextState.SetRenderingMode(PoDoFo::PdfTextRenderingMode::Invisible); for (const auto &[uu, pic] : sheet->pictures) { if (!pic.on_top) render_picture(document, painter, pic); } + for (const auto &[uu_sym, sym] : sheet->block_symbols) { for (const auto &[uu, pic] : sym.symbol.pictures) { if (!pic.on_top) @@ -167,7 +175,7 @@ class PDFExporter { } } - auto dest = PoDoFo::PdfDestination(page); + auto dest = std::make_shared(page); if (first) { first_pages.emplace(path, dest); first = false; @@ -187,10 +195,10 @@ class PDFExporter { acc.accumulate(c); } const auto [a, b] = acc.get(); - PoDoFo::PdfRect rect(to_pt(a.x), to_pt(a.y), to_pt(b.x - a.x), to_pt(b.y - a.y)); + PoDoFo::Rect rect(to_pt(a.x), to_pt(a.y), to_pt(b.x - a.x), to_pt(b.y - a.y)); annotations.emplace_back( uuid_vec_append(path, sheet->block_symbols.at(ir.uuid).block_instance->uuid), - page->GetPageNumber() - 1, rect); + page.GetPageNumber() - 1, rect); } } else if (ir.type == ObjectType::SCHEMATIC_SYMBOL) { @@ -203,35 +211,29 @@ class PDFExporter { acc.accumulate(c); } const auto [a, b] = acc.get(); - PoDoFo::PdfRect rect(to_pt(a.x), to_pt(a.y), to_pt(b.x - a.x), to_pt(b.y - a.y)); + PoDoFo::Rect rect(to_pt(a.x), to_pt(a.y), to_pt(b.x - a.x), to_pt(b.y - a.y)); datasheet_annotations.emplace_back(sym.component->part->get_datasheet(), - page->GetPageNumber() - 1, rect); + page.GetPageNumber() - 1, rect); } } } } } - painter.FinishPage(); + painter.FinishDrawing(); -#ifdef HAVE_OUTLINE PoDoFo::PdfOutlineItem *sheet_node; if (parent) { - sheet_node = parent->CreateChild(sheet->name, dest); + sheet_node = parent->CreateChild(PoDoFo::PdfString(sheet->name), dest); } else { - sheet_node = outlines->CreateRoot(sheet->name); + sheet_node = outlines->CreateRoot(PoDoFo::PdfString(sheet->name)); sheet_node->SetDestination(dest); } -#endif for (auto sym : sheet->get_block_symbols_sorted()) { -#ifdef HAVE_OUTLINE - auto sym_node = sheet_node->CreateChild(sym->block_instance->refdes, dest); - sym_node->SetTextFormat(PoDoFo::ePdfOutlineFormat_Italic); -#else - PoDoFo::PdfOutlineItem *sym_node = nullptr; -#endif + auto sym_node = sheet_node->CreateChild(PoDoFo::PdfString(sym->block_instance->refdes), dest); + sym_node->SetTextFormat(PoDoFo::PdfOutlineFormat::Italic); export_schematic(*sym->schematic, uuid_vec_append(path, sym->block_instance->uuid), prv, sym_node); } } diff --git a/src/export_pdf/export_pdf_board.cpp b/src/export_pdf/export_pdf_board.cpp index cc1a0062..d7c8ea0d 100644 --- a/src/export_pdf/export_pdf_board.cpp +++ b/src/export_pdf/export_pdf_board.cpp @@ -18,15 +18,13 @@ void export_pdf(const class Board &brd, const class PDFExportSettings &settings, cb = &cb_nop; cb("Initializing", 0); - PoDoFo::PdfStreamedDocument document(settings.output_filename.c_str()); + PoDoFo::PdfMemDocument document; PoDoFo::PdfPainter painter; painter.SetPrecision(9); - auto info = document.GetInfo(); - info->SetCreator("horizon EDA"); - info->SetProducer("horizon EDA"); + document.GetMetadata().SetCreator(PoDoFo::PdfString("Horizon EDA")); + document.GetMetadata().SetProducer(PoDoFo::PdfString("Horizon EDA")); - - auto font = document.CreateFont("Helvetica"); + auto font = document.GetFonts().SearchFont("Helvetica"); PDFExportSettings my_settings(settings); my_settings.include_text = false; // need to work out text placement @@ -38,19 +36,19 @@ void export_pdf(const class Board &brd, const class PDFExportSettings &settings, auto width = bbox.second.x - bbox.first.x + border_width * 2; auto height = bbox.second.y - bbox.first.y + border_width * 2; - auto page = document.CreatePage(PoDoFo::PdfRect(0, 0, to_pt(width), to_pt(height))); - painter.SetPage(page); - painter.SetLineCapStyle(PoDoFo::ePdfLineCapStyle_Round); - painter.SetFont(font); - painter.SetColor(0, 0, 0); - painter.SetTextRenderingMode(PoDoFo::ePdfTextRenderingMode_Invisible); + auto &page = document.GetPages().CreatePage(PoDoFo::Rect(0, 0, to_pt(width), to_pt(height))); + painter.SetCanvas(page); + painter.GraphicsState.SetLineCapStyle(PoDoFo::PdfLineCapStyle::Round); + painter.GraphicsState.SetFillColor(PoDoFo::PdfColor(0, 0, 0)); + painter.TextState.SetFont(*font, 10); + painter.TextState.SetRenderingMode(PoDoFo::PdfTextRenderingMode::Invisible); if (settings.mirror) { - painter.SetTransformationMatrix(-1, 0, 0, 1, to_pt(bbox.second.x + border_width), - to_pt(-bbox.first.y + border_width)); + painter.GraphicsState.SetCurrentMatrix(PoDoFo::Matrix::FromCoefficients( + -1, 0, 0, 1, to_pt(bbox.second.x + border_width), to_pt(-bbox.first.y + border_width))); } else { - painter.SetTransformationMatrix(1, 0, 0, 1, to_pt(-bbox.first.x + border_width), - to_pt(-bbox.first.y + border_width)); + painter.GraphicsState.SetCurrentMatrix(PoDoFo::Matrix::FromCoefficients( + 1, 0, 0, 1, to_pt(-bbox.first.x + border_width), to_pt(-bbox.first.y + border_width))); } ca.layer_filter = true; ca.use_layer_colors = true; @@ -87,8 +85,8 @@ void export_pdf(const class Board &brd, const class PDFExportSettings &settings, render_picture(document, painter, pic); } - painter.FinishPage(); + painter.FinishDrawing(); - document.Close(); + document.Save(settings.output_filename.c_str()); } } // namespace horizon diff --git a/src/export_pdf/export_pdf_util.cpp b/src/export_pdf/export_pdf_util.cpp index 09687ed2..50aeeaf4 100644 --- a/src/export_pdf/export_pdf_util.cpp +++ b/src/export_pdf/export_pdf_util.cpp @@ -4,51 +4,23 @@ namespace horizon { +#define CONVERSION_CONSTANT 0.002834645669291339 + void render_picture(PoDoFo::PdfDocument &doc, PoDoFo::PdfPainter &painter, const Picture &pic, const Placement &tr) { - PoDoFo::PdfImage img(&doc); + auto img = doc.CreateImage(); Placement pl = tr; pl.accumulate(pic.placement); - { - std::vector picdata; - picdata.reserve(pic.data->width * pic.data->height * 3); - for (const auto x : pic.data->data) { - picdata.push_back((x)&0xff); - picdata.push_back((x >> 8) & 0xff); - picdata.push_back((x >> 16) & 0xff); - } - - PoDoFo::PdfMemoryInputStream stream(picdata.data(), picdata.size()); - img.SetImageColorSpace(PoDoFo::ePdfColorSpace_DeviceRGB); - img.SetImageData(pic.data->width, pic.data->height, 8, &stream); - } - - PoDoFo::PdfImage img_mask(&doc); - { - std::vector picdata; - picdata.reserve(pic.data->width * pic.data->height); - for (const auto x : pic.data->data) { - picdata.push_back(((x >> 24) & 0xff) * pic.opacity); - } - - PoDoFo::PdfMemoryInputStream stream(picdata.data(), picdata.size()); - img_mask.SetImageColorSpace(PoDoFo::ePdfColorSpace_DeviceGray); - img_mask.SetImageData(pic.data->width, pic.data->height, 8, &stream); - } - - img.SetImageSoftmask(&img_mask); - painter.Save(); const auto fangle = pl.get_angle_rad(); - - painter.SetTransformationMatrix(cos(fangle), sin(fangle), -sin(fangle), cos(fangle), to_pt((double)pl.shift.x), - to_pt((double)pl.shift.y)); + painter.GraphicsState.SetCurrentMatrix(PoDoFo::Matrix::FromCoefficients( + cos(fangle), sin(fangle), -sin(fangle), cos(fangle), to_pt((double)pl.shift.x), to_pt((double)pl.shift.y))); const int64_t w = pic.data->width * pic.px_size; const int64_t h = pic.data->height * pic.px_size; const auto p = Coordd(w, h) / -2; const double sz = pic.px_size / (1e3 / CONVERSION_CONSTANT); - painter.DrawImage(to_pt(p.x), to_pt(p.y), &img, sz, sz); + painter.DrawImage(*img, to_pt(p.x), to_pt(p.y), sz, sz); painter.Restore(); } } // namespace horizon diff --git a/src/export_pdf/legacy/canvas_pdf.cpp b/src/export_pdf/legacy/canvas_pdf.cpp new file mode 100644 index 00000000..5221986f --- /dev/null +++ b/src/export_pdf/legacy/canvas_pdf.cpp @@ -0,0 +1,309 @@ +#include "canvas_pdf.hpp" +#include "common/pdf_export_settings.hpp" +#include "util/str_util.hpp" +#include "util/geom_util.hpp" +#include "common/polygon.hpp" +#include "common/hole.hpp" +#include "canvas/appearance.hpp" +#include "board/plane.hpp" + +namespace horizon { + +double to_pt(double x_nm) +{ + return x_nm * .000002834645669291339; +} + +CanvasPDF::CanvasPDF(PoDoFo::PdfPainter &p, PoDoFo::PdfFont &f, const PDFExportSettings &s) + : Canvas::Canvas(), painter(p), font(f), settings(s), metrics(font.GetFontMetrics()) +{ + img_mode = true; + Appearance apperarance; + layer_colors = apperarance.layer_colors; +} + +bool CanvasPDF::pdf_layer_visible(int l) const +{ + if (layer_filter == false) + return true; + else + return l == current_layer; +} + +Color CanvasPDF::get_pdf_layer_color(int layer) const +{ + if (use_layer_colors) + return get_layer_color(layer); + else + return Color(0, 0, 0); +} + +void CanvasPDF::img_line(const Coordi &p0, const Coordi &p1, const uint64_t width, int layer, bool tr) +{ + if (!pdf_layer_visible(layer)) + return; + painter.Save(); + auto w = std::max(width, settings.min_line_width); + painter.SetStrokeWidth(to_pt(w)); + Coordi rp0 = p0; + Coordi rp1 = p1; + if (tr) { + rp0 = transform.transform(p0); + rp1 = transform.transform(p1); + } + auto color = get_pdf_layer_color(layer); + painter.SetStrokingColor(color.r, color.g, color.b); + painter.DrawLine(to_pt(rp0.x), to_pt(rp0.y), to_pt(rp1.x), to_pt(rp1.y)); + painter.Restore(); +} + +void CanvasPDF::img_draw_text(const Coordf &p, float size, const std::string &rtext, int angle, bool flip, + TextOrigin origin, int layer, uint64_t width, TextData::Font tfont, bool center, + bool mirror) +{ + if (!settings.include_text) + return; + if (!pdf_layer_visible(layer)) + return; + + angle = wrap_angle(angle); + bool backwards = (angle > 16384) && (angle <= 49152); + float yshift = 0; + switch (origin) { + case TextOrigin::CENTER: + yshift = 0; + break; + default: + yshift = size / 2; + } + + std::string text(rtext); + trim(text); + std::stringstream ss(text); + std::string line; + unsigned int n_lines = std::count(text.begin(), text.end(), '\n'); + unsigned int i_line = 0; + float lineskip = size * 1.35 + width; + if (mirror) { + lineskip *= -1; + } + font.SetFontSize(to_pt(size) * 1.6); + while (std::getline(ss, line, '\n')) { + line = TextData::trim(line); + int64_t line_width = metrics->StringWidthMM(line.c_str()) * 1000; + + Placement tf; + tf.shift.x = p.x; + tf.shift.y = p.y; + + Placement tr; + if (flip) + tr.set_angle(32768 - angle); + else + tr.set_angle(angle); + if (backwards ^ mirror) + tf.shift += tr.transform(Coordi(0, -lineskip * (n_lines - i_line))); + else + tf.shift += tr.transform(Coordi(0, -lineskip * i_line)); + + int xshift = 0; + if (backwards) { + tf.set_angle(angle - 32768); + xshift = -line_width; + } + else { + tf.set_angle(angle); + } + tf.mirror = flip; + if (center) { + if (backwards) { + xshift += line_width / 2; + } + else { + xshift -= line_width / 2; + } + } + double fangle = tf.get_angle_rad(); + painter.Save(); + Coordi p0(xshift, yshift); + Coordi pt = tf.transform(p0); + + painter.SetTransformationMatrix(cos(fangle), sin(fangle), -sin(fangle), cos(fangle), to_pt(pt.x), to_pt(pt.y)); + PoDoFo::PdfString pstr(reinterpret_cast(line.c_str())); + painter.DrawText(0, to_pt(size) / -2, pstr); + painter.Restore(); + + i_line++; + } +} + +void CanvasPDF::img_polygon(const Polygon &ipoly, bool tr) +{ + if (!pdf_layer_visible(ipoly.layer)) + return; + painter.Save(); + auto color = get_pdf_layer_color(ipoly.layer); + painter.SetColor(color.r, color.g, color.b); + painter.SetStrokingColor(color.r, color.g, color.b); + painter.SetStrokeWidth(to_pt(settings.min_line_width)); + if (ipoly.usage == nullptr) { // regular patch + draw_polygon(ipoly, tr); + if (fill) + painter.Fill(); + else + painter.Stroke(); + } + else if (auto plane = dynamic_cast(ipoly.usage.ptr)) { + for (const auto &frag : plane->fragments) { + for (const auto &path : frag.paths) { + bool first = true; + for (const auto &it : path) { + Coordi p(it.X, it.Y); + if (tr) + p = transform.transform(p); + if (first) + painter.MoveTo(to_pt(p.x), to_pt(p.y)); + else + painter.LineTo(to_pt(p.x), to_pt(p.y)); + first = false; + } + painter.ClosePath(); + } + } + if (fill) + painter.Fill(true); + else + painter.Stroke(); + } + painter.Restore(); +} + +void CanvasPDF::img_hole(const Hole &hole) +{ + if (!pdf_layer_visible(PDFExportSettings::HOLES_LAYER)) + return; + painter.Save(); + + auto color = get_pdf_layer_color(PDFExportSettings::HOLES_LAYER); + painter.SetColor(color.r, color.g, color.b); + painter.SetStrokingColor(color.r, color.g, color.b); + painter.SetStrokeWidth(to_pt(settings.min_line_width)); + + auto hole2 = hole; + if (settings.set_holes_size) { + hole2.diameter = settings.holes_diameter; + } + draw_polygon(hole2.to_polygon(), true); + if (fill) + painter.Fill(true); + else + painter.Stroke(); + painter.Restore(); +} + +// c is the arc center. +// angles must be in radians, c and r must be in mm. +// See "How to determine the control points of a Bézier curve that approximates a +// small circular arc" by Richard ADeVeneza, Nov 2004 +// https://www.tinaja.com/glib/bezcirc2.pdf +static Coordd pdf_arc_segment(PoDoFo::PdfPainter &painter, const Coordd c, const double r, double a0, double a1) +{ + const auto da = a0 - a1; + assert(da != 0); + assert(std::abs(da) <= M_PI / 2 + 1e-6); + + // Shift to bisect at x axis + const auto theta = (a0 + a1) / 2; + const auto phi = da / 2; + + // Compute points of unit circle for given delta angle + const auto p0 = Coordd(cos(phi), sin(phi)); + const auto p1 = Coordd((4 - p0.x) / 3, (1 - p0.x) * (3 - p0.x) / (3 * p0.y)); + const auto p2 = Coordd(p1.x, -p1.y); + const auto p3 = Coordd(p0.x, -p0.y); + + // Transform points + const auto c1 = p1.rotate(theta) * r + c; + const auto c2 = p2.rotate(theta) * r + c; + const auto c3 = p3.rotate(theta) * r + c; + + painter.CubicBezierTo(to_pt(c1.x), to_pt(c1.y), to_pt(c2.x), to_pt(c2.y), to_pt(c3.x), to_pt(c3.y)); + return c3; // end point +} + +static void pdf_arc(PoDoFo::PdfPainter &painter, const Coordd start, const Coordd c, const Coordd end, bool cw) +{ + const auto r = (start - c).mag(); + + // Get angles relative to the x axis + double a0 = (start - c).angle(); + double a1 = (end - c).angle(); + + // Circle or large arc + if (cw && a0 <= a1) { + a0 += 2 * M_PI; + } + else if (!cw && a0 >= a1) { + a0 -= 2 * M_PI; + } + + const double da = (cw) ? -M_PI / 2 : M_PI / 2; + if (cw) { + assert(a0 > a1); + } + else { + assert(a0 < a1); + } + double e = a1 - a0; + while (std::abs(e) > 1e-6) { + const auto d = (cw) ? std::max(e, da) : std::min(e, da); + const auto a = a0 + d; + pdf_arc_segment(painter, c, r, a0, a); + a0 = a; + e = a1 - a0; + } +} + + +void CanvasPDF::draw_polygon(const Polygon &ipoly, bool tr) +{ + assert(ipoly.usage == nullptr); + bool first = true; + for (auto it = ipoly.vertices.cbegin(); it < ipoly.vertices.cend(); it++) { + Coordd p = it->position; + if (tr) + p = transform.transform(p); + auto it_next = it + 1; + if (it_next == ipoly.vertices.cend()) { + it_next = ipoly.vertices.cbegin(); + } + if (first) { + painter.MoveTo(to_pt(p.x), to_pt(p.y)); + } + if (it->type == Polygon::Vertex::Type::LINE) { + if (!first) { + painter.LineTo(to_pt(p.x), to_pt(p.y)); + } + } + else if (it->type == Polygon::Vertex::Type::ARC) { + Coordd end = it_next->position; + Coordd c = project_onto_perp_bisector(end, it->position, it->arc_center); + if (!first) + painter.LineTo(to_pt(p.x), to_pt(p.y)); + + if (tr) { + c = transform.transform(c); + end = transform.transform(end); + } + pdf_arc(painter, p, c, end, it->arc_reverse); + } + first = false; + } + + painter.ClosePath(); +} + +void CanvasPDF::request_push() +{ +} +} // namespace horizon diff --git a/src/export_pdf/legacy/canvas_pdf.hpp b/src/export_pdf/legacy/canvas_pdf.hpp new file mode 100644 index 00000000..ff301308 --- /dev/null +++ b/src/export_pdf/legacy/canvas_pdf.hpp @@ -0,0 +1,41 @@ +#pragma once +#include "canvas/canvas.hpp" +#include "util/podofo_inc.hpp" + +namespace horizon { + +double to_pt(double x_nm); + +class CanvasPDF : public Canvas { +public: + CanvasPDF(PoDoFo::PdfPainter &painter, PoDoFo::PdfFont &font, const class PDFExportSettings &settings); + void push() override + { + } + + void request_push() override; + bool layer_filter = false; + int current_layer = 0; + bool fill = true; + bool use_layer_colors = false; + const auto &get_selectables() const + { + return selectables; + } + +private: + PoDoFo::PdfPainter &painter; + PoDoFo::PdfFont &font; + const PDFExportSettings &settings; + const PoDoFo::PdfFontMetrics *metrics; + void img_line(const Coordi &p0, const Coordi &p1, const uint64_t width, int layer, bool tr) override; + void img_polygon(const class Polygon &poly, bool tr) override; + void img_draw_text(const Coordf &p, float size, const std::string &rtext, int angle, bool flip, TextOrigin origin, + int layer = 10000, uint64_t width = 0, TextData::Font font = TextData::Font::SIMPLEX, + bool center = false, bool mirror = false) override; + void img_hole(const Hole &hole) override; + bool pdf_layer_visible(int l) const; + void draw_polygon(const Polygon &ipoly, bool tr); + Color get_pdf_layer_color(int layer) const; +}; +} // namespace horizon diff --git a/src/export_pdf/legacy/export_pdf.cpp b/src/export_pdf/legacy/export_pdf.cpp new file mode 100644 index 00000000..cf8514fa --- /dev/null +++ b/src/export_pdf/legacy/export_pdf.cpp @@ -0,0 +1,248 @@ +#include "export_pdf.hpp" +#include "canvas_pdf.hpp" +#include "util/podofo_inc.hpp" +#include "util/util.hpp" +#include "schematic/schematic.hpp" +#include "export_pdf_util.hpp" +#include "schematic/iinstancce_mapping_provider.hpp" +#include "util/bbox_accumulator.hpp" +#include "pool/part.hpp" + +namespace horizon { + +static void cb_nop(std::string, double) +{ +} + +class MyInstanceMappingProvider : public IInstanceMappingProvider { +public: + MyInstanceMappingProvider(const Schematic &sch) : top(sch) + { + } + void set_instance_path(const UUIDVec &p) + { + instance_path = p; + } + + const class BlockInstanceMapping *get_block_instance_mapping() const override + { + if (instance_path.size()) + return &top.block->block_instance_mappings.at(instance_path); + else + return nullptr; + } + + unsigned int get_sheet_index_for_path(const class UUID &sheet, const UUIDVec &path) const + { + return top.sheet_mapping.sheet_numbers.at(uuid_vec_append(path, sheet)); + } + + unsigned int get_sheet_index(const class UUID &sheet) const override + { + return get_sheet_index_for_path(sheet, instance_path); + } + + unsigned int get_sheet_total() const override + { + return top.sheet_mapping.sheet_total; + } + + +private: + const Schematic ⊤ + UUIDVec instance_path; +}; + +#if PODOFO_VERSION_MAJOR != 0 || PODOFO_VERSION_MINOR != 9 || PODOFO_VERSION_PATCH != 5 +#define HAVE_OUTLINE +#endif + +using Callback = std::function; + +class PDFExporter { +public: + PDFExporter(const class PDFExportSettings &settings, Callback callback) + : document(settings.output_filename.c_str()), font(document.CreateFont("Helvetica")), + canvas(painter, *font, settings), cb(callback) + { + canvas.use_layer_colors = false; + } + + void export_pdf(const class Schematic &sch) + { + cb("Initializing", 0); + auto info = document.GetInfo(); + info->SetCreator("Horizon EDA"); + info->SetProducer("Horizon EDA"); + if (sch.block->project_meta.count("author")) { + info->SetAuthor(sch.block->project_meta.at("author")); + } + std::string title = "Schematic"; + if (sch.block->project_meta.count("project_title")) { + title = sch.block->project_meta.at("project_title"); + } + info->SetTitle(title); + MyInstanceMappingProvider prv(sch); + +#ifdef HAVE_OUTLINE + outlines = document.GetOutlines(); + PoDoFo::PdfOutlineItem *proot = nullptr; +#else + PoDoFo::PdfOutlineItem *proot = nullptr; +#endif + + export_schematic(sch, {}, prv, proot); + for (auto &[path, number, rect] : annotations) { + auto page = document.GetPage(number); + auto annot = page->CreateAnnotation(PoDoFo::ePdfAnnotation_Link, rect); + annot->SetBorderStyle(0, 0, 0); + annot->SetDestination(first_pages.at(path)); + } + for (auto &[url, number, rect] : datasheet_annotations) { + auto page = document.GetPage(number); + auto annot = page->CreateAnnotation(PoDoFo::ePdfAnnotation_Link, rect); + annot->SetBorderStyle(0, 0, 0); + + PoDoFo::PdfAction action(PoDoFo::ePdfAction_URI, &document); + action.SetURI(PoDoFo::PdfString(url)); + annot->SetAction(action); + } + document.Close(); + } + +private: + PoDoFo::PdfStreamedDocument document; + PoDoFo::PdfPainter painter; + PoDoFo::PdfFont *font = nullptr; + std::map first_pages; + std::vector> annotations; + std::vector> datasheet_annotations; + PoDoFo::PdfOutlines *outlines = nullptr; + CanvasPDF canvas; + Callback cb; + + void export_schematic(const Schematic &sch, const UUIDVec &path, MyInstanceMappingProvider &prv, + PoDoFo::PdfOutlineItem *parent) + { + if (Block::instance_path_too_long(path, __FUNCTION__)) + return; + prv.set_instance_path(path); + Schematic my_sch = sch; + my_sch.expand(false, &prv); + bool first = true; + auto sheets = my_sch.get_sheets_sorted(); + for (const auto sheet : sheets) { + const auto idx = prv.get_sheet_index_for_path(sheet->uuid, path); + const auto progress = (double)idx / prv.get_sheet_total(); + cb("Exporting sheet " + format_m_of_n(idx, prv.get_sheet_total()), progress); + auto page = + document.CreatePage(PoDoFo::PdfRect(0, 0, to_pt(sheet->frame.width), to_pt(sheet->frame.height))); + painter.SetPage(page); + painter.SetLineCapStyle(PoDoFo::ePdfLineCapStyle_Round); + painter.SetFont(font); + painter.SetColor(0, 0, 0); + painter.SetTextRenderingMode(PoDoFo::ePdfTextRenderingMode_Invisible); + + for (const auto &[uu, pic] : sheet->pictures) { + if (!pic.on_top) + render_picture(document, painter, pic); + } + for (const auto &[uu_sym, sym] : sheet->block_symbols) { + for (const auto &[uu, pic] : sym.symbol.pictures) { + if (!pic.on_top) + render_picture(document, painter, pic, sym.placement); + } + } + + canvas.update(*sheet); + + for (const auto &[uu, pic] : sheet->pictures) { + if (pic.on_top) + render_picture(document, painter, pic); + } + for (const auto &[uu_sym, sym] : sheet->block_symbols) { + for (const auto &[uu, pic] : sym.symbol.pictures) { + if (pic.on_top) + render_picture(document, painter, pic, sym.placement); + } + } + + auto dest = PoDoFo::PdfDestination(page); + if (first) { + first_pages.emplace(path, dest); + first = false; + } + + { + const auto &items = canvas.get_selectables().get_items(); + const auto &items_ref = canvas.get_selectables().get_items_ref(); + const auto n = items.size(); + for (size_t i = 0; i < n; i++) { + const auto &it = items.at(i); + const auto &ir = items_ref.at(i); + if (ir.type == ObjectType::SCHEMATIC_BLOCK_SYMBOL) { + if (it.is_box()) { + BBoxAccumulator acc; + for (const auto &c : it.get_corners()) { + acc.accumulate(c); + } + const auto [a, b] = acc.get(); + PoDoFo::PdfRect rect(to_pt(a.x), to_pt(a.y), to_pt(b.x - a.x), to_pt(b.y - a.y)); + annotations.emplace_back( + uuid_vec_append(path, sheet->block_symbols.at(ir.uuid).block_instance->uuid), + page->GetPageNumber() - 1, rect); + } + } + else if (ir.type == ObjectType::SCHEMATIC_SYMBOL) { + if (it.is_box()) { + const auto &sym = sheet->symbols.at(ir.uuid); + if (sym.component->part && sym.component->part->get_datasheet().size()) { + BBoxAccumulator acc; + + for (const auto &c : it.get_corners()) { + acc.accumulate(c); + } + const auto [a, b] = acc.get(); + PoDoFo::PdfRect rect(to_pt(a.x), to_pt(a.y), to_pt(b.x - a.x), to_pt(b.y - a.y)); + datasheet_annotations.emplace_back(sym.component->part->get_datasheet(), + page->GetPageNumber() - 1, rect); + } + } + } + } + } + + painter.FinishPage(); + +#ifdef HAVE_OUTLINE + PoDoFo::PdfOutlineItem *sheet_node; + if (parent) { + sheet_node = parent->CreateChild(sheet->name, dest); + } + else { + sheet_node = outlines->CreateRoot(sheet->name); + sheet_node->SetDestination(dest); + } +#endif + + for (auto sym : sheet->get_block_symbols_sorted()) { +#ifdef HAVE_OUTLINE + auto sym_node = sheet_node->CreateChild(sym->block_instance->refdes, dest); + sym_node->SetTextFormat(PoDoFo::ePdfOutlineFormat_Italic); +#else + PoDoFo::PdfOutlineItem *sym_node = nullptr; +#endif + export_schematic(*sym->schematic, uuid_vec_append(path, sym->block_instance->uuid), prv, sym_node); + } + } + } +}; + +void export_pdf(const class Schematic &sch, const class PDFExportSettings &settings, Callback cb) +{ + if (!cb) + cb = &cb_nop; + PDFExporter ex(settings, cb); + ex.export_pdf(sch); +} +} // namespace horizon diff --git a/src/export_pdf/legacy/export_pdf.hpp b/src/export_pdf/legacy/export_pdf.hpp new file mode 100644 index 00000000..1d8fc99d --- /dev/null +++ b/src/export_pdf/legacy/export_pdf.hpp @@ -0,0 +1,8 @@ +#pragma once +#include +#include + +namespace horizon { +void export_pdf(const class Schematic &sch, const class PDFExportSettings &settings, + std::function cb = nullptr); +} diff --git a/src/export_pdf/legacy/export_pdf_board.cpp b/src/export_pdf/legacy/export_pdf_board.cpp new file mode 100644 index 00000000..cc1a0062 --- /dev/null +++ b/src/export_pdf/legacy/export_pdf_board.cpp @@ -0,0 +1,94 @@ +#include "export_pdf.hpp" +#include "canvas_pdf.hpp" +#include "util/podofo_inc.hpp" +#include "util/util.hpp" +#include "board/board.hpp" +#include "export_pdf_util.hpp" + +namespace horizon { + +static void cb_nop(std::string, double) +{ +} + +void export_pdf(const class Board &brd, const class PDFExportSettings &settings, + std::function cb) +{ + if (!cb) + cb = &cb_nop; + cb("Initializing", 0); + + PoDoFo::PdfStreamedDocument document(settings.output_filename.c_str()); + PoDoFo::PdfPainter painter; + painter.SetPrecision(9); + auto info = document.GetInfo(); + info->SetCreator("horizon EDA"); + info->SetProducer("horizon EDA"); + + + auto font = document.CreateFont("Helvetica"); + + PDFExportSettings my_settings(settings); + my_settings.include_text = false; // need to work out text placement + CanvasPDF ca(painter, *font, my_settings); + + cb("Exporting Board", 0); + int64_t border_width = 1_mm; + auto bbox = brd.get_bbox(); + auto width = bbox.second.x - bbox.first.x + border_width * 2; + auto height = bbox.second.y - bbox.first.y + border_width * 2; + + auto page = document.CreatePage(PoDoFo::PdfRect(0, 0, to_pt(width), to_pt(height))); + painter.SetPage(page); + painter.SetLineCapStyle(PoDoFo::ePdfLineCapStyle_Round); + painter.SetFont(font); + painter.SetColor(0, 0, 0); + painter.SetTextRenderingMode(PoDoFo::ePdfTextRenderingMode_Invisible); + if (settings.mirror) { + painter.SetTransformationMatrix(-1, 0, 0, 1, to_pt(bbox.second.x + border_width), + to_pt(-bbox.first.y + border_width)); + } + else { + painter.SetTransformationMatrix(1, 0, 0, 1, to_pt(-bbox.first.x + border_width), + to_pt(-bbox.first.y + border_width)); + } + ca.layer_filter = true; + ca.use_layer_colors = true; + + std::vector layers_sorted; + for (const auto &it : settings.layers) { + if (it.second.enabled) { + layers_sorted.push_back(it.first); + ca.set_layer_color(it.first, it.second.color); + } + } + std::sort(layers_sorted.begin(), layers_sorted.end(), + [&brd](const auto a, const auto b) { return brd.get_layer_position(a) < brd.get_layer_position(b); }); + if (settings.reverse_layers) + std::reverse(layers_sorted.begin(), layers_sorted.end()); + + for (const auto &[uu, pic] : brd.pictures) { + if (!pic.on_top) + render_picture(document, painter, pic); + } + + unsigned int i_layer = 0; + for (int layer : layers_sorted) { + ca.clear(); + ca.current_layer = layer; + ca.fill = settings.layers.at(layer).mode == PDFExportSettings::Layer::Mode::FILL; + cb("Exporting layer " + format_m_of_n(i_layer, layers_sorted.size()), ((double)i_layer) / layers_sorted.size()); + ca.update(brd); + i_layer++; + } + + for (const auto &[uu, pic] : brd.pictures) { + if (pic.on_top) + render_picture(document, painter, pic); + } + + painter.FinishPage(); + + document.Close(); +} +} // namespace horizon diff --git a/src/export_pdf/legacy/export_pdf_board.hpp b/src/export_pdf/legacy/export_pdf_board.hpp new file mode 100644 index 00000000..366ef53d --- /dev/null +++ b/src/export_pdf/legacy/export_pdf_board.hpp @@ -0,0 +1,7 @@ +#pragma once +#include + +namespace horizon { +void export_pdf(const class Board &brd, const class PDFExportSettings &settings, + std::function cb = nullptr); +} diff --git a/src/export_pdf/legacy/export_pdf_util.cpp b/src/export_pdf/legacy/export_pdf_util.cpp new file mode 100644 index 00000000..09687ed2 --- /dev/null +++ b/src/export_pdf/legacy/export_pdf_util.cpp @@ -0,0 +1,54 @@ +#include "canvas_pdf.hpp" +#include "common/picture.hpp" +#include "export_pdf_util.hpp" + +namespace horizon { + +void render_picture(PoDoFo::PdfDocument &doc, PoDoFo::PdfPainter &painter, const Picture &pic, const Placement &tr) +{ + PoDoFo::PdfImage img(&doc); + Placement pl = tr; + pl.accumulate(pic.placement); + + { + std::vector picdata; + picdata.reserve(pic.data->width * pic.data->height * 3); + for (const auto x : pic.data->data) { + picdata.push_back((x)&0xff); + picdata.push_back((x >> 8) & 0xff); + picdata.push_back((x >> 16) & 0xff); + } + + PoDoFo::PdfMemoryInputStream stream(picdata.data(), picdata.size()); + img.SetImageColorSpace(PoDoFo::ePdfColorSpace_DeviceRGB); + img.SetImageData(pic.data->width, pic.data->height, 8, &stream); + } + + PoDoFo::PdfImage img_mask(&doc); + { + std::vector picdata; + picdata.reserve(pic.data->width * pic.data->height); + for (const auto x : pic.data->data) { + picdata.push_back(((x >> 24) & 0xff) * pic.opacity); + } + + PoDoFo::PdfMemoryInputStream stream(picdata.data(), picdata.size()); + img_mask.SetImageColorSpace(PoDoFo::ePdfColorSpace_DeviceGray); + img_mask.SetImageData(pic.data->width, pic.data->height, 8, &stream); + } + + img.SetImageSoftmask(&img_mask); + + painter.Save(); + const auto fangle = pl.get_angle_rad(); + + painter.SetTransformationMatrix(cos(fangle), sin(fangle), -sin(fangle), cos(fangle), to_pt((double)pl.shift.x), + to_pt((double)pl.shift.y)); + const int64_t w = pic.data->width * pic.px_size; + const int64_t h = pic.data->height * pic.px_size; + const auto p = Coordd(w, h) / -2; + const double sz = pic.px_size / (1e3 / CONVERSION_CONSTANT); + painter.DrawImage(to_pt(p.x), to_pt(p.y), &img, sz, sz); + painter.Restore(); +} +} // namespace horizon diff --git a/src/export_pdf/legacy/export_pdf_util.hpp b/src/export_pdf/legacy/export_pdf_util.hpp new file mode 100644 index 00000000..a87580ef --- /dev/null +++ b/src/export_pdf/legacy/export_pdf_util.hpp @@ -0,0 +1,8 @@ +#pragma once +#include "util/podofo_inc.hpp" +#include "util/placement.hpp" + +namespace horizon { +void render_picture(PoDoFo::PdfDocument &doc, PoDoFo::PdfPainter &painter, const class Picture &pic, + const Placement &tr = Placement()); +}