summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorWerner Almesberger <werner@almesberger.net>2016-08-02 14:00:08 (GMT)
committerWerner Almesberger <werner@almesberger.net>2016-08-02 14:00:08 (GMT)
commit8745b40e3eb216fc5cfa7373fea3b4ae991f1c3e (patch)
treeeab2aa7ec1941bd3da99f1dbcf64eab04d34667a
downloadeeshow-8745b40e3eb216fc5cfa7373fea3b4ae991f1c3e.zip
eeshow-8745b40e3eb216fc5cfa7373fea3b4ae991f1c3e.tar.gz
eeshow-8745b40e3eb216fc5cfa7373fea3b4ae991f1c3e.tar.bz2
rename sch2fig to eeshow
-rw-r--r--DEMO25
-rw-r--r--Makefile91
-rw-r--r--TODO24
-rw-r--r--cro.c461
-rw-r--r--cro.h31
-rw-r--r--diff.c279
-rw-r--r--diff.h22
-rw-r--r--dwg.c463
-rw-r--r--dwg.h46
-rw-r--r--fig.c293
-rw-r--r--fig.h22
-rw-r--r--file.c76
-rw-r--r--file.h22
-rw-r--r--gfx.c120
-rw-r--r--gfx.h72
-rw-r--r--git-file.c371
-rw-r--r--git-file.h22
-rw-r--r--lib-parse.c268
-rw-r--r--lib-render.c396
-rw-r--r--lib.h124
-rw-r--r--main.c172
-rw-r--r--main.h31
-rw-r--r--misc.c114
-rw-r--r--misc.h51
-rw-r--r--neo900-template.fig15
-rw-r--r--record.c342
-rw-r--r--record.h58
-rw-r--r--sch-parse.c562
-rw-r--r--sch-render.c174
-rw-r--r--sch.h123
-rwxr-xr-xsch2pdf92
-rw-r--r--style.c31
-rw-r--r--style.h91
-rw-r--r--test.lib62
-rw-r--r--test.pro34
-rw-r--r--test.sch372
-rw-r--r--test/README24
-rwxr-xr-xtest/comp29
-rwxr-xr-xtest/genpng37
-rw-r--r--text.c158
-rw-r--r--text.h58
-rw-r--r--util.h52
42 files changed, 5910 insertions, 0 deletions
diff --git a/DEMO b/DEMO
new file mode 100644
index 0000000..dc307db
--- /dev/null
+++ b/DEMO
@@ -0,0 +1,25 @@
+# Prerequisites (depends on distribution)
+
+apt-get install libcairo2-dev
+apt-get install libgit2-dev
+apt-get install qiv
+
+# Get all the things we need
+
+git clone http://neo900.org/git/ee.git newdir
+cd newdir/hw
+git clone git://projects.qi-hardware.com/kicad-libs.git
+git clone git://projects.qi-hardware.com/eda-tools.git
+make -C eda-tools/eeshow
+
+# Generate PNG for old, new, and difference
+
+LIBS="neo900.lib kicad-libs/components/powered.lib"
+eda-tools/eeshow/eeshow $LIBS 6a9f71:neo900_SS_5.sch -- png -s 2 -o old.png
+eda-tools/eeshow/eeshow $LIBS neo900_SS_5.sch -- png -s 2 -o new.png
+eda-tools/eeshow/eeshow $LIBS 6a9f71:neo900_SS_5.sch -- \
+ diff -s 2 -o diff.png $LIBS neo900_SS_5.sch
+
+# View the result
+
+qiv -t diff.png old.png new.png
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..1fa2b28
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,91 @@
+#
+# Makefile - build eeshow
+#
+# Written 2016 by Werner Almesberger
+# Copyright 2016 by Werner Almesberger
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+
+NAME = eeshow
+OBJS = main.o sch-parse.o sch-render.o lib-parse.o lib-render.o \
+ file.o git-file.o \
+ style.o fig.o record.o cro.o diff.o gfx.o dwg.o text.o misc.o
+
+CFLAGS = -g -Wall -Wextra -Wno-unused-parameter -Wshadow \
+ -Wmissing-prototypes -Wmissing-declarations \
+ `pkg-config --cflags cairo` \
+ `pkg-config --cflags libgit2`
+LDLIBS = -lm \
+ `pkg-config --libs cairo` \
+ `pkg-config --libs libgit2`
+
+include ../common/Makefile.c-common
+
+.PHONY: test neo900 sch test testref png pngref diff view newref
+
+all:: $(NAME)
+
+$(NAME): $(OBJS)
+ $(CC) -o $(NAME) $(OBJS) $(LDLIBS)
+
+#----- Test sheet -------------------------------------------------------------
+
+sch:
+ eeschema test.sch
+
+test: $(NAME)
+ ./$(NAME) test.lib test.sch >out.fig
+ fig2dev -L png -m 2 out.fig _out.png
+ [ ! -r ref.png ] || \
+ compare -metric AE ref.png _out.png _diff.png || \
+ qiv -t -R -D _diff.png ref.png _out.png
+
+testref: $(NAME)
+ ./$(NAME) test.lib test.sch | fig2dev -L png -m 2 >ref.png
+
+png: $(NAME)
+ ./$(NAME) test.lib test.sch -- png -o _out.png -s 2
+ [ ! -r pngref.png ] || \
+ compare -metric AE pngref.png _out.png _diff.png || \
+ qiv -t -R -D _diff.png pngref.png _out.png
+
+pngref: $(NAME)
+ ./$(NAME) test.lib test.sch -- png -o pngref.png -s 2
+
+clean::
+ rm -f out.fig _out.png _diff.png
+
+#----- Render Neo900 schematics -----------------------------------------------
+
+NEO900_HW = ../../../n9/ee/hw
+KICAD_LIBS = ../../kicad-libs/components
+
+SHEET ?= 12
+
+neo900: $(NAME)
+ ./$(NAME) $(NEO900_HW)/neo900.lib \
+ $(KICAD_LIBS)/powered.lib \
+ $(NEO900_HW)/neo900_SS_$(SHEET).sch \
+ >out.fig
+
+neo900.pdf: $(NAME) sch2pdf neo900-template.fig
+ ./sch2pdf -o $@ -t neo900-template.fig \
+ $(NEO900_HW)/neo900.lib $(KICAD_LIBS)/powered.lib \
+ $(NEO900_HW)/neo900.sch
+
+#----- Regression test based on Neo900 schematics -----------------------------
+
+diff: $(NAME)
+ test/genpng test out
+ test/comp test || $(MAKE) view
+
+view:
+ qiv -t -R -D `echo test/_diff*.png | \
+ sed 's/\([^ ]*\)_diff\([^ ]*\)/\1_diff\2 \1ref\2 \1out\2/g'`
+
+newref:
+ test/genpng test ref
diff --git a/TODO b/TODO
new file mode 100644
index 0000000..a2e2899
--- /dev/null
+++ b/TODO
@@ -0,0 +1,24 @@
+- better text size guessing also for FIG
+- unify alignment, direction
+- support fonts attributes ?
+- support line thickness ?
+- ~ as overline (grep for ~ in out.fig)
+- glabel: build for "right" style, then rotate poly (like hlabel)
+- show open pins / wires
+- check remaining alignment / direction / rotation cases in switch statements
+- support mirroring (and detect-complain if unexpected) [should be done now]
+- pin shapes (inverted, clock, etc.)
+- optionally display pin type
+- find libraries (e.g., from .pro)
+- PDF TOC
+- let user set PNG size or zoom level
+- parse .kicad_wks
+- on parse error, politely complain, don't terminate;
+ convert abort / assert(0) to proper error indications
+- implement destructors
+- check for memory leaks
+- record.c (bb_rot): implement bounding boxes for text
+- nesting gfx in diff is a huge kludge, caused by global vars in gfx.c
+- move path name guessing into file.c
+- return indication of whether diff found any differences
+- in diff, pass only options understood by cairo_png
diff --git a/cro.c b/cro.c
new file mode 100644
index 0000000..8ede2b9
--- /dev/null
+++ b/cro.c
@@ -0,0 +1,461 @@
+/*
+ * cro.c - Cairo graphics back-end
+ *
+ * Written 2016 by Werner Almesberger
+ * Copyright 2016 by Werner Almesberger
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ */
+
+
+#include <stddef.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <stdint.h>
+#include <unistd.h>
+#include <math.h>
+
+#include <cairo/cairo.h>
+#include <cairo/cairo-pdf.h>
+
+#include "util.h"
+#include "style.h"
+#include "text.h"
+#include "gfx.h"
+#include "record.h"
+#include "main.h"
+#include "cro.h"
+
+
+/*
+ * FIG works with 1/1200 in
+ * KiCad works with mil
+ * 1 point = 1/72 in
+ */
+
+#define DEFAULT_SCALE (72.0 / 1200)
+
+
+struct cro_ctx {
+ struct record record; /* must be first */
+
+ int xo, yo;
+ float scale;
+
+ cairo_t *cr;
+ cairo_surface_t *s;
+
+ struct record *sheets; /* for PDF */
+ unsigned n_sheets;
+
+ const char *output_name;
+};
+
+
+static inline int cd(struct cro_ctx *cc, int x)
+{
+ return x * cc->scale;
+}
+
+
+static inline int cx(struct cro_ctx *cc, int x)
+{
+ return cc->xo + x * cc->scale;
+}
+
+
+static inline int xc(struct cro_ctx *cc, int x)
+{
+ return (x - cc->xo) / cc->scale;
+}
+
+
+static inline int cy(struct cro_ctx *cc, int y)
+{
+ return cc->yo + y * cc->scale;
+}
+
+
+static inline float pt(struct cro_ctx *cc, int x)
+{
+ return cd(cc, x) * 72 * 1.5 / 1200.0;
+}
+
+
+static void set_color(cairo_t *cr, int color)
+{
+ uint32_t c;
+
+ if (color < 0)
+ return;
+ c = color_rgb[color];
+ cairo_set_source_rgb(cr, (c >> 16) / 255.0, ((c >> 8) & 255) / 255.0,
+ (c & 255) / 255.0);
+}
+
+
+static void paint(cairo_t *cr, int color, int fill_color)
+{
+ if (fill_color != COLOR_NONE) {
+ set_color(cr, fill_color);
+ if (color == COLOR_NONE)
+ cairo_fill(cr);
+ else
+ cairo_fill_preserve(cr);
+ }
+ if (color != COLOR_NONE) {
+ set_color(cr, color);
+ cairo_stroke(cr);
+ }
+}
+
+
+/* ----- General items ----------------------------------------------------- */
+
+
+static void cr_line(void *ctx, int sx, int sy, int ex, int ey,
+ int color, unsigned layer)
+{
+ struct cro_ctx *cc = ctx;
+ static const double dashes[] = { 4, 2 };
+
+ cairo_new_path(cc->cr);
+ cairo_move_to(cc->cr, cx(cc, sx), cy(cc, sy));
+ cairo_line_to(cc->cr, cx(cc, ex), cy(cc, ey));
+ cairo_set_dash(cc->cr, dashes, ARRAY_ELEMENTS(dashes), 0);
+ paint(cc->cr, color, COLOR_NONE);
+ cairo_set_dash(cc->cr, NULL, 0, 0);
+}
+
+
+static void cr_poly(void *ctx,
+ int points, const int x[points], const int y[points],
+ int color, int fill_color, unsigned layer)
+{
+ struct cro_ctx *cc = ctx;
+ bool closed;
+ int i;
+
+ if (points < 2)
+ return;
+ closed = x[0] == x[points - 1] && y[0] == y[points - 1];
+
+ cairo_new_path(cc->cr);
+ cairo_move_to(cc->cr, cx(cc, x[0]), cy(cc, y[0]));
+
+ for (i = 1; i != points - closed; i++)
+ cairo_line_to(cc->cr, cx(cc, x[i]), cy(cc, y[i]));
+ if (closed)
+ cairo_close_path(cc->cr);
+
+ paint(cc->cr, color, fill_color);
+}
+
+
+static void cr_circ(void *ctx, int x, int y, int r,
+ int color, int fill_color, unsigned layer)
+{
+ struct cro_ctx *cc = ctx;
+
+ cairo_new_path(cc->cr);
+ cairo_arc(cc->cr, cx(cc, x), cy(cc, y), cd(cc, r), 0, 2 * M_PI);
+ paint(cc->cr, color, fill_color);
+}
+
+
+static void cr_arc(void *ctx, int x, int y, int r, int sa, int ea,
+ int color, int fill_color, unsigned layer)
+{
+ struct cro_ctx *cc = ctx;
+
+ cairo_new_path(cc->cr);
+ cairo_arc(cc->cr, cx(cc, x), cy(cc, y), cd(cc, r),
+ -ea / 180.0 * M_PI, -sa / 180.0 * M_PI);
+ paint(cc->cr, color, fill_color);
+}
+
+
+#define TEXT_STRETCH 1.3
+
+
+static void cr_text(void *ctx, int x, int y, const char *s, unsigned size,
+ enum text_align align, int rot, unsigned color, unsigned layer)
+{
+ struct cro_ctx *cc = ctx;
+ cairo_text_extents_t ext;
+ cairo_matrix_t m;
+
+ cairo_set_font_size(cc->cr, cd(cc, size) * TEXT_STRETCH);
+ cairo_text_extents(cc->cr, s, &ext);
+
+ set_color(cc->cr, color);
+
+ cairo_move_to(cc->cr, cx(cc, x), cy(cc, y));
+
+ cairo_get_matrix(cc->cr, &m);
+ cairo_rotate(cc->cr, -rot / 180.0 * M_PI);
+
+ switch (align) {
+ case text_min:
+ break;
+ case text_mid:
+ cairo_rel_move_to(cc->cr, -ext.width / 2.0, 0);
+ break;
+ case text_max:
+ cairo_rel_move_to(cc->cr, -ext.width, 0);
+ break;
+ default:
+ abort();
+ }
+
+ cairo_show_text(cc->cr, s);
+ cairo_set_matrix(cc->cr, &m);
+}
+
+
+static unsigned cr_text_width(void *ctx, const char *s, unsigned size)
+{
+ struct cro_ctx *cc = ctx;
+ cairo_text_extents_t ext;
+
+ cairo_set_font_size(cc->cr, cx(cc, size) * TEXT_STRETCH);
+ cairo_text_extents(cc->cr, s, &ext);
+ return xc(cc, ext.width) * 1.05; /* @@@ Cairo seems to underestimate */
+}
+
+
+/* ----- Initializatio and termination ------------------------------------- */
+
+
+static const struct gfx_ops real_cro_ops = {
+ .name = "cairo",
+ .line = cr_line,
+ .poly = cr_poly,
+ .circ = cr_circ,
+ .arc = cr_arc,
+ .text = cr_text,
+ .text_width = cr_text_width,
+};
+
+
+static struct cro_ctx *init_common(int argc, char *const *argv)
+{
+ struct cro_ctx *cc;
+ char c;
+
+ cc = alloc_type(struct cro_ctx);
+ cc->xo = cc->yo = 0;
+ cc->scale = DEFAULT_SCALE;
+
+ cc->sheets = NULL;
+ cc->n_sheets = 0;
+
+ cc->output_name = NULL;
+ while ((c = getopt(argc, argv, "o:s:")) != EOF)
+ switch (c) {
+ case 'o':
+ cc->output_name = optarg;
+ break;
+ case 's':
+ cc->scale = atof(optarg) * DEFAULT_SCALE;
+ break;
+ default:
+ usage(*argv);
+ }
+
+ record_init(&cc->record, &real_cro_ops, cc);
+
+ return cc;
+}
+
+
+static void *cr_png_init(int argc, char *const *argv)
+{
+ struct cro_ctx *cc;
+
+ cc = init_common(argc, argv);
+
+ /* cr_text_width needs *something* to work with */
+
+ cc->s = cairo_image_surface_create(CAIRO_FORMAT_RGB24, 16, 16);
+ cc->cr = cairo_create(cc->s);
+
+ return cc;
+}
+
+
+static void *cr_pdf_init(int argc, char *const *argv)
+{
+ struct cro_ctx *cc;
+
+ cc = init_common(argc, argv);
+
+ /* cr_text_width needs *something* to work with */
+
+ cc->s = cairo_pdf_surface_create(NULL, 16, 16);
+ cc->cr = cairo_create(cc->s);
+
+ return cc;
+}
+
+
+static void end_common(struct cro_ctx *cc, int *w, int *h)
+{
+ int x, y;
+
+ cairo_surface_destroy(cc->s);
+ cairo_destroy(cc->cr);
+
+ record_bbox(&cc->record, &x, &y, w, h);
+
+// fprintf(stderr, "%dx%d%+d%+d\n", *w, *h, x, y);
+ cc->xo = -cd(cc, x);
+ cc->yo = -cd(cc, y);
+ *w = cd(cc, *w);
+ *h = cd(cc, *h);
+// fprintf(stderr, "%dx%d%+d%+d\n", *w, *h, x, y);
+}
+
+
+static void cr_png_end(void *ctx)
+{
+ struct cro_ctx *cc = ctx;
+ int w, h;
+
+ end_common(cc, &w, &h);
+
+ cc->s = cairo_image_surface_create(CAIRO_FORMAT_RGB24, w, h);
+ cc->cr = cairo_create(cc->s);
+
+ set_color(cc->cr, COLOR_WHITE);
+ cairo_paint(cc->cr);
+
+ cairo_select_font_face(cc->cr, "Helvetica", CAIRO_FONT_SLANT_NORMAL,
+ CAIRO_FONT_WEIGHT_BOLD);
+ cairo_set_line_width(cc->cr, 2);
+
+ record_replay(&cc->record);
+ record_destroy(&cc->record);
+
+ if (cc->output_name)
+ cairo_surface_write_to_png(cc->s, cc->output_name);
+}
+
+
+static void cr_pdf_new_sheet(void *ctx)
+{
+ struct cro_ctx *cc = ctx;
+
+ cc->n_sheets++;
+ cc->sheets = realloc(cc->sheets, sizeof(struct record) * cc->n_sheets);
+ if (!cc->sheets) {
+ perror("realloc");
+ exit(1);
+ }
+ cc->sheets[cc->n_sheets - 1] = cc->record;
+ record_wipe(&cc->record);
+}
+
+
+static void cr_pdf_end(void *ctx)
+{
+ struct cro_ctx *cc = ctx;
+ int w, h;
+ unsigned i;
+
+ end_common(cc, &w, &h);
+
+ cc->s = cairo_pdf_surface_create(cc->output_name, w, h);
+ cc->cr = cairo_create(cc->s);
+
+ cairo_select_font_face(cc->cr, "Helvetica", CAIRO_FONT_SLANT_NORMAL,
+ CAIRO_FONT_WEIGHT_BOLD);
+ cairo_set_line_width(cc->cr, 2);
+
+ for (i = 0; i != cc->n_sheets; i++) {
+ set_color(cc->cr, COLOR_WHITE);
+ cairo_paint(cc->cr);
+
+ record_replay(cc->sheets + i);
+ record_destroy(cc->sheets + i);
+
+ cairo_show_page(cc->cr);
+ }
+
+ record_replay(&cc->record);
+ record_destroy(&cc->record);
+
+ cairo_show_page(cc->cr);
+
+ cairo_surface_destroy(cc->s);
+ cairo_destroy(cc->cr);
+}
+
+
+uint32_t *cro_img_end(void *ctx, int *w, int *h, int *stride)
+{
+ struct cro_ctx *cc = ctx;
+ uint32_t *data;
+
+ end_common(cc, w, h);
+
+ *stride = cairo_format_stride_for_width(CAIRO_FORMAT_RGB24, *w);
+ data = alloc_size(*stride * *h);
+
+ cc->s = cairo_image_surface_create_for_data((unsigned char *) data,
+ CAIRO_FORMAT_RGB24, *w, *h, *stride);
+ cc->cr = cairo_create(cc->s);
+
+ set_color(cc->cr, COLOR_WHITE);
+ cairo_paint(cc->cr);
+
+ cairo_select_font_face(cc->cr, "Helvetica", CAIRO_FONT_SLANT_NORMAL,
+ CAIRO_FONT_WEIGHT_BOLD);
+ cairo_set_line_width(cc->cr, 2);
+
+ record_replay(&cc->record);
+ record_destroy(&cc->record);
+
+ return data;
+}
+
+
+void cro_img_write(void *ctx, const char *name)
+{
+ struct cro_ctx *cc = ctx;
+
+ cairo_surface_write_to_png(cc->s, name);
+}
+
+
+/* ----- Operations -------------------------------------------------------- */
+
+
+const struct gfx_ops cro_png_ops = {
+ .name = "png",
+ .line = record_line,
+ .poly = record_poly,
+ .circ = record_circ,
+ .arc = record_arc,
+ .text = record_text,
+ .text_width = cr_text_width,
+ .init = cr_png_init,
+ .end = cr_png_end,
+};
+
+const struct gfx_ops cro_pdf_ops = {
+ .name = "pdf",
+ .line = record_line,
+ .poly = record_poly,
+ .circ = record_circ,
+ .arc = record_arc,
+ .text = record_text,
+ .text_width = cr_text_width,
+ .init = cr_pdf_init,
+ .new_sheet = cr_pdf_new_sheet,
+ .end = cr_pdf_end,
+};
diff --git a/cro.h b/cro.h
new file mode 100644
index 0000000..3595da8
--- /dev/null
+++ b/cro.h
@@ -0,0 +1,31 @@
+/*
+ * cro.h - Cairo graphics back-end
+ *
+ * Written 2016 by Werner Almesberger
+ * Copyright 2016 by Werner Almesberger
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ */
+
+
+#ifndef CRO_H
+#define CRO_H
+
+#include <stdint.h>
+
+#include "gfx.h"
+
+
+extern const struct gfx_ops cro_png_ops;
+extern const struct gfx_ops cro_pdf_ops;
+
+#define cro_img_ops cro_png_ops /* just don't call cro_img_ops.end */
+
+
+uint32_t *cro_img_end(void *ctx, int *w, int *h, int *stride);
+void cro_img_write(void *ctx, const char *name);
+
+#endif /* !CRO_H */
diff --git a/diff.c b/diff.c
new file mode 100644
index 0000000..8f7d4a0
--- /dev/null
+++ b/diff.c
@@ -0,0 +1,279 @@
+/*
+ * diff.c - Schematics difference
+ *
+ * Written 2016 by Werner Almesberger
+ * Copyright 2016 by Werner Almesberger
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ */
+
+
+#include <stddef.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <unistd.h>
+
+#include "util.h"
+#include "main.h"
+#include "cro.h"
+#include "sch.h"
+#include "lib.h"
+#include "diff.h"
+
+
+#define DEFAULT_FRAME_RADIUS 30
+
+
+struct area {
+ int xa, ya, xb, yb;
+ struct area *next;
+};
+
+struct diff {
+ void *cr_ctx;
+ uint32_t *new_img;
+ int w, h, stride;
+ const char *output_name;
+ int frame_radius;
+ struct area *areas;
+};
+
+
+/* ----- Wrappers ---------------------------------------------------------- */
+
+
+static void diff_line(void *ctx, int sx, int sy, int ex, int ey,
+ int color, unsigned layer)
+{
+ const struct diff *diff = ctx;
+
+ cro_img_ops.line(diff->cr_ctx, sx, sy, ex, ey, color, layer);
+}
+
+
+static void diff_poly(void *ctx,
+ int points, const int x[points], const int y[points],
+ int color, int fill_color, unsigned layer)
+{
+ const struct diff *diff = ctx;
+
+ cro_img_ops.poly(diff->cr_ctx, points, x, y, color, fill_color, layer);
+}
+
+
+static void diff_circ(void *ctx, int x, int y, int r,
+ int color, int fill_color, unsigned layer)
+{
+ const struct diff *diff = ctx;
+
+ cro_img_ops.circ(diff->cr_ctx, x, y, r, color, fill_color, layer);
+}
+
+
+static void diff_arc(void *ctx, int x, int y, int r, int sa, int ea,
+ int color, int fill_color, unsigned layer)
+{
+ const struct diff *diff = ctx;
+
+ cro_img_ops.arc(diff->cr_ctx, x, y, r, sa, ea,
+ color, fill_color, layer);
+}
+
+
+static void diff_text(void *ctx, int x, int y, const char *s, unsigned size,
+ enum text_align align, int rot, unsigned color, unsigned layer)
+{
+ const struct diff *diff = ctx;
+
+ cro_img_ops.text(diff->cr_ctx, x, y, s, size, align, rot,
+ color, layer);
+}
+
+
+static unsigned diff_text_width(void *ctx, const char *s, unsigned size)
+{
+ const struct diff *diff = ctx;
+
+ return cro_img_ops.text_width(diff->cr_ctx, s, size);
+}
+
+
+/* ----- Initialization and termination ------------------------------------ */
+
+
+static void *diff_init(int argc, char *const *argv)
+{
+ struct diff *diff;
+ char c;
+ int arg;
+ struct sch_ctx new_sch;
+ struct lib new_lib;
+
+ diff = alloc_type(struct diff);
+ diff->areas = NULL;
+
+ sch_init(&new_sch, 0);
+ lib_init(&new_lib);
+
+ diff->output_name = NULL;
+ diff->frame_radius = DEFAULT_FRAME_RADIUS;
+ while ((c = getopt(argc, argv, "o:s:")) != EOF)
+ switch (c) {
+ case 'o':
+ diff->output_name = optarg;
+ break;
+ case 's':
+ /* for cro_png */
+ break;
+ default:
+ usage(*argv);
+ }
+
+ if (argc - optind < 1)
+ usage(*argv);
+
+ for (arg = optind; arg != argc - 1; arg++)
+ lib_parse(&new_lib, argv[arg]);
+ sch_parse(&new_sch, argv[argc - 1], &new_lib);
+
+ optind = 0;
+ gfx_init(&cro_img_ops, argc, argv);
+ diff->cr_ctx = gfx_ctx;
+ sch_render(new_sch.sheets);
+ diff->new_img = cro_img_end(gfx_ctx,
+ &diff->w, &diff->h, &diff->stride);
+
+ optind = 0;
+ diff->cr_ctx = cro_img_ops.init(argc, argv);
+
+ return diff;
+}
+
+
+/* steal from schhist/ppmdiff.c */
+
+#define ONLY_OLD 0xff0000
+#define ONLY_NEW 0x00d000
+#define BOTH 0x707070
+
+#define AREA_FILL 0xffffc8
+
+
+static void mark_area(struct diff *diff, int x, int y)
+{
+ struct area *area;
+
+ for (area = diff->areas; area; area = area->next)
+ if (x >= area->xa && x <= area->xb &&
+ y >= area->ya && y <= area->yb) {
+ if (area->xa > x - diff->frame_radius)
+ area->xa = x - diff->frame_radius;
+ if (area->xb < x + diff->frame_radius)
+ area->xb = x + diff->frame_radius;
+ if (area->ya > y - diff->frame_radius)
+ area->ya = y - diff->frame_radius;
+ if (area->yb < y + diff->frame_radius)
+ area->yb = y + diff->frame_radius;
+ return;
+ }
+
+ area = alloc_type(struct area);
+
+ area->xa = x - diff->frame_radius;
+ area->xb = x + diff->frame_radius;
+ area->ya = y - diff->frame_radius;
+ area->yb = y + diff->frame_radius;
+
+ area->next = diff->areas;
+ diff->areas = area;
+}
+
+
+#define MASK 0xffffff
+
+
+static void differences(struct diff *diff, uint32_t *a, const uint32_t *b)
+{
+ int x, y;
+ unsigned skip = diff->w * 4 - diff->stride;
+
+ for (y = 0; y != diff->h; y++) {
+ for (x = 0; x != diff->w; x++) {
+ if (!((*a ^ *b) & MASK)) {
+ *a = ((*a >> 3) & 0x1f1f1f) | 0xe0e0e0;
+// *a = ((*a >> 2) & 0x3f3f3f) | 0xc0c0c0;
+ } else {
+ mark_area(diff, x, y);
+//fprintf(stderr, "0x%06x 0x%06x", *a, *b);
+ *a = (*a & MASK) == MASK ? ONLY_NEW :
+ (*b & MASK) == MASK ? ONLY_OLD : BOTH;
+//fprintf(stderr, "-> 0x%06x\n", *a);
+ }
+ a++;
+ b++;
+ }
+ a += skip;
+ b += skip;
+ }
+}
+
+
+static void show_areas(struct diff *diff, uint32_t *a)
+{
+ const struct area *area;
+ uint32_t *p;
+ int x, y;
+
+ for (area = diff->areas; area; area = area->next)
+ for (y = area->ya; y != area->yb; y++) {
+ if (y < 0 || y >= diff->h)
+ continue;
+ p = a + y * (diff->stride >> 2);
+ for (x = area->xa; x != area->xb; x++) {
+ if (x >= 0 && x < diff->w &&
+ (p[x] & MASK) == MASK)
+ p[x] = AREA_FILL;
+ }
+ }
+}
+
+
+static void diff_end(void *ctx)
+{
+ struct diff *diff = ctx;
+ uint32_t *old_img;
+ int w, h, stride;
+
+ old_img = cro_img_end(diff->cr_ctx, &w, &h, &stride);
+ if (diff->w != w || diff->h != h) {
+ fprintf(stderr, "%d x %d vs. %d x %d image\n",
+ w, h, diff->w, diff->h);
+ exit(1);
+ }
+
+ differences(diff, old_img, diff->new_img);
+ show_areas(diff, old_img);
+
+ if (diff->output_name)
+ cro_img_write(diff->cr_ctx, diff->output_name);
+}
+
+
+/* ----- Operations -------------------------------------------------------- */
+
+
+const struct gfx_ops diff_ops = {
+ .name = "diff",
+ .line = diff_line,
+ .poly = diff_poly,
+ .circ = diff_circ,
+ .arc = diff_arc,
+ .text = diff_text,
+ .text_width = diff_text_width,
+ .init = diff_init,
+ .end = diff_end,
+};
diff --git a/diff.h b/diff.h
new file mode 100644
index 0000000..22626b1
--- /dev/null
+++ b/diff.h
@@ -0,0 +1,22 @@
+/*
+ * diff.h - Schematics difference
+ *
+ * Written 2016 by Werner Almesberger
+ * Copyright 2016 by Werner Almesberger
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ */
+
+
+#ifndef DIFF_H
+#define DIFF_H
+
+#include "gfx.h"
+
+
+extern const struct gfx_ops diff_ops;
+
+#endif /* !DIFF_H */
diff --git a/dwg.c b/dwg.c
new file mode 100644
index 0000000..08fa64a
--- /dev/null
+++ b/dwg.c
@@ -0,0 +1,463 @@
+/*
+ * dwg.c - Complex drawing functions
+ *
+ * Written 2016 by Werner Almesberger
+ * Copyright 2016 by Werner Almesberger
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ */
+
+
+#define _GNU_SOURCE /* for asprintf */
+#include <stdbool.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <assert.h>
+
+#include "util.h"
+#include "misc.h"
+#include "style.h"
+#include "text.h"
+#include "gfx.h"
+#include "dwg.h"
+
+
+/* ----- Labels ------------------------------------------------------------ */
+
+
+enum box_type { // ___
+ box_simple, // [___]
+ box_left, // <___]
+ box_right, // [___>
+ box_both, // <___>
+};
+
+
+static enum box_type flip_box(enum box_type box)
+{
+ switch (box) {
+ case box_simple:
+ return box_simple;
+ case box_left:
+ return box_right;
+ case box_right:
+ return box_left;
+ case box_both:
+ return box_both;
+ default:
+ abort();
+ }
+}
+
+
+void dwg_label(int x, int y, const char *s, int dir, int dim,
+ enum dwg_shape shape)
+{
+ struct text txt = {
+ .s = s,
+ .size = dim,
+ .x = x,
+ .y = y,
+ .rot = 0,
+ .hor = 0,
+ .vert = text_min,
+ };
+ int dx = 0, dy = 0;
+
+ switch (dir) {
+ case 0: /* right */
+ txt.rot = 0;
+ txt.hor = text_min;
+ dy = 1;
+ break;
+ case 1: /* up */
+ txt.rot = 90;
+ txt.hor = text_min;
+ dx = -1;
+ break;
+ case 2: /* left */
+ txt.rot = 0;
+ txt.hor = text_max;
+ dy = 1;
+ break;
+ case 3: /* down */
+ txt.rot = 90;
+ txt.hor = text_max;
+ dx = -1;
+ break;
+ default:
+ assert(0);
+ }
+
+ txt.y -= dy * LABEL_OFFSET;
+ txt.x += dx * LABEL_OFFSET;
+ text_fig(&txt, COLOR_LABEL, LAYER_LABEL);
+}
+
+
+void dwg_glabel(int x, int y, const char *s, int dir, int dim,
+ enum dwg_shape shape)
+{
+ struct text txt = {
+ .s = s,
+ .size = dim,
+ .x = x,
+ .y = y,
+ .rot = 0,
+ .hor = 0,
+ .vert = text_mid,
+ };
+ int n = 6;
+ int vx[7];
+ int vy[7];
+ int half = (dim >> 1) + GLABEL_OFFSET;
+ enum box_type box;
+ int dx, shift_flat, shift_tip;
+ bool anchor_right = 1;
+ char *tag;
+
+ switch (shape) {
+ case dwg_unspec:
+ box = box_simple;
+ break;
+ case dwg_in:
+ box = box_right;
+ break;
+ case dwg_out:
+ box = box_left;
+ break;
+ case dwg_bidir:
+ box = box_both;
+ break;
+ default:
+ assert(0);
+ }
+
+ switch (dir) {
+ case 0: /* left */
+ txt.rot = 0;
+ txt.hor = text_max;
+ dx = -1;
+ break;
+ case 1: /* up */
+ txt.rot = 90;
+ txt.hor = text_min;
+ dx = 1;
+ box = flip_box(box);
+ anchor_right = !anchor_right;
+ break;
+ case 2: /* right */
+ txt.rot = 0;
+ txt.hor = text_min;
+ dx = 1;
+ box = flip_box(box);
+ anchor_right = !anchor_right;
+ break;
+ case 3: /* down */
+ txt.rot = 90;
+ txt.hor = text_max;
+ dx = -1;
+ break;
+ default:
+ assert(0);
+ }
+
+ shift_flat = dx * GLABEL_OFFSET;
+ shift_tip = dx * (GLABEL_OFFSET + half);
+
+ switch (box) {
+ case box_simple:
+ n = 5;
+ text_shift(&txt, txt.hor, text_mid, shift_flat, 0);
+ text_rel(&txt, text_min, text_min,
+ -GLABEL_OFFSET, GLABEL_OFFSET, vx + 1, vy + 1);
+ text_rel(&txt, text_max, text_min,
+ GLABEL_OFFSET, GLABEL_OFFSET, vx + 2, vy + 2);
+ text_rel(&txt, text_max, text_max,
+ GLABEL_OFFSET, -GLABEL_OFFSET, vx + 3, vy + 3);
+ text_rel(&txt, text_min, text_max,
+ -GLABEL_OFFSET, -GLABEL_OFFSET, vx + 4, vy + 4);
+ break;
+ case box_right:
+ text_shift(&txt, txt.hor, text_mid,
+ anchor_right ? shift_tip : shift_flat, 0);
+ text_rel(&txt, text_min, text_min,
+ -GLABEL_OFFSET, GLABEL_OFFSET, vx + 1, vy + 1);
+ text_rel(&txt, text_max, text_min,
+ GLABEL_OFFSET, GLABEL_OFFSET, vx + 2, vy + 2);
+ text_rel(&txt, text_max, text_mid, GLABEL_OFFSET + half, 0,
+ vx + 3, vy + 3);
+ text_rel(&txt, text_max, text_max,
+ GLABEL_OFFSET, -GLABEL_OFFSET, vx + 4, vy + 4);
+ text_rel(&txt, text_min, text_max,
+ -GLABEL_OFFSET, -GLABEL_OFFSET, vx + 5, vy + 5);
+ break;
+ case box_left:
+ text_shift(&txt, txt.hor, text_mid,
+ anchor_right ? shift_flat : shift_tip, 0);
+ text_rel(&txt, text_min, text_min,
+ -GLABEL_OFFSET, GLABEL_OFFSET, vx + 1, vy + 1);
+ text_rel(&txt, text_max, text_min,
+ GLABEL_OFFSET, GLABEL_OFFSET, vx + 2, vy + 2);
+ text_rel(&txt, text_max, text_max,
+ GLABEL_OFFSET, -GLABEL_OFFSET, vx + 3, vy + 3);
+ text_rel(&txt, text_min, text_max,
+ -GLABEL_OFFSET, -GLABEL_OFFSET, vx + 4, vy + 4);
+ text_rel(&txt, text_min, text_mid, -GLABEL_OFFSET- half, 0,
+ vx + 5, vy + 5);
+ break;
+ case box_both:
+ n = 7;
+ text_shift(&txt, txt.hor, text_mid, shift_tip, 0);
+ text_rel(&txt, text_min, text_min,
+ -GLABEL_OFFSET, GLABEL_OFFSET, vx + 1, vy + 1);
+ text_rel(&txt, text_max, text_min,
+ GLABEL_OFFSET, GLABEL_OFFSET, vx + 2, vy + 2);
+ text_rel(&txt, text_max, text_mid, GLABEL_OFFSET + half, 0,
+ vx + 3, vy + 3);
+ text_rel(&txt, text_max, text_max,
+ GLABEL_OFFSET, -GLABEL_OFFSET, vx + 4, vy + 4);
+ text_rel(&txt, text_min, text_max,
+ -GLABEL_OFFSET, -GLABEL_OFFSET, vx + 5, vy + 5);
+ text_rel(&txt, text_min, text_mid, -GLABEL_OFFSET- half, 0,
+ vx + 6, vy + 6);
+ break;
+ default:
+ assert(0);
+ }
+
+ text_fig(&txt, COLOR_GLABEL, LAYER_GLABEL);
+
+ vx[0] = vx[n - 1];
+ vy[0] = vy[n - 1];
+ gfx_poly(n, vx, vy, COLOR_GLABEL, COLOR_NONE, LAYER_GLABEL);
+
+ if (asprintf(&tag, "G:%s", s)) {}
+ gfx_tag(tag, n, vx, vy);
+}
+
+
+static int make_box(enum box_type box, int h, int *vx, int *vy)
+{
+ int r = h / 2;
+
+ switch (box) {
+ case box_simple:
+ vx[0] = 0;
+ vy[0] = -r;
+ vx[1] = 2 * r;
+ vy[1] = -r;
+ vx[2] = 2 * r;
+ vy[2] = r;
+ vx[3] = 0;
+ vy[3] = r;
+ return 4;
+ case box_right:
+ vx[0] = 0;
+ vy[0] = -r;
+ vx[1] = r;
+ vy[1] = -r;
+ vx[2] = 2 * r;
+ vy[2] = 0;
+ vx[3] = r;
+ vy[3] = r;
+ vx[4] = 0;
+ vy[4] = r;
+ return 5;
+ case box_left:
+ vx[0] = r;
+ vy[0] = -r;
+ vx[1] = 2 * r;
+ vy[1] = -r;
+ vx[2] = 2 * r;
+ vy[2] = r;
+ vx[3] = r;
+ vy[3] = r;
+ vx[4] = 0;
+ vy[4] = 0;
+ return 5;
+ case box_both:
+ vx[0] = 0;
+ vy[0] = 0;
+ vx[1] = r;
+ vy[1] = -r;
+ vx[2] = 2 * r;
+ vy[2] = 0;
+ vx[3] = r;
+ vy[3] = r;
+ return 4;
+ default:
+ assert(0);
+ }
+}
+
+
+void dwg_hlabel(int x, int y, const char *s, int dir, int dim,
+ enum dwg_shape shape)
+{
+ struct text txt = {
+ .s = s,
+ .size = dim,
+ .x = x,
+ .y = y,
+ .rot = 0,
+ .hor = 0,
+ .vert = text_mid,
+ };
+ int vx[6], vy[6];
+ int rot;
+ int n, i;
+
+ switch (shape) {
+ case dwg_unspec:
+ n = make_box(box_simple, dim, vx, vy);
+ break;
+ case dwg_in:
+ n = make_box(box_left, dim, vx, vy);
+ break;
+ case dwg_out:
+ n = make_box(box_right, dim, vx, vy);
+ break;
+ case dwg_bidir:
+ n = make_box(box_both, dim, vx, vy);
+ break;
+ default:
+ assert(0);
+ }
+
+ switch (dir) {
+ case 0: /* right */
+ rot = 180;
+ txt.hor = text_max;
+ break;
+ case 1: /* up */
+ rot = 90;
+ txt.hor = text_min;
+ break;
+ case 2: /* left */
+ rot = 0;
+ txt.hor = text_min;
+ break;
+ case 3: /* down */
+ rot = 270;
+ txt.hor = text_max;
+ break;
+ default:
+ assert(0);
+ }
+
+ txt.x += rx((1 + HLABEL_OFFSET_F) * dim, 0, rot);
+ txt.y += ry((1 + HLABEL_OFFSET_F) * dim, 0, rot);
+
+ for (i = 0; i != n; i++) {
+ int tmp;
+
+ tmp = x + rx(vx[i], vy[i], rot);
+ vy[i] = y + ry(vx[i], vy[i], rot);
+ vx[i] = tmp;
+ }
+
+ vx[n] = vx[0];
+ vy[n] = vy[0];
+
+ txt.rot = rot % 180;
+
+ text_fig(&txt, COLOR_HLABEL, LAYER_HLABEL);
+ gfx_poly(n + 1, vx, vy, COLOR_HLABEL, COLOR_NONE, LAYER_HLABEL);
+}
+
+
+/* ----- Text -------------------------------------------------------------- */
+
+
+void dwg_text(int x, int y, const char *s, int dir, int dim,
+ enum dwg_shape shape)
+{
+ struct text txt = {
+ .s = s,
+ .size = dim,
+ .x = x,
+ .y = y,
+ .rot = 0,
+ .hor = text_min,
+ .vert = text_min,
+ };
+
+ switch (dir) {
+ case 0: /* right */
+ break;
+ case 1: /* up */
+ text_rot(&txt, 90);
+ break;
+ case 2: /* left */
+ txt.hor = text_max;
+ break;
+ case 3: /* down */
+ text_rot(&txt, 90);
+ txt.hor = text_max;
+ break;
+ default:
+ assert(2 + 2 == 5);
+ }
+
+ text_fig(&txt, COLOR_TEXT, LAYER_TEXT);
+}
+
+
+/* ----- Connections ------------------------------------------------------- */
+
+
+void dwg_junction(int x, int y)
+{
+ gfx_circ(x, y, JUNCTION_R, COLOR_NONE, COLOR_WIRE, LAYER_WIRES);
+}
+
+
+void dwg_noconn(int x, int y)
+{
+ int vx[2] = { x - NOCONN_LEN, x + NOCONN_LEN };
+ int vy[2] = { y - NOCONN_LEN, y + NOCONN_LEN };
+
+ gfx_poly(2, vx, vy, COLOR_NOCONN, COLOR_NONE, LAYER_NOCONN);
+ swap(vy[0], vy[1]);
+ gfx_poly(2, vx, vy, COLOR_NOCONN, COLOR_NONE, LAYER_NOCONN);
+}
+
+
+/* ----- Lines ------------------------------------------------------------- */
+
+/*
+ * We can't use gfx_poly because lines are dashed and we don't have that
+ * property at the gfx_poly API.
+ */
+
+void dwg_line(int sx, int sy, int ex, int ey)
+{
+ gfx_line(sx, sy, ex, ey, COLOR_SHEET_DWG, LAYER_LINES);
+}
+
+
+/* ----- Wires and busses -------------------------------------------------- */
+
+
+void dwg_wire(int sx, int sy, int ex, int ey)
+{
+ int vx[] = { sx, ex };
+ int vy[] = { sy, ey };
+
+ // WIDTH_WIRE
+ gfx_poly(2, vx, vy, COLOR_WIRE, COLOR_NONE, LAYER_WIRES);
+}
+
+
+void dwg_bus(int sx, int sy, int ex, int ey)
+{
+ int vx[] = { sx, ex };
+ int vy[] = { sy, ey };
+
+ // WIDTH_BUS
+ gfx_poly(2, vx, vy, COLOR_BUS, COLOR_NONE, LAYER_BUSSES);
+}
diff --git a/dwg.h b/dwg.h
new file mode 100644
index 0000000..697f524
--- /dev/null
+++ b/dwg.h
@@ -0,0 +1,46 @@
+/*
+ * dwg.h - Complex drawing functions
+ *
+ * Written 2016 by Werner Almesberger
+ * Copyright 2016 by Werner Almesberger
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ */
+
+
+#ifndef DWG_H
+#define DWG_H
+
+#include "fig.h"
+
+
+enum dwg_shape {
+ dwg_unspec, // UnSpc
+ dwg_in, // Input
+ dwg_out, // Output
+ dwg_tri, // 3State
+ dwg_bidir, // Bidirectional
+};
+
+
+void dwg_label(int x, int y, const char *s, int dir, int dim,
+ enum dwg_shape shape);
+void dwg_hlabel(int x, int y, const char *s, int dir, int dim,
+ enum dwg_shape shape);
+void dwg_glabel(int x, int y, const char *s, int dir, int dim,
+ enum dwg_shape shape);
+void dwg_text(int x, int y, const char *s, int dir, int dim,
+ enum dwg_shape shape);
+
+void dwg_junction(int x, int y);
+void dwg_noconn(int x, int y);
+
+void dwg_line(int sx, int sy, int ex, int ey);
+
+void dwg_wire(int sx, int sy, int ex, int ey);
+void dwg_bus(int sx, int sy, int ex, int ey);
+
+#endif /* !DWG_H */
diff --git a/fig.c b/fig.c
new file mode 100644
index 0000000..04ec9f4
--- /dev/null
+++ b/fig.c
@@ -0,0 +1,293 @@
+/*
+ * fig.c - Generate FIG output for Eeschema items
+ *
+ * Written 2016 by Werner Almesberger
+ * Copyright 2016 by Werner Almesberger
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ */
+
+
+#include <stdbool.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <unistd.h>
+#include <string.h>
+#include <math.h>
+#include <assert.h>
+
+#include "util.h"
+#include "style.h"
+#include "text.h"
+#include "main.h"
+#include "fig.h"
+
+
+/*
+ * FIG works with 1/1200 in
+ * KiCad works with mil
+ * 1 point = 1/72 in
+ */
+
+
+static inline int cx(int x)
+{
+ return x * 1200 / 1000;
+}
+
+
+static inline int cy(int y)
+{
+ return y * 1200 / 1000;
+}
+
+
+static inline float pt(int x)
+{
+ return cx(x) * 72 * 1.5 / 1200.0;
+}
+
+
+/* ----- Schematics items -------------------------------------------------- */
+
+
+static void fig_line(void *ctx, int sx, int sy, int ex, int ey,
+ int color, unsigned layer)
+{
+ // TypeStyle FillCol AreaFil Cap FwdAr
+ // SubTy Color Pen StyleV Rad BwdAr
+ // Thick Depth Join Points
+ printf("2 1 2 %d %d 7 %d -1 -1 3.0 1 1 -1 0 0 2\n",
+ WIDTH_LINE, color, layer);
+ printf("\t%d %d %d %d\n", cx(sx), cy(sy), cx(ex), cy(ey));
+}
+
+
+/* ----- General items ----------------------------------------------------- */
+
+
+static void fig_rect(void *ctx, int sx, int sy, int ex, int ey,
+ int color, int fill_color, unsigned layer)
+{
+ // Type Thick Depth StyleV Rad
+ // SubTy Color Pen Join FwdAr
+ // Style FillCol AreaFil Cap BwdAr
+ printf("2 2 0 %d %d %d %d -1 %d 0.0 1 1 -1 0 0 5\n",
+ color == -1 ? 0 : WIDTH_COMP_DWG, color, fill_color, layer,
+ fill_color == -1 ? -1 : 20);
+ printf("\t%d %d %d %d %d %d %d %d %d %d\n",
+ cx(sx), cy(sy), cx(ex), cy(sy), cx(ex), cy(ey), cx(sx), cy(ey),
+ cx(sx), cy(sy));
+}
+
+
+static void fig_poly(void *ctx,
+ int points, const int x[points], const int y[points],
+ int color, int fill_color, unsigned layer)
+{
+ int i;
+ char ch = '\t';
+
+ // Type Thick Depth StyleV Rad
+ // SubTy Color Pen Join FwdAr
+ // Style FillCol AreaFil Cap BwdAr
+ printf("2 1 0 %d %d %d %d -1 %d 0.0 1 1 -1 0 0 %d\n",
+ color == -1 ? 0 : WIDTH_COMP_DWG, color, fill_color, layer,
+ fill_color == -1 ? -1 : 20, points);
+ for (i = 0; i != points; i++) {
+ printf("%c%d %d", ch, cx(x[i]), cy(y[i]));
+ ch = ' ';
+ }
+ printf("\n");
+}
+
+
+static void fig_circ(void *ctx, int x, int y, int r,
+ int color, int fill_color, unsigned layer)
+{
+ // Type Thick Depth StyleV Cx Rx Sx Ex
+ // SubTy Color Pen Dir Cy Ry Sy Ey
+ // Style FillCol AreaFil Angle
+ printf("1 3 0 %d %d %d %d -1 %d 0.0 1 0.0 %d %d %d %d %d %d %d %d\n",
+ color == -1 ? 0 : WIDTH_COMP_DWG, color, fill_color, layer,
+ fill_color == -1 ? -1 : 20,
+ cx(x), cy(y), r, r,
+ cx(x), cy(y), cx(x) + r, cy(y));
+}
+
+
+static int ax(int x, int y, int r, int angle)
+{
+ float a = angle / 180.0 * M_PI;
+
+ return cx(x + r * cos(a));
+}
+
+
+static int ay(int x, int y, int r, int angle)
+{
+ float a = angle / 180.0 * M_PI;
+
+ return cy(y - r * sin(a));
+}
+
+
+static void fig_arc(void *ctx, int x, int y, int r, int sa, int ea,
+ int color, int fill_color, unsigned layer)
+{
+ int ma = (sa + ea) / 2;
+
+ // Type Thick Depth StyleV FwdAr
+ // SubTy Color Pen Cap BwdAr
+ // Style FillCol AreaFil Dir points
+ printf("5 1 0 %d %d %d %d -1 %d 0.0 1 1 0 0 %d %d %d %d %d %d %d %d\n",
+ color == -1 ? 0 : WIDTH_COMP_DWG, color, fill_color, layer,
+ fill_color == -1 ? -1 : 20,
+ cx(x), cy(y),
+ ax(x, y, r, sa), ay(x, y, r, sa),
+ ax(x, y, r, ma), ay(x, y, r, ma),
+ ax(x, y, r, ea), ay(x, y, r, ea));
+}
+
+
+static void fig_tag(void *ctx, const char *s,
+ int points, const int x[points], const int y[points])
+{
+ printf("# href=\"%s\" alt=\"\"\n", s);
+ fig_poly(ctx, points, x, y, COLOR_NONE, COLOR_NONE, 999);
+}
+
+
+static void fig_text(void *ctx, int x, int y, const char *s, unsigned size,
+ enum text_align align, int rot, unsigned color, unsigned layer)
+{
+ // Type Depth FontSiz Height
+ // Just Pen Angle Length
+ // Color Font Flags X Y
+ printf("4 %u %d %d -1 %d %f %f 4 0.0 0.0 %d %d %s\\001\n",
+ align, color, layer, FONT_HELVETICA_BOLD,
+ pt(size), rot / 180.0 * M_PI, cx(x), cy(y), s);
+}
+
+
+static unsigned fig_text_width(void *ctx, const char *s, unsigned size)
+{
+ /*
+ * Note that we stretch the text size, so the ratio is larger than
+ * expressed here.
+ */
+ return strlen(s) * size * 1.0;
+}
+
+
+/* ----- FIG file header --------------------------------------------------- */
+
+
+static void fig_header(void)
+{
+ printf("#FIG 3.2\n");
+ printf("Landscape\n");
+ printf("Center\n");
+ printf("Metric\n");
+ printf("A4\n");
+ printf("100.00\n");
+ printf("Single\n");
+ printf("-2\n");
+ printf("1200 2\n");
+
+ /* User32, COLOR_DARK_YELLOW */
+ printf("0 32 #848400\n");
+}
+
+
+static bool apply_vars(char *buf, int n_vars, const char **vars)
+{
+ char *p;
+ const char **var, *eq;
+ int var_len, value_len;
+
+ p = strchr(buf, '<');
+ if (!p)
+ return 0;
+ for (var = vars; var != vars + n_vars; var++) {
+ eq = strchr(*var, '=');
+ assert(eq);
+ var_len = eq - *var;
+ if (strncmp(p + 1, *var, var_len))
+ continue;
+ value_len = strlen(eq + 1);
+ memmove(p + value_len, p + var_len + 2,
+ strlen(p + var_len + 2) + 1);
+ memcpy(p, eq + 1, value_len);
+ return 1;
+ }
+ return 0;
+}
+
+
+
+static void *fig_init(int argc, char *const *argv)
+{
+ const char *template = NULL;
+ const char **vars = NULL;
+ int n_vars = 0;
+ char c;
+ int arg;
+ FILE *file;
+ char buf[1000];
+
+ while ((c = getopt(argc, argv, "t:")) != EOF)
+ switch (c) {
+ case 't':
+ template = optarg;
+ break;
+ default:
+ usage(*argv);
+ }
+
+ for (arg = optind; arg != argc; arg++) {
+ if (!strchr(argv[arg], '='))
+ usage(*argv);
+ n_vars++;
+ vars = realloc(vars, sizeof(const char *) * n_vars);
+ vars[n_vars - 1] = argv[arg];
+ }
+
+ if (!template) {
+ fig_header();
+ return NULL;
+ }
+
+ file = fopen(template, "r");
+ if (!file) {
+ perror(template);
+ exit(1);
+ }
+ while (fgets(buf, sizeof(buf), file)) {
+ while (apply_vars(buf, n_vars, vars));
+ printf("%s", buf);
+ }
+ fclose(file);
+
+ return NULL;
+}
+
+
+/* ----- Operations -------------------------------------------------------- */
+
+
+const struct gfx_ops fig_ops = {
+ .name = "fig",
+ .line = fig_line,
+ .rect = fig_rect,
+ .poly = fig_poly,
+ .circ = fig_circ,
+ .arc = fig_arc,
+ .text = fig_text,
+ .tag = fig_tag,
+ .text_width = fig_text_width,
+ .init = fig_init,
+};
diff --git a/fig.h b/fig.h
new file mode 100644
index 0000000..6e6a361
--- /dev/null
+++ b/fig.h
@@ -0,0 +1,22 @@
+/*
+ * fig.h - Generate FIG output for Eeschema items
+ *
+ * Written 2016 by Werner Almesberger
+ * Copyright 2016 by Werner Almesberger
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ */
+
+
+#ifndef FIG_H
+#define FIG_H
+
+#include "gfx.h"
+
+
+extern const struct gfx_ops fig_ops;
+
+#endif /* !FIG_H */
diff --git a/file.c b/file.c
new file mode 100644
index 0000000..1145173
--- /dev/null
+++ b/file.c
@@ -0,0 +1,76 @@
+/*
+ * file.c - Open and read a file
+ *
+ * Written 2016 by Werner Almesberger
+ * Copyright 2016 by Werner Almesberger
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ */
+
+#include <stdbool.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+
+#include "util.h"
+#include "main.h"
+#include "git-file.h"
+#include "file.h"
+
+
+bool file_cat(void *user, const char *line)
+{
+ printf("%s\n", line);
+ return 1;
+}
+
+
+static void read_from_file(FILE *file,
+ bool (*parse)(void *user, const char *line), void *user)
+{
+ char buf[1000];
+ char *nl;
+
+ while (fgets(buf, sizeof(buf), file)) {
+ nl = strchr(buf, '\n');
+ if (nl)
+ *nl = 0;
+ if (!parse(user, buf))
+ break;
+ }
+}
+
+
+void file_read(const char *name, bool (*parse)(void *user, const char *line),
+ void *user)
+{
+ FILE *file;
+ char *colon, *tmp;
+
+ file = fopen(name, "r");
+ if (file) {
+ if (verbose)
+ fprintf(stderr, "reading %s\n", name);
+ read_from_file(file, parse, user);
+ fclose(file);
+ return;
+ }
+
+ if (verbose)
+ perror(name);
+
+ colon = strchr(name, ':');
+ if (!colon) {
+ if (!verbose)
+ perror(name);
+ exit(1);
+ }
+
+ tmp = stralloc(name);
+ tmp[colon - name] = 0;
+ git_read(tmp, colon + 1, parse, user);
+ free(tmp);
+}
diff --git a/file.h b/file.h
new file mode 100644
index 0000000..a6cc170
--- /dev/null
+++ b/file.h
@@ -0,0 +1,22 @@
+/*
+ * file.h - Open and read a file
+ *
+ * Written 2016 by Werner Almesberger
+ * Copyright 2016 by Werner Almesberger
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ */
+
+#ifndef FILE_H
+#define FILE_H
+
+#include <stdbool.h>
+
+bool file_cat(void *user, const char *line);
+void file_read(const char *name, bool (*parse)(void *user, const char *line),
+ void *user);
+
+#endif /* !FILE_H */
diff --git a/gfx.c b/gfx.c
new file mode 100644
index 0000000..ba88134
--- /dev/null
+++ b/gfx.c
@@ -0,0 +1,120 @@
+/*
+ * gfx.c - Generate graphical output for Eeschema items
+ *
+ * Written 2016 by Werner Almesberger
+ * Copyright 2016 by Werner Almesberger
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ */
+
+
+#include <stdbool.h>
+
+#include "style.h"
+#include "text.h"
+#include "gfx.h"
+
+
+void *gfx_ctx;
+
+static const struct gfx_ops *gfx_ops;
+
+
+void gfx_line(int sx, int sy, int ex, int ey, int color, unsigned layer)
+{
+ if (gfx_ops->line) {
+ gfx_ops->line(gfx_ctx, sx, sy, ex, ey, color, layer);
+ return;
+ }
+
+ int vx[] = { sx, ex };
+ int vy[] = { sy, ey };
+
+ gfx_poly(2, vx, vy, color, COLOR_NONE, layer);
+}
+
+
+void gfx_rect(int sx, int sy, int ex, int ey,
+ int color, int fill_color, unsigned layer)
+{
+ if (gfx_ops->rect) {
+ gfx_ops->rect(gfx_ctx, sx, sy, ex, ey,
+ color, fill_color, layer);
+ return;
+ }
+
+ int vx[] = { sx, ex, ex, sx, sx };
+ int vy[] = { sy, sy, ey, ey, sy };
+
+ gfx_poly(5, vx, vy, color, fill_color, layer);
+}
+
+
+void gfx_poly(int points, const int x[points], const int y[points],
+ int color, int fill_color, unsigned layer)
+{
+ gfx_ops->poly(gfx_ctx, points, x, y, color, fill_color, layer);
+}
+
+
+void gfx_circ(int x, int y, int r, int color, int fill_color, unsigned layer)
+{
+ gfx_ops->circ(gfx_ctx, x, y, r, color, fill_color, layer);
+}
+
+
+void gfx_arc(int x, int y, int r, int sa, int ea,
+ int color, int fill_color, unsigned layer)
+{
+ gfx_ops->arc(gfx_ctx, x, y, r, sa, ea, color, fill_color, layer);
+}
+
+
+void gfx_text(int x, int y, const char *s, unsigned size,
+ enum text_align align, int rot, unsigned color, unsigned layer)
+{
+ gfx_ops->text(gfx_ctx, x, y, s, size, align, rot, color, layer);
+}
+
+
+void gfx_tag(const char *s,
+ unsigned points, const int x[points], int const y[points])
+{
+ if (gfx_ops->tag)
+ gfx_ops->tag(gfx_ctx, s, points, x, y);
+}
+
+
+unsigned gfx_text_width(const char *s, unsigned size)
+{
+ return gfx_ops->text_width(gfx_ctx, s, size);
+}
+
+
+void gfx_init(const struct gfx_ops *ops, int argc, char *const *argv)
+{
+ gfx_ctx = ops->init(argc, argv);
+ gfx_ops = ops;
+}
+
+
+void gfx_new_sheet(void)
+{
+ if (gfx_ops->new_sheet)
+ gfx_ops->new_sheet(gfx_ctx);
+}
+
+
+bool gfx_multi_sheet(void)
+{
+ return !!gfx_ops->new_sheet;
+}
+
+void gfx_end(void)
+{
+ if (gfx_ops->end)
+ gfx_ops->end(gfx_ctx);
+}
diff --git a/gfx.h b/gfx.h
new file mode 100644
index 0000000..d707c24
--- /dev/null
+++ b/gfx.h
@@ -0,0 +1,72 @@
+/*
+ * gfx.h - Generate graphical output for Eeschema items
+ *
+ * Written 2016 by Werner Almesberger
+ * Copyright 2016 by Werner Almesberger
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ */
+
+
+#ifndef GFX_H
+#define GFX_H
+
+#include <stdbool.h>
+
+#include "text.h"
+
+
+struct gfx_ops {
+ const char *name;
+ void (*line)(void *ctx, int sx, int sy, int ex, int ey,
+ int color, unsigned layer);
+ void (*rect)(void *ctx, int sx, int sy, int ex, int ey,
+ int color, int fill_color, unsigned layer);
+ void (*poly)(void *ctx,
+ int points, const int x[points], const int y[points],
+ int color, int fill_color, unsigned layer);
+ void (*circ)(void *ctx, int x, int y, int r,
+ int color, int fill_color, unsigned layer);
+ void (*arc)(void *ctx, int x, int y, int r, int sa, int ea,
+ int color, int fill_color, unsigned layer);
+ void (*text)(void *ctx, int x, int y, const char *s, unsigned size,
+ enum text_align align, int rot, unsigned color, unsigned layer);
+ void (*tag)(void *ctx, const char *s,
+ int points, const int x[points], const int y[points]);
+ unsigned (*text_width)(void *ctx, const char *s, unsigned size);
+ void *(*init)(int argc, char *const *argv);
+ void (*new_sheet)(void *ctx);
+ void (*end)(void *ctx);
+};
+
+
+extern void *gfx_ctx;
+
+
+/* wrappers */
+
+void gfx_line(int sx, int sy, int ex, int ey, int color, unsigned layer);
+void gfx_rect(int sx, int sy, int ex, int ey,
+ int color, int fill_color, unsigned layer);
+void gfx_poly(int points, const int x[points], const int y[points],
+ int color, int fill_color, unsigned layer);
+void gfx_circ(int x, int y, int r, int color, int fill_color, unsigned layer);
+void gfx_arc(int x, int y, int r, int sa, int ea,
+ int color, int fill_color, unsigned layer);
+void gfx_text(int x, int y, const char *s, unsigned size,
+ enum text_align align, int rot, unsigned color, unsigned layer);
+void gfx_tag(const char *s,
+ unsigned points, const int x[points], int const y[points]);
+unsigned gfx_text_width(const char *s, unsigned size);
+
+/* inititalization and termination */
+
+void gfx_init(const struct gfx_ops *ops, int argc, char *const *argv);
+void gfx_new_sheet(void);
+bool gfx_multi_sheet(void);
+void gfx_end(void);
+
+#endif /* !GFX_H */
diff --git a/git-file.c b/git-file.c
new file mode 100644
index 0000000..098f136
--- /dev/null
+++ b/git-file.c
@@ -0,0 +1,371 @@
+/*
+ * git-file.c - Open and read a file from git
+ *
+ * Written 2016 by Werner Almesberger
+ * Copyright 2016 by Werner Almesberger
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ */
+
+#define _GNU_SOURCE /* for get_current_dir_name */
+#include <stdlib.h>
+#include <stdio.h>
+#include <unistd.h>
+#include <string.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+
+#include <git2.h>
+
+#include "util.h"
+#include "main.h"
+#include "git-file.h"
+
+
+static git_repository *select_repo(const char *path)
+{
+ git_repository *repo = NULL;
+ char *tmp = stralloc(path);
+ char *slash;
+
+ /*
+ * If we can't find a repo, this may be due to the file or directory
+ * the path points to not existing in the currently checked-out tree.
+ * So we trim off elements until we find a repository.
+ */
+ while (1) {
+ if (verbose > 2)
+ fprintf(stderr, "trying \"%s\"\n", tmp);
+ if (!git_repository_open_ext(&repo, *tmp ? tmp : "/",
+ GIT_REPOSITORY_OPEN_CROSS_FS, NULL))
+ break;
+ slash = strrchr(tmp, '/');
+ if (!slash)
+ break;
+ *slash = 0;
+ }
+ free(tmp);
+ return repo;
+}
+
+
+static git_tree *pick_revision(git_repository *repo, const char *revision)
+{
+ git_commit *commit;
+ git_object *obj;
+ git_tree *tree;
+
+ if (git_revparse_single(&obj, repo, revision)) {
+ const git_error *e = giterr_last();
+
+ fprintf(stderr, "%s: %s\n",
+ git_repository_path(repo), e->message);
+ exit(1);
+ }
+
+ if (git_object_type(obj) != GIT_OBJ_COMMIT) {
+ fprintf(stderr, "%s: not a commit\n", revision);
+ exit(1);
+ }
+ commit = (git_commit *) obj;
+
+ if (git_commit_tree(&tree, commit)) {
+ const git_error *e = giterr_last();
+
+ fprintf(stderr, "%s: %s\n", revision, e->message);
+ exit(1);
+ }
+
+ return tree;
+}
+
+
+static char *canonical_path_into_repo(const char *repo_dir, const char *path)
+{
+ struct stat repo_st, path_st;
+ char *tmp, *tmp2, *slash, *tail, *real;
+ char *to;
+ const char *end, *from;
+
+ /* identify inode of repo root */
+
+ if (stat(repo_dir, &repo_st) < 0) {
+ perror(repo_dir);
+ exit(1);
+ }
+ if (!S_ISDIR(repo_st.st_mode)) {
+ fprintf(stderr, "%s: not a directory\n", repo_dir);
+ exit(1);
+ }
+
+ /* convert relative paths to absolute */
+
+ if (*path == '/') {
+ tmp = stralloc(path);
+ } else {
+ char *cwd = get_current_dir_name();
+
+ tmp = alloc_size(strlen(cwd) + 1 + strlen(path) + 1);
+ sprintf(tmp, "%s/%s", cwd, path);
+ free(cwd);
+ }
+
+ /* remove trailing / */
+
+ slash = strrchr(tmp, '/');
+ if (slash && slash != tmp && !slash[1])
+ *slash = 0;
+
+
+ /*
+ * If path does point to inexistent object, separate into the part that
+ * is valid on the current system and the tail containing dead things.
+ */
+ end = tail = strchr(tmp, 0);
+
+ while (1) {
+ if (verbose > 2)
+ fprintf(stderr, "probing \"%s\" tail \"%s\"\n",
+ tmp, tail);
+ if (stat(tmp, &path_st) == 0)
+ break;
+ if (!tmp[1]) {
+ fprintf(stderr, "%s: cannot resolve\n", path);
+ exit(1);
+ }
+ slash = strrchr(tmp, '/');
+ if (tail != end)
+ tail[-1] = '/';
+ tail = slash + 1;
+ *slash = 0;
+ }
+
+ /* remove . and .. from tail */
+
+ if (verbose > 2)
+ fprintf(stderr, "input tail \"%s\"\n", tail);
+ from = to = tail;
+ while (1) {
+ if (!strncmp(from, "./", 2)) {
+ from += 2;
+ continue;
+ }
+ if (!strcmp(from, "."))
+ break;
+ if (strncmp(from, "../", 3) && strcmp(from, "..")) {
+ while (*from) {
+ *to++ = *from++;
+ if (from[-1] == '/')
+ break;
+ }
+ if (!*from)
+ break;
+ }
+ if (to == tail) {
+ /*
+ * We have something like this:
+ * /home/repo/dead/../../foo
+ */
+ fprintf(stderr, "%s: can't climb out of dead path\n",
+ path);
+ exit(1);
+ }
+
+ /*
+ * We have something like
+ * "foo/" -> ""
+ * or
+ * "foo/bar/" -> "foo/"
+ * where "to" points to the end.
+ */
+ to--;
+ while (to != tail && to[-1] != '/')
+ to--;
+ }
+ *to = 0;
+ if (verbose > 2)
+ fprintf(stderr, "output tail \"%s\"\n", tail);
+
+ /* resolve all symlinks */
+
+ real = realpath(tmp, NULL);
+ if (verbose > 2)
+ fprintf(stderr, "realpath(\"%s\") = \"%s\"\n", tmp, real);
+
+ /* append tail */
+
+ if (*tail) {
+ tmp2 = alloc_size(strlen(real) + 1 + strlen(tail) + 1);
+ sprintf(tmp2, "%s/%s", real, tail);
+ free(real);
+ } else {
+ tmp2 = real;
+ }
+ free(tmp);
+ tmp = tmp2;
+
+ if (verbose > 1)
+ fprintf(stderr, "full object path \"%s\"\n", tmp);
+
+ /* find which part of our path is inside the repo */
+
+ end = tail = strchr(tmp, 0);
+ while (1) {
+ if (verbose > 2)
+ fprintf(stderr, "trying \"%s\" tail \"%s\"\n",
+ tmp, tail);
+
+ if (stat(tmp, &path_st) == 0 &&
+ path_st.st_dev == repo_st.st_dev &&
+ path_st.st_ino == repo_st.st_ino)
+ break;
+
+ /* "this cannot happen" */
+ if (tail == tmp) {
+ fprintf(stderr,
+ "divergent paths:\nrepo \"%s\"\nobject \"%s\"\n",
+ repo_dir, tmp);
+ exit(1);
+ }
+
+ slash = strrchr(tmp, '/');
+ if (tail != end)
+ tail[-1] = '/';
+ tail = slash + 1;
+ *slash = 0;
+ }
+
+ if (verbose > 1)
+ fprintf(stderr, "path in repo \"%s\"\n", tail);
+
+ tmp2 = stralloc(tail);
+ free(tmp);
+ return tmp2;
+}
+
+
+static git_tree_entry *find_file(git_repository *repo, git_tree *tree,
+ const char *path)
+{
+ git_tree_entry *entry;
+ char *repo_path = stralloc(git_repository_path(repo));
+ char *slash, *canon_path;
+ int len;
+
+ /* remove trailing / from repo_path */
+ slash = strrchr(repo_path, '/');
+ if (slash && slash != repo_path && !slash[1])
+ *slash = 0;
+
+ len = strlen(repo_path);
+ if (len >= 5 && !strcmp(repo_path + len - 5, "/.git"))
+ repo_path[len == 5 ? 1 : len - 5] = 0;
+
+ if (verbose > 1)
+ fprintf(stderr, "repo dir \"%s\"\n", repo_path);
+
+ canon_path = canonical_path_into_repo(repo_path, path);
+
+ if (git_tree_entry_bypath(&entry, tree, canon_path)) {
+ const git_error *e = giterr_last();
+
+ fprintf(stderr, "%s: %s\n", path, e->message);
+ exit(1);
+ }
+ free(canon_path);
+
+ return entry;
+}
+
+
+static const void *get_data(git_repository *repo, git_tree_entry *entry,
+ unsigned *size)
+{
+ git_object *obj;
+ git_blob *blob;
+
+ if (git_tree_entry_type(entry) != GIT_OBJ_BLOB) {
+ fprintf(stderr, "entry is not a blob\n");
+ exit(1);
+ }
+ if (git_tree_entry_to_object(&obj, repo, entry)) {
+ const git_error *e = giterr_last();
+
+ fprintf(stderr, "%s\n", e->message);
+ exit(1);
+ }
+
+ blob = (git_blob *) obj;
+ *size = git_blob_rawsize(blob);
+ return git_blob_rawcontent(blob);
+}
+
+
+static bool send_line(const char *s, unsigned len,
+ bool (*parse)(void *user, const char *line), void *user)
+{
+ char *tmp = alloc_size(len + 1);
+ bool res;
+
+ memcpy(tmp, s, len);
+ tmp[len] = 0;
+ res = parse(user, tmp);
+ free(tmp);
+ return res;
+}
+
+
+static void send_data(const char *data, unsigned size,
+ bool (*parse)(void *user, const char *line), void *user)
+{
+ const char *end = data + size;
+ const char *p = data;
+ const char *nl;
+
+ while (p != end) {
+ nl = memchr(p, '\n', end - p);
+ if (!nl) {
+ send_line(p, end - p, parse, user);
+ return;
+ }
+ if (!send_line(p, nl - p, parse, user))
+ return;
+ p = nl + 1;
+ }
+}
+
+
+void git_read(const char *revision, const char *name,
+ bool (*parse)(void *user, const char *line), void *user)
+{
+ static bool initialized = 0;
+ git_repository *repo;
+ git_tree *tree;
+ git_tree_entry *entry;
+ const void *data;
+ unsigned size;
+
+ if (!initialized) {
+ git_libgit2_init();
+ initialized = 1;
+ }
+
+ repo = select_repo(name);
+ if (!repo) {
+ fprintf(stderr, "%s:%s not found\n", revision, name);
+ exit(1);
+ }
+ if (verbose > 1)
+ fprintf(stderr, "using repository %s\n",
+ git_repository_path(repo));
+
+ tree = pick_revision(repo, revision);
+ entry = find_file(repo, tree, name);
+ if (verbose)
+ fprintf(stderr, "reading %s:%s\n", revision, name);
+ data = get_data(repo, entry, &size);
+ send_data(data, size, parse, user);
+}
diff --git a/git-file.h b/git-file.h
new file mode 100644
index 0000000..4642528
--- /dev/null
+++ b/git-file.h
@@ -0,0 +1,22 @@
+/*
+ * git-file.h - Open and read a file from git
+ *
+ * Written 2016 by Werner Almesberger
+ * Copyright 2016 by Werner Almesberger
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ */
+
+#ifndef GIT_FILE_H
+#define GIT_FILE_H
+
+#include <stdbool.h>
+
+
+void git_read(const char *revision, const char *name,
+ bool (*parse)(void *user, const char *line), void *user);
+
+#endif /* !GIT_FILE_H */
diff --git a/lib-parse.c b/lib-parse.c
new file mode 100644
index 0000000..21d37ff
--- /dev/null
+++ b/lib-parse.c
@@ -0,0 +1,268 @@
+/*
+ * lib.c - Parse Eeschema .lib file
+ *
+ * Written 2016 by Werner Almesberger
+ * Copyright 2016 by Werner Almesberger
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ */
+
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <assert.h>
+
+#include "util.h"
+#include "text.h"
+#include "file.h"
+#include "lib.h"
+
+
+/* ----- Text -------------------------------------------------------------- */
+
+
+static enum text_style decode_style(const char *s)
+{
+ if (!strcmp(s, "Normal"))
+ return text_normal;
+ assert(0);
+}
+
+
+/* ----- Polygons ---------------------------------------------------------- */
+
+
+static bool parse_poly(struct lib_poly *poly, const char *line, int points)
+{
+ int i, n;
+
+ poly->points = points;
+ poly->x = alloc_size(sizeof(int) * points);
+ poly->y = alloc_size(sizeof(int) * points);
+ for (i = 0; i != points; i++) {
+ if (sscanf(line, "%d %d %n",
+ poly->x + i, poly->y + i, &n) != 2)
+ return 0;
+ line += n;
+ }
+ if (sscanf(line, "%c", &poly->fill) != 1)
+ return 0;
+ return 1;
+}
+
+
+/* ----- Definitions ------------------------------------------------------- */
+
+
+static bool parse_def(struct lib *lib, const char *line)
+{
+ char *s;
+ char draw_num, draw_name;
+ unsigned name_offset;
+ unsigned units;
+
+ if (sscanf(line, "DEF %ms %*s %*d %u %c %c %u",
+ &s, &name_offset, &draw_num, &draw_name, &units) != 5)
+ return 0;
+
+ lib->curr_comp = alloc_type(struct comp);
+ if (*s == '~')
+ s++;
+ lib->curr_comp->name = s;
+ lib->curr_comp->units = units;
+
+ lib->curr_comp->visible = 0;
+ lib->curr_comp->show_pin_name = draw_name == 'Y';
+ lib->curr_comp->show_pin_num = draw_num == 'Y';
+ lib->curr_comp->name_offset = name_offset;
+
+ lib->curr_comp->objs = NULL;
+ lib->next_obj = &lib->curr_comp->objs;
+
+ lib->curr_comp->next = NULL;
+ *lib->next_comp = lib->curr_comp;
+ lib->next_comp = &lib->curr_comp->next;
+
+ return 1;
+}
+
+
+/* ----- Arcs -------------------------------------------------------------- */
+
+
+static bool parse_arc(struct lib_obj *obj, const char *line)
+{
+ struct lib_arc *arc = &obj->u.arc;
+ int a1, a2;
+
+ if (sscanf(line, "A %d %d %d %d %d %u %u %u %c",
+ &arc->x, &arc->y, &arc->r, &a1, &a2, &obj->unit, &obj->convert,
+ &arc->thick, &arc->fill) != 9)
+ return 0;
+
+ /*
+ * KiCad arcs can be clockwise or counter-clockwise. They must always
+ * be smaller than 180 degrees.
+ */
+
+ while (a1 < 0)
+ a1 += 3600;
+ while (a2 < 0)
+ a2 += 3600;
+ a1 %= 3600;
+ a2 %= 3600;
+ if (a2 < a1)
+ a2 += 3600;
+ assert(a2 - a1 != 1800);
+ if (a2 - a1 > 1800)
+ swap(a1, a2);
+
+ arc->start_a = (a1 % 3600) / 10;
+ arc->end_a = (a2 % 3600) / 10;
+
+ return 1;
+}
+
+
+/* ----- Library parser ---------------------------------------------------- */
+
+
+static bool lib_parse_line(void *user, const char *line)
+{
+ struct lib *lib = user;
+ int n = 0;
+ unsigned points;
+ struct lib_obj *obj;
+ char *style;
+ unsigned zero1, zero2;
+ char vis;
+
+ lib->lineno++;
+
+ switch (lib->state) {
+ case lib_skip:
+ if (parse_def(lib, line)) {
+ lib->state = lib_def;
+ return 1;
+ }
+ return 1;
+ case lib_def:
+ if (sscanf(line, "DRAW%n", &n) == 0 && n) {
+ lib->state = lib_draw;
+ return 1;
+ }
+ if (sscanf(line, "F%d \"\" %*d %*d %*d %*c %c", &n, &vis) == 2
+ || sscanf(line, "F%d \"%*[^\"]\" %*d %*d %*d %*c %c",
+ &n, &vis) == 2) {
+ if (vis == 'V')
+ lib->curr_comp->visible |= 1 << n;
+ return 1;
+ }
+ /* @@@ explicitly ignore FPLIST */
+ return 1;
+ case lib_draw:
+ if (sscanf(line, "ENDDRAW%n", &n) == 0 && n) {
+ lib->state = lib_skip;
+ return 1;
+ }
+
+ obj = alloc_type(struct lib_obj);
+ obj->next = NULL;
+ *lib->next_obj = obj;
+ lib->next_obj = &obj->next;
+
+ if (sscanf(line, "P %u %u %u %u %n",
+ &points, &obj->unit, &obj->convert, &obj->u.poly.thick,
+ &n) == 4) {
+ obj->type = lib_obj_poly;
+ if (parse_poly(&obj->u.poly, line + n, points))
+ return 1;
+ break;
+ }
+ if (sscanf(line, "S %d %d %d %d %u %u %d %c",
+ &obj->u.rect.sx, &obj->u.rect.sy, &obj->u.rect.ex,
+ &obj->u.rect.ey, &obj->unit, &obj->convert,
+ &obj->u.rect.thick, &obj->u.rect.fill) == 8) {
+ obj->type = lib_obj_rect;
+ return 1;
+ }
+ if (sscanf(line, "C %d %d %d %u %u %d %c",
+ &obj->u.circ.x, &obj->u.circ.y, &obj->u.circ.r,
+ &obj->unit, &obj->convert, &obj->u.circ.thick,
+ &obj->u.circ.fill) == 7) {
+ obj->type = lib_obj_circ;
+ return 1;
+ }
+ if (parse_arc(obj, line)) {
+ obj->type = lib_obj_arc;
+ return 1;
+ }
+ n = sscanf(line,
+ "T %d %d %d %d %u %u %u \"%m[^\"]\" %ms %u %c %c",
+ &obj->u.text.orient, &obj->u.text.x, &obj->u.text.y,
+ &obj->u.text.dim, &zero1, &obj->unit, &obj->convert,
+ &obj->u.text.s, &style, &zero2,
+ &obj->u.text.hor_align, &obj->u.text.vert_align);
+ if (n != 12) {
+ n = sscanf(line,
+ "T %d %d %d %d %u %u %u %ms %ms %u %c %c",
+ &obj->u.text.orient, &obj->u.text.x, &obj->u.text.y,
+ &obj->u.text.dim, &zero1, &obj->unit, &obj->convert,
+ &obj->u.text.s, &style, &zero2,
+ &obj->u.text.hor_align, &obj->u.text.vert_align);
+ while (n == 12) {
+ char *tilde;
+
+ tilde = strchr(obj->u.text.s, '~');
+ if (!tilde)
+ break;
+ *tilde = ' ';
+ }
+ }
+ /*
+ * zero2 seems to be the font style: 0 = normal, 1 = bold ?
+ */
+ if (n == 12) {
+ if (zero1) {
+ fprintf(stderr, "%u: only understand 0 x x\n"
+ "\"%s\"\n", lib->lineno, line);
+ exit(1);
+ }
+ obj->u.text.style = decode_style(style);
+ obj->type = lib_obj_text;
+ return 1;
+ }
+ if (sscanf(line, "X %ms %ms %d %d %d %c %d %d %u %u %c",
+ &obj->u.pin.name, &obj->u.pin.number,
+ &obj->u.pin.x, &obj->u.pin.y, &obj->u.pin.length,
+ &obj->u.pin.orient,
+ &obj->u.pin.number_size, &obj->u.pin.name_size,
+ &obj->unit, &obj->convert, &obj->u.pin.etype) == 11) {
+ obj->type = lib_obj_pin;
+ return 1;
+ }
+ break;
+ default:
+ abort();
+ }
+ fprintf(stderr, "%u: cannot parse\n\"%s\"\n", lib->lineno, line);
+ exit(1);
+}
+
+
+void lib_parse(struct lib *lib, const char *file)
+{
+ lib->state = lib_skip;
+ lib->lineno = 0;
+ file_read(file, lib_parse_line, lib);
+}
+
+
+void lib_init(struct lib *lib)
+{
+ lib->comps = NULL;
+ lib->next_comp = &lib->comps;
+}
diff --git a/lib-render.c b/lib-render.c
new file mode 100644
index 0000000..fe20c9a
--- /dev/null
+++ b/lib-render.c
@@ -0,0 +1,396 @@
+/*
+ * lib.c - Render component from library
+ *
+ * Written 2016 by Werner Almesberger
+ * Copyright 2016 by Werner Almesberger
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ */
+
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <assert.h>
+
+#include "util.h"
+#include "misc.h"
+#include "style.h"
+#include "gfx.h"
+#include "text.h"
+#include "sch.h"
+#include "lib.h"
+
+
+/* ----- Drawing ----------------------------------------------------------- */
+
+
+static void draw_poly(const struct lib_poly *poly, const int m[6])
+{
+ int n = poly->points;
+ int x[n];
+ int y[n];
+ int i;
+
+ for (i = 0; i != n; i++) {
+ x[i] = mx(poly->x[i], poly->y[i], m);
+ y[i] = my(poly->x[i], poly->y[i], m);
+ }
+
+ gfx_poly(n, x, y, COLOR_COMP_DWG, COLOR_NONE, LAYER_COMP_DWG);
+
+ switch (poly->fill) {
+ case 'N':
+ break;
+ case 'F':
+ gfx_poly(n, x, y, COLOR_NONE, COLOR_COMP_DWG,
+ LAYER_COMP_DWG_BG);
+ break;
+ case 'f':
+ gfx_poly(n, x, y, COLOR_NONE, COLOR_COMP_DWG_BG,
+ LAYER_COMP_DWG_BG);
+ break;
+ default:
+ abort();
+ }
+}
+
+
+static void draw_rect(const struct lib_rect *rect, const int m[6])
+{
+ int sx = mx(rect->sx, rect->sy, m);
+ int sy = my(rect->sx, rect->sy, m);
+ int ex = mx(rect->ex, rect->ey, m);
+ int ey = my(rect->ex, rect->ey, m);
+
+ gfx_rect(sx, sy, ex, ey, COLOR_COMP_DWG, COLOR_NONE, LAYER_COMP_DWG);
+
+ switch (rect->fill) {
+ case 'N':
+ break;
+ case 'F':
+ gfx_rect(sx, sy, ex, ey, COLOR_NONE, COLOR_COMP_DWG,
+ LAYER_COMP_DWG_BG);
+ break;
+ case 'f':
+ gfx_rect(sx, sy, ex, ey, COLOR_NONE, COLOR_COMP_DWG_BG,
+ LAYER_COMP_DWG_BG);
+ break;
+ default:
+ abort();
+ }
+}
+
+
+static void draw_circ(const struct lib_circ *circ, const int m[6])
+{
+ int x = mx(circ->x, circ->y, m);
+ int y = my(circ->x, circ->y, m);
+ int r = circ->r;
+
+ gfx_circ(x, y, r, COLOR_COMP_DWG, COLOR_NONE, LAYER_COMP_DWG);
+
+ switch (circ->fill) {
+ case 'N':
+ break;
+ case 'F':
+ gfx_circ(x, y, r, COLOR_NONE, COLOR_COMP_DWG,
+ LAYER_COMP_DWG_BG);
+ break;
+ case 'f':
+ gfx_circ(x, y, r, COLOR_NONE, COLOR_COMP_DWG_BG,
+ LAYER_COMP_DWG_BG);
+ break;
+ default:
+ abort();
+ }
+}
+
+
+static void draw_arc(const struct lib_arc *arc, const int m[6])
+{
+ int a = matrix_to_angle(m);
+ int x = mx(arc->x, arc->y, m);
+ int y = my(arc->x, arc->y, m);
+ int sa = angle_add(arc->start_a, a);
+ int ea = angle_add(arc->end_a, a);
+
+ if (matrix_is_mirrored(m)) {
+ sa = 180 - sa;
+ ea = 180 - ea;
+ while (ea < sa)
+ ea += 360;
+ while (ea - sa > 360)
+ ea -= 360;
+ if (ea - sa >= 180) {
+ swap(sa, ea);
+ sa += 360;
+ }
+ }
+
+ gfx_arc(x, y, arc->r, sa, ea,
+ COLOR_COMP_DWG, COLOR_NONE, LAYER_COMP_DWG);
+
+ assert(arc->fill == 'N');
+}
+
+
+static void draw_pin_name(const struct comp *comp, const struct lib_pin *pin,
+ const int m[6], int dx, int dy, int rot, enum text_align hor)
+{
+ int ox, oy, sx, sy;
+
+ if (comp->name_offset) {
+ ox = dx * (pin->length + comp->name_offset);
+ oy = dy * (pin->length + comp->name_offset);
+ sx = sy = 0;
+ } else {
+ ox = dx * pin->length / 2;
+ oy = dy * pin->length / 2;
+ sx = mxr(-dy * PIN_NUM_OFFSET, dx * PIN_NUM_OFFSET, m);
+ sy = myr(-dy * PIN_NUM_OFFSET, dx * PIN_NUM_OFFSET, m);
+ if (sx > 0)
+ sx = -sx;
+ if (sy > 0)
+ sy = -sy;
+ }
+
+ struct text txt = {
+ .s = pin->name,
+ .x = mx(pin->x + ox, pin->y + oy, m) + sx,
+ .y = my(pin->x + ox, pin->y + oy, m) + sy,
+ .size = pin->name_size,
+ .rot = rot,
+ .hor = comp->name_offset ? hor : text_mid,
+ .vert = comp->name_offset ? text_mid : text_min,
+ };
+
+ text_rot(&txt, matrix_to_angle(m));
+ if (matrix_is_mirrored(m)) {
+ if ((txt.rot % 180) == 0)
+ txt.hor = text_flip(txt.hor);
+ else
+ txt.vert = text_flip(txt.vert);
+ }
+
+ switch (txt.rot) {
+ case 180:
+ case 270:
+ text_flip_x(&txt);
+ break;
+ default:
+ break;
+ }
+
+ text_fig(&txt, COLOR_PIN_NAME, LAYER_PIN_NAME);
+}
+
+
+static void draw_pin_num(const struct comp *comp, const struct lib_pin *pin,
+ const int m[6], int dx, int dy, int rot, enum text_align hor)
+{
+ int ox, oy, sx, sy;
+
+ ox = dx * pin->length / 2;
+ oy = dy * pin->length / 2;
+
+ sx = mxr(-dy * PIN_NUM_OFFSET, dx * PIN_NUM_OFFSET, m);
+ sy = myr(-dy * PIN_NUM_OFFSET, dx * PIN_NUM_OFFSET, m);
+ if (sx > 0)
+ sx = -sx;
+ if (sy > 0)
+ sy = -sy;
+
+ if (!comp->name_offset) {
+ sx = -sx;
+ sy = -sy;
+ }
+
+ struct text txt = {
+ .s = pin->number,
+ .x = mx(pin->x + ox, pin->y + oy, m) + sx,
+ .y = my(pin->x + ox, pin->y + oy, m) + sy,
+ .size = pin->number_size,
+ .rot = rot,
+ .hor = text_mid,
+ .vert = comp->name_offset ? text_min : text_max,
+ };
+
+ text_rot(&txt, matrix_to_angle(m) % 180);
+ if (matrix_is_mirrored(m)) {
+ switch (txt.rot) {
+ case 0:
+ txt.hor = text_flip(txt.hor);
+ break;
+ case 90:
+ break;
+ case 180:
+ txt.hor = text_flip(txt.hor);
+ break;
+ case 270:
+ break;
+ }
+ }
+
+ switch (txt.rot) {
+ case 180:
+ case 270:
+ text_flip_x(&txt);
+ break;
+ default:
+ break;
+ }
+
+ text_fig(&txt, COLOR_PIN_NUMBER, LAYER_PIN_NUMBER);
+}
+
+
+static void draw_pin(const struct comp *comp, const struct lib_pin *pin,
+ const int m[6])
+{
+ int x[2], y[2];
+ int dx = 0, dy = 0;
+ int rot;
+ enum text_align hor;
+
+ switch (pin->orient) {
+ case 'U':
+ dy = 1;
+ rot = 90;
+ hor = text_min;
+ break;
+ case 'D':
+ dy = -1;
+ rot = 90;
+ hor = text_max;
+ break;
+ case 'R':
+ dx = 1;
+ rot = 0;
+ hor = text_min;
+ break;
+ case 'L':
+ dx = -1;
+ rot = 0;
+ hor = text_max;
+ break;
+ default:
+ abort();
+ }
+ x[0] = mx(pin->x, pin->y, m);
+ y[0] = my(pin->x, pin->y, m);
+ x[1] = mx(pin->x + dx * pin->length, pin->y + dy * pin->length, m);
+ y[1] = my(pin->x + dx * pin->length, pin->y + dy * pin->length, m);
+ gfx_poly(2, x, y, COLOR_COMP_DWG, COLOR_NONE, LAYER_COMP_DWG);
+
+ if (comp->show_pin_name)
+ draw_pin_name(comp, pin, m, dx, dy, rot, hor);
+
+ if (comp->show_pin_num)
+ draw_pin_num(comp, pin, m, dx, dy, rot, hor);
+}
+
+
+static void draw_text(const struct lib_text *text, const int m[6])
+{
+ struct text txt = {
+ .s = text->s,
+ .size = text->dim,
+ .x = mx(text->x, text->y, m),
+ .y = my(text->x, text->y, m),
+ .rot = angle_add(text->orient / 10, matrix_to_angle(m)),
+ };
+
+ decode_alignment(&txt, text->hor_align, text->vert_align);
+
+ switch (txt.rot) {
+ case 180:
+ case 270:
+ /* @@@ consolidate this with text_flip_x */
+ txt.rot = angle_add(txt.rot, 180);
+ txt.hor = text_flip(txt.hor);
+ txt.vert = text_flip(txt.vert);
+// text_flip_x(&txt);
+ break;
+ default:
+ break;
+ }
+
+ if (matrix_is_mirrored(m))
+ switch (txt.rot) {
+ case 0:
+ case 180:
+ txt.hor = text_flip(txt.hor);
+ break;
+ case 90:
+ case 270:
+ txt.vert = text_flip(txt.vert);
+ break;
+ default:
+ abort();
+ }
+
+ text_fig(&txt, COLOR_COMP_DWG, WIDTH_COMP_DWG);
+}
+
+
+static void draw(const struct comp *comp, const struct lib_obj *obj,
+ const int m[6])
+{
+ switch (obj->type) {
+ case lib_obj_poly:
+ draw_poly(&obj->u.poly, m);
+ break;
+ case lib_obj_rect:
+ draw_rect(&obj->u.rect, m);
+ break;
+ case lib_obj_circ:
+ draw_circ(&obj->u.circ, m);
+ break;
+ case lib_obj_arc:
+ draw_arc(&obj->u.arc, m);
+ break;
+ case lib_obj_text:
+ draw_text(&obj->u.text, m);
+ break;
+ case lib_obj_pin:
+ draw_pin(comp, &obj->u.pin, m);
+ break;
+ default:
+ abort();
+ }
+}
+
+
+const struct comp *lib_find(const struct lib *lib, const char *name)
+{
+ const struct comp *comp;
+
+ for (comp = lib->comps; comp; comp = comp->next)
+ if (!strcmp(comp->name, name))
+ return comp;
+ fprintf(stderr, "\"%s\" not found\n", name);
+ exit(1);
+}
+
+
+bool lib_field_visible(const struct comp *comp, int n)
+{
+ return (comp->visible >> n) & 1;
+}
+
+
+void lib_render(const struct comp *comp, unsigned unit, const int m[4])
+{
+ const struct lib_obj *obj;
+
+ if (!unit)
+ unit = 1;
+ for (obj = comp->objs; obj; obj = obj->next) {
+ if (obj->unit && obj->unit != unit)
+ continue;
+ draw(comp, obj, m);
+ }
+}
diff --git a/lib.h b/lib.h
new file mode 100644
index 0000000..4f1e9f0
--- /dev/null
+++ b/lib.h
@@ -0,0 +1,124 @@
+/*
+ * lib.h - Parse Eeschema .lib file
+ *
+ * Written 2016 by Werner Almesberger
+ * Copyright 2016 by Werner Almesberger
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ */
+
+
+#ifndef LIB_H
+#define LIB_H
+
+#include <stdbool.h>
+
+
+enum lib_state {
+ lib_skip, /* before a definition */
+ lib_def, /* in definition */
+ lib_draw, /* in drawings */
+};
+
+struct lib_obj {
+ enum lib_obj_type {
+ lib_obj_poly,
+ lib_obj_rect,
+ lib_obj_circ,
+ lib_obj_arc,
+ lib_obj_text,
+ lib_obj_pin,
+ } type;
+ unsigned unit;
+ unsigned convert;
+ union {
+ struct lib_poly {
+ int thick;
+ char fill;
+ int points;
+ int *x;
+ int *y;
+ } poly;
+ struct lib_rect {
+ int thick;
+ char fill;
+ int sx, sy;
+ int ex, ey;
+ } rect;
+ struct lib_circ {
+ int x, y;
+ int r;
+ int thick;
+ char fill;
+ } circ;
+ struct lib_arc {
+ int x, y;
+ int r;
+ int start_a, end_a;
+ int thick;
+ char fill;
+ } arc;
+ struct lib_text {
+ int orient;
+ int x, y;
+ int dim;
+ char *s;
+ enum text_style style;
+ char hor_align;
+ char vert_align;
+ } text;
+ struct lib_pin {
+ char *name;
+ char *number;
+ int x, y;
+ int length;
+ char orient;
+ int number_size;
+ int name_size;
+ char etype;
+ // @@@ shape
+ } pin;
+ } u;
+ struct lib_obj *next;
+};
+
+struct comp {
+ const char *name;
+ unsigned units;
+
+ unsigned visible; /* visible fields, bit mask */
+ bool show_pin_name;
+ bool show_pin_num;
+ unsigned name_offset;
+
+ struct lib_obj *objs;
+ struct comp *next;
+};
+
+struct lib {
+ enum lib_state state;
+ unsigned lineno;
+
+ struct comp *comps;
+
+ struct comp *curr_comp; /* current component */
+ struct comp **next_comp;
+ struct lib_obj **next_obj;
+
+};
+
+
+extern struct comp *comps;
+
+
+const struct comp *lib_find(const struct lib *lib, const char *name);
+bool lib_field_visible(const struct comp *comp, int n);
+void lib_render(const struct comp *comp, unsigned unit, const int m[6]);
+
+void lib_parse(struct lib *lib, const char *file);
+void lib_init(struct lib *lib);
+
+#endif /* !LIB_H */
diff --git a/main.c b/main.c
new file mode 100644
index 0000000..7e02d0e
--- /dev/null
+++ b/main.c
@@ -0,0 +1,172 @@
+/*
+ * main.c - Convert Eeschema schematics to FIG
+ *
+ * Written 2016 by Werner Almesberger
+ * Copyright 2016 by Werner Almesberger
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ */
+
+
+#include <stdbool.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <unistd.h>
+#include <string.h>
+
+#include "util.h"
+#include "fig.h"
+#include "cro.h"
+#include "diff.h"
+#include "gfx.h"
+#include "file.h"
+#include "lib.h"
+#include "sch.h"
+#include "main.h"
+
+
+int verbose = 0;
+
+
+static struct gfx_ops const *ops_list[] = {
+ &fig_ops,
+ &cro_png_ops,
+ &cro_pdf_ops,
+ &diff_ops,
+};
+
+
+void usage(const char *name)
+{
+ fprintf(stderr,
+"usage: %s [-r] [-v ...] [[rev:]file.lib ...] [rev:]file.sch\n"
+" %*s-- driver_spec\n"
+" %s [-v ...] -C [rev:]file\n"
+"\n"
+" rev git revision\n"
+" -r recurse into sub-sheets\n"
+" -v increase verbosity of diagnostic output\n"
+" -C 'cat' the file to standard output\n"
+"\n"
+"FIG driver spec:\n"
+" fig [-t template.fig] [var=value ...]\n"
+"\n"
+" var=value substitute \"<var>\" with \"value\" in template\n"
+" -t template.fig merge this file with generated output\n"
+"\n"
+"Cairo PNG driver spec:\n"
+" png [-o output.png] [-s scale]\n"
+"\n"
+" -o output.png generate PNG output and write to specified file\n"
+" -s scale scale by indicated factor (default: 1.0)\n"
+"\n"
+"Cairo PDF driver spec:\n"
+" pdf [-o output.pdf] [-s scale]\n"
+"\n"
+" see PNG\n"
+"\n"
+"Diff driver spec:\n"
+" diff [-o output.pdf] [-s scale] [file.lib ...] file.sch\n"
+"\n"
+" see PNG\n"
+ , name, (int) strlen(name) + 1, "", name);
+ exit(1);
+}
+
+
+int main(int argc, char *const *argv)
+{
+ struct lib lib;
+ struct sch_ctx sch_ctx;
+ bool recurse = 0;
+ const char *cat = NULL;
+ char c;
+ int arg, dashdash;
+ int gfx_argc;
+ char **gfx_argv;
+ const struct gfx_ops **ops = ops_list;
+
+ for (dashdash = 1; dashdash != argc; dashdash++)
+ if (!strcmp(argv[dashdash], "--"))
+ break;
+
+ while ((c = getopt(dashdash, argv, "rvC:")) != EOF)
+ switch (c) {
+ case 'r':
+ recurse = 1;
+ break;
+ case 'v':
+ verbose++;
+ break;
+ case 'C':
+ cat = optarg;
+ break;
+ default:
+ usage(*argv);
+ }
+
+ if (cat) {
+ if (argc != optind)
+ usage(*argv);
+ file_read(cat, file_cat, NULL);
+ return 0;
+ }
+
+ if (dashdash - optind < 1)
+ usage(*argv);
+
+ lib_init(&lib);
+ for (arg = optind; arg != dashdash - 1; arg++)
+ lib_parse(&lib, argv[arg]);
+
+ if (dashdash == argc) {
+ gfx_argc = 1;
+ gfx_argv = alloc_size(sizeof(const char *) * 2);
+ gfx_argv[0] = (char *) (*ops)->name;
+ gfx_argv[1] = NULL;
+ } else {
+ gfx_argc = argc - dashdash - 1;
+ if (!gfx_argc)
+ usage(*argv);
+ gfx_argv = alloc_size(sizeof(const char *) * (gfx_argc + 1));
+ memcpy(gfx_argv, argv + dashdash + 1,
+ sizeof(const char *) * (gfx_argc + 1));
+
+ for (ops = ops_list; ops != ARRAY_END(ops_list); ops++)
+ if (!strcmp((*ops)->name, *gfx_argv))
+ goto found;
+ fprintf(stderr, "graphics backend \"%s\" not found\n",
+ *gfx_argv);
+ exit(1);
+found:
+ ;
+ }
+
+ optind = 0; /* reset getopt */
+
+ sch_init(&sch_ctx, recurse);
+ sch_parse(&sch_ctx, argv[dashdash - 1], &lib);
+ gfx_init(*ops, gfx_argc, gfx_argv);
+ if (recurse) {
+ const struct sheet *sheet;
+
+ if (!gfx_multi_sheet()) {
+ fprintf(stderr,
+ "graphics backend only supports single sheet\n");
+ exit(1);
+ }
+ for (sheet = sch_ctx.sheets; sheet; sheet = sheet->next) {
+ sch_render(sheet);
+ if (sheet->next)
+ gfx_new_sheet();
+ }
+ } else {
+ sch_render(sch_ctx.sheets);
+ }
+ gfx_end();
+
+ return 0;
+}
diff --git a/main.h b/main.h
new file mode 100644
index 0000000..4a3bdc1
--- /dev/null
+++ b/main.h
@@ -0,0 +1,31 @@
+/*
+ * main.h - Convert Eeschema schematics to FIG
+ *
+ * Written 2016 by Werner Almesberger
+ * Copyright 2016 by Werner Almesberger
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ */
+
+#ifndef MAIN_H
+#define MAIN_H
+
+#include <stdbool.h>
+
+
+/*
+ * 0: no progress indications
+ * 1: reasonable progress indications
+ * 2: verbose output
+ * > 2: go wild !
+ */
+
+extern int verbose;
+
+
+void usage(const char *name);
+
+#endif /* !MAIN_H */
diff --git a/misc.c b/misc.c
new file mode 100644
index 0000000..5e2401c
--- /dev/null
+++ b/misc.c
@@ -0,0 +1,114 @@
+/*
+ * misc.c - Helper functions for geometry and attributes
+ *
+ * Written 2016 by Werner Almesberger
+ * Copyright 2016 Werner Almesberger
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ */
+
+
+#include <stdbool.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <assert.h>
+
+#include "misc.h"
+
+
+static bool eq(const int m[6], int xx, int xy, int yx, int yy)
+{
+ return m[1] == xx && m[2] == xy && m[4] == yx && m[5] == yy;
+}
+
+
+unsigned matrix_to_angle(const int m[6])
+{
+ if (eq(m, 1, 0, 0, -1))
+ return 0;
+ if (eq(m, 0, -1, -1, 0))
+ return 90;
+ if (eq(m, -1, 0, 0, 1))
+ return 180;
+ if (eq(m, 0, 1, 1, 0))
+ return 270;
+ if (eq(m, -1, 0, 0, -1))
+ return 0;
+ if (eq(m, 1, 0, 0, 1)) /* x-flipped */
+ return 180;
+ if (eq(m, 0, 1, -1, 0)) /* x-flipped, 90 deg */
+ return 90;
+ fprintf(stderr, "unrecognized matrix %d %d %d %d\n",
+ m[1], m[2], m[4], m[5]);
+ exit(1);
+}
+
+
+bool matrix_is_mirrored(const int m[6])
+{
+ if (eq(m, 1, 0, 0, -1))
+ return 0;
+ if (eq(m, 0, -1, -1, 0))
+ return 0;
+ if (eq(m, -1, 0, 0, 1))
+ return 0;
+ if (eq(m, 0, 1, 1, 0))
+ return 0;
+
+ if (eq(m, -1, 0, 0, -1))
+ return 1;
+ if (eq(m, 1, 0, 0, 1))
+ return 1;
+ if (eq(m, 0, 1, -1, 0))
+ return 1;
+ assert(0);
+}
+
+
+int angle_add(int a, int b)
+{
+ a += b;
+ while (a < 0)
+ a += 360;
+ return a % 360;
+}
+
+
+
+int rx(int x, int y, int rot)
+{
+ switch (rot) {
+ case 0:
+ return x;
+ case 90:
+ return y;
+ case 180:
+ return -x;
+ case 270:
+ return -y;
+ default:
+ assert(0);
+
+ }
+}
+
+
+int ry(int x, int y, int rot)
+{
+ switch (rot) {
+ case 0:
+ return y;
+ case 90:
+ return -x;
+ case 180:
+ return -y;
+ case 270:
+ return x;
+ default:
+ assert(0);
+
+ }
+}
diff --git a/misc.h b/misc.h
new file mode 100644
index 0000000..40c15f0
--- /dev/null
+++ b/misc.h
@@ -0,0 +1,51 @@
+/*
+ * misc.h - Helper functions for geometry and attributes
+ *
+ * Written 2016 by Werner Almesberger
+ * Copyright 2016 Werner Almesberger
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ */
+
+
+#ifndef MISC_H
+#define MISC_H
+
+#include <stdbool.h>
+
+
+static inline int mxr(int x, int y, const int m[6])
+{
+ return x * m[1] + y * m[2];
+}
+
+
+static inline int myr(int x, int y, const int m[6])
+{
+ return x * m[4] + y * m[5];
+}
+
+
+static inline int mx(int x, int y, const int m[6])
+{
+ return m[0] + mxr(x, y, m);
+}
+
+
+static inline int my(int x, int y, const int m[6])
+{
+ return m[3] + myr(x, y, m);
+}
+
+
+unsigned matrix_to_angle(const int m[6]);
+bool matrix_is_mirrored(const int m[6]);
+int angle_add(int a, int b);
+
+int rx(int x, int y, int rot);
+int ry(int x, int y, int rot);
+
+#endif /* !MISC_H */
diff --git a/neo900-template.fig b/neo900-template.fig
new file mode 100644
index 0000000..4371404
--- /dev/null
+++ b/neo900-template.fig
@@ -0,0 +1,15 @@
+#FIG 3.2 Produced by xfig version 3.2.5c
+Landscape
+Center
+Metric
+A4
+100.00
+Single
+-2
+1200 2
+0 32 #c0c0c0
+2 2 0 1 7 7 250 -1 -1 0.000 0 0 -1 0 0 5
+ 225 450 19350 450 19350 13500 225 13500 225 450
+4 2 32 199 -1 18 150 0.0000 4 1890 13980 19305 13455 <NUMBER>\001
+4 2 0 50 -1 18 24 0.0000 4 300 1635 19125 13140 <TITLE>\001
+4 0 0 250 -1 22 12 0.0000 4 180 3090 315 13410 CC-BY-SA (c) 2014-2016 Neo900 & GDC\001
diff --git a/record.c b/record.c
new file mode 100644
index 0000000..03cf90c
--- /dev/null
+++ b/record.c
@@ -0,0 +1,342 @@
+/*
+ * record.c - Record graphics operations by layers and replay
+ *
+ * Written 2016 by Werner Almesberger
+ * Copyright 2016 by Werner Almesberger
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ */
+
+
+#include <stdlib.h>
+#include <limits.h>
+#include <math.h>
+
+#include "util.h"
+#include "style.h"
+#include "gfx.h"
+#include "text.h"
+#include "record.h"
+
+
+struct record_obj {
+ enum ro_type {
+ ro_line,
+ ro_rect,
+ ro_poly,
+ ro_circ,
+ ro_arc,
+ ro_text,
+ } type;
+
+ int x, y;
+ int color, fill_color;
+ union {
+ struct {
+ int ex, ey;
+ } line;
+ struct {
+ int ex, ey;
+ } rect;
+ struct {
+ unsigned n;
+ int *vx, *vy;
+ } poly;
+ struct {
+ int r;
+ } circ;
+ struct {
+ int r;
+ int sa, ea;
+ } arc;
+ struct {
+ char *s;
+ unsigned size;
+ enum text_align align;
+ int rot;
+ } text;
+ } u;
+ struct record_obj *next;
+};
+
+
+static void bb(struct record *rec, int x, int y)
+{
+ if (rec->xmin > x)
+ rec->xmin = x;
+ if (rec->ymin > y)
+ rec->ymin = y;
+ if (rec->xmax < x)
+ rec->xmax = x;
+ if (rec->ymax < y)
+ rec->ymax = y;
+}
+
+
+static void bb_rot(struct record *rec, int x, int y, int rot)
+{
+ double a = -rot / 180.0 * M_PI;
+
+ // @@@ figure this out later
+return;
+ bb(rec, cos(a) * x + sin(a) * y, cos(a) * y - sin(a) * x);
+}
+
+
+static struct record_obj *new_obj(struct record *rec, enum ro_type type,
+ int color, int fill_color, unsigned layer)
+{
+ struct record_layer **curr_layer;
+ struct record_layer *new_layer;
+ struct record_obj *new_obj;
+
+ for (curr_layer = &rec->layers; *curr_layer;
+ curr_layer= &(*curr_layer)->next) {
+ if ((*curr_layer)->layer == layer)
+ goto this_layer;
+ if ((*curr_layer)->layer < layer)
+ break;
+ }
+
+ new_layer = alloc_type(struct record_layer);
+ new_layer->layer = layer;
+ new_layer->objs = NULL;
+ new_layer->next_obj = &new_layer->objs;
+ new_layer->next = *curr_layer;
+ *curr_layer = new_layer;
+
+this_layer:
+ new_obj = alloc_type(struct record_obj);
+ new_obj->type = type;
+ new_obj->color = color;
+ new_obj->fill_color = fill_color;
+ new_obj->next = NULL;
+
+ *(*curr_layer)->next_obj = new_obj;
+ (*curr_layer)->next_obj = &new_obj->next;
+
+ return new_obj;
+}
+
+
+void record_line(void *ctx, int sx, int sy, int ex, int ey,
+ int color, unsigned layer)
+{
+ struct record *rec = ctx;
+ struct record_obj *obj =
+ new_obj(rec, ro_line, color, COLOR_NONE, layer);
+
+ bb(rec, sx, sy);
+ bb(rec, ex, ey);
+
+ obj->x = sx;
+ obj->y = sy;
+ obj->u.line.ex = ex;
+ obj->u.line.ey = ey;
+}
+
+
+void record_rect(void *ctx, int sx, int sy, int ex, int ey,
+ int color, int fill_color, unsigned layer)
+{
+ struct record *rec = ctx;
+ struct record_obj *obj =
+ new_obj(rec, ro_rect, color, fill_color, layer);
+
+ bb(rec, sx, sy);
+ bb(rec, ex, ey);
+
+ obj->x = sx;
+ obj->y = sy;
+ obj->u.rect.ex = ex;
+ obj->u.rect.ey = ey;
+}
+
+
+void record_poly(void *ctx,
+ int points, const int x[points], const int y[points],
+ int color, int fill_color, unsigned layer)
+{
+ struct record *rec = ctx;
+ struct record_obj *obj =
+ new_obj(ctx, ro_poly, color, fill_color, layer);
+ int i;
+ unsigned size;
+
+ for (i = 0; i != points; i++)
+ bb(rec, x[i], y[i]);
+
+ obj->u.poly.n = points;
+ size = sizeof(int) * points;
+ obj->u.poly.vx = alloc_size(size);
+ obj->u.poly.vy = alloc_size(size);
+ memcpy(obj->u.poly.vx, x, size);
+ memcpy(obj->u.poly.vy, y, size);
+}
+
+
+void record_circ(void *ctx, int x, int y, int r,
+ int color, int fill_color, unsigned layer)
+{
+ struct record *rec = ctx;
+ struct record_obj *obj =
+ new_obj(ctx, ro_circ, color, fill_color, layer);
+
+ bb(rec, x - r, y - r);
+ bb(rec, x + r, y + r);
+
+ obj->x = x;
+ obj->y = y;
+ obj->u.circ.r = r;
+}
+
+
+void record_arc(void *ctx, int x, int y, int r, int sa, int ea,
+ int color, int fill_color, unsigned layer)
+{
+ struct record *rec = ctx;
+ struct record_obj *obj = new_obj(ctx, ro_arc, color, fill_color, layer);
+
+ bb(rec, x - r, y - r);
+ bb(rec, x + r, y + r);
+
+ obj->x = x;
+ obj->y = y;
+ obj->u.arc.r = r;
+ obj->u.arc.sa = sa;
+ obj->u.arc.ea = ea;
+}
+
+
+void record_text(void *ctx, int x, int y, const char *s, unsigned size,
+ enum text_align align, int rot, unsigned color, unsigned layer)
+{
+ struct record *rec = ctx;
+ struct record_obj *obj =
+ new_obj(ctx, ro_text, color, COLOR_NONE, layer);
+ unsigned width = rec->ops->text_width(rec->user, s, size);
+
+ bb_rot(rec, x, y - size, rot);
+ bb_rot(rec, x + width, y, rot);
+
+ obj->x = x;
+ obj->y = y;
+ obj->u.text.s = stralloc(s);
+ obj->u.text.size = size;
+ obj->u.text.align = align;
+ obj->u.text.rot = rot;
+}
+
+
+void record_init(struct record *rec, const struct gfx_ops *ops, void *user)
+{
+ rec->ops = ops;
+ rec->user = user;
+ rec->xmin = rec->ymin = INT_MAX;
+ rec->xmax = rec->ymax = INT_MIN;
+ rec->layers = NULL;
+}
+
+
+void record_wipe(struct record *rec)
+{
+ rec->layers = NULL;
+}
+
+
+void record_replay(const struct record *rec)
+{
+ const struct gfx_ops *ops = rec->ops;
+ void *ctx = rec->user;
+ const struct record_layer *layer;
+ const struct record_obj *obj;
+
+ for (layer = rec->layers; layer; layer = layer->next)
+ for (obj = layer->objs; obj; obj = obj->next)
+ switch (obj->type) {
+ case ro_line:
+ ops->line(ctx, obj->x, obj->y,
+ obj->u.line.ex, obj->u.line.ey,
+ obj->color, layer->layer);
+ break;
+ case ro_rect:
+ ops->rect(ctx, obj->x, obj->y,
+ obj->u.rect.ex, obj->u.rect.ey,
+ obj->color, obj->fill_color, layer->layer);
+ break;
+ case ro_poly:
+ ops->poly(ctx, obj->u.poly.n,
+ obj->u.poly.vx, obj->u.poly.vy,
+ obj->color, obj->fill_color, layer->layer);
+ break;
+ case ro_circ:
+ ops->circ(ctx, obj->x, obj->y,
+ obj->u.circ.r,
+ obj->color, obj->fill_color, layer->layer);
+ break;
+ case ro_arc:
+ ops->arc(ctx, obj->x, obj->y, obj->u.arc.r,
+ obj->u.arc.sa, obj->u.arc.ea,
+ obj->color, obj->fill_color, layer->layer);
+ break;
+ case ro_text:
+ ops->text(ctx, obj->x, obj->y, obj->u.text.s,
+ obj->u.text.size, obj->u.text.align,
+ obj->u.text.rot,
+ obj->color, layer->layer);
+ break;
+ default:
+ abort();
+ }
+}
+
+
+void record_bbox(const struct record *rec, int *x, int *y, int *w, int *h)
+{
+ if (x)
+ *x = rec->xmin;
+ if (y)
+ *y = rec->ymin;
+ if (w)
+ *w = rec->xmax - rec->xmin + 1;
+ if (h)
+ *h = rec->ymax - rec->ymin + 1;
+}
+
+
+static void record_obj_destroy(struct record_obj *obj)
+{
+ switch (obj->type) {
+ case ro_poly:
+ free(obj->u.poly.vx);
+ free(obj->u.poly.vy);
+ break;
+ case ro_text:
+ free(obj->u.text.s);
+ break;
+ default:
+ break;
+ }
+ free(obj);
+}
+
+
+void record_destroy(struct record *rec)
+{
+ struct record_layer *next_layer;
+ struct record_obj *next_obj;
+
+ while (rec->layers) {
+ next_layer = rec->layers->next;
+ while (rec->layers->objs) {
+ next_obj = rec->layers->objs->next;
+ record_obj_destroy(rec->layers->objs);
+ rec->layers->objs = next_obj;
+ }
+ free(rec->layers);
+ rec->layers = next_layer;
+ }
+}
diff --git a/record.h b/record.h
new file mode 100644
index 0000000..3ff26f8
--- /dev/null
+++ b/record.h
@@ -0,0 +1,58 @@
+/*
+ * record.h - Record graphics operations by layers and replay
+ *
+ * Written 2016 by Werner Almesberger
+ * Copyright 2016 by Werner Almesberger
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ */
+
+
+#ifndef RECORD_H
+#define RECORD_H
+
+#include "gfx.h"
+
+
+struct record_obj;
+
+struct record_layer {
+ unsigned layer;
+ struct record_obj *objs;
+ struct record_obj **next_obj;
+ struct record_layer *next;
+};
+
+struct record {
+ const struct gfx_ops *ops;
+ void *user;
+ int xmin, xmax;
+ int ymin, ymax;
+ struct record_layer *layers;
+};
+
+
+void record_line(void *ctx, int sx, int sy, int ex, int ey,
+ int color, unsigned layer);
+void record_rect(void *ctx, int sx, int sy, int ex, int ey,
+ int color, int fill_color, unsigned layer);
+void record_poly(void *ctx,
+ int points, const int x[points], const int y[points],
+ int color, int fill_color, unsigned layer);
+void record_circ(void *ctx, int x, int y, int r,
+ int color, int fill_color, unsigned layer);
+void record_arc(void *ctx, int x, int y, int r, int sa, int ea,
+ int color, int fill_color, unsigned layer);
+void record_text(void *ctx, int x, int y, const char *s, unsigned size,
+ enum text_align align, int rot, unsigned color, unsigned layer);
+
+void record_init(struct record *rec, const struct gfx_ops *ops, void *user);
+void record_wipe(struct record *rec);
+void record_replay(const struct record *rec);
+void record_bbox(const struct record *rec, int *x, int *y, int *w, int *h);
+void record_destroy(struct record *rec);
+
+#endif /* !RECORD_H */
diff --git a/sch-parse.c b/sch-parse.c
new file mode 100644
index 0000000..e47ac49
--- /dev/null
+++ b/sch-parse.c
@@ -0,0 +1,562 @@
+/*
+ * sch-parse.c - Parse Eeschema .sch file
+ *
+ * Written 2016 by Werner Almesberger
+ * Copyright 2016 by Werner Almesberger
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ */
+
+
+#include <stddef.h>
+#include <stdbool.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <unistd.h>
+#include <string.h>
+#include <assert.h>
+
+#include "util.h"
+#include "dwg.h"
+#include "file.h"
+#include "lib.h"
+#include "sch.h"
+
+
+/* ----- (Global) Labels --------------------------------------------------- */
+
+
+static enum dwg_shape do_decode_shape(const char *s)
+{
+ if (!strcmp(s, "UnSpc"))
+ return dwg_unspec;
+ if (!strcmp(s, "Input"))
+ return dwg_in;
+ if (!strcmp(s, "Output"))
+ return dwg_out;
+ if (!strcmp(s, "3State"))
+ return dwg_tri;
+ if (!strcmp(s, "BiDi"))
+ return dwg_bidir;
+ fprintf(stderr, "unknown shape: \"%s\"\n", s);
+ exit(1);
+}
+
+
+static enum dwg_shape decode_shape(const char *s)
+{
+ enum dwg_shape res;
+
+ res = do_decode_shape(s);
+ free((void *) s);
+ return res;
+}
+
+
+/* ----- Component fields -------------------------------------------------- */
+
+
+void decode_alignment(struct text *txt, char hor, char vert)
+{
+ switch (hor) {
+ case 'L':
+ txt->hor = text_min;
+ break;
+ case 'C':
+ txt->hor = text_mid;
+ break;
+ case 'R':
+ txt->hor = text_max;
+ break;
+ default:
+ assert(0);
+ }
+
+ switch (vert) {
+ case 'B':
+ txt->vert = text_min;
+ break;
+ case 'C':
+ txt->vert = text_mid;
+ break;
+ case 'T':
+ txt->vert = text_max;
+ break;
+ default:
+ assert(0);
+ }
+}
+
+
+static bool parse_field(struct sch_ctx *ctx, const char *line)
+{
+ struct sch_comp *comp = &ctx->obj.u.comp;
+ int n;
+ unsigned flags;
+ char hv, hor, vert, italic, bold;
+ struct comp_field *field;
+ struct text *txt;
+
+ field = alloc_type(struct comp_field);
+ txt = &field->txt;
+
+ if (sscanf(line, "F %d \"\" %c %d %d %u %u %c %c%c%c",
+ &n, &hv, &txt->x, &txt->y, &txt->size, &flags, &hor, &vert,
+ &italic, &bold) == 10) {
+ free(field);
+ return 1;
+ }
+ if (sscanf(line, "F %d \"%m[^\"]\" %c %d %d %u %u %c %c%c%c",
+ &n, &txt->s, &hv, &txt->x, &txt->y, &txt->size, &flags,
+ &hor, &vert, &italic, &bold) != 11)
+ return 0;
+
+ if (flags || !lib_field_visible(comp->comp, n)) {
+ free(field);
+ return 1;
+ }
+
+ if (n == 0 && comp->comp->units > 1) {
+ int len = strlen(txt->s);
+ char *s;
+
+ s = realloc((void *) txt->s, len + 3);
+ if (!s) {
+ perror("realloc");
+ exit(1);
+ }
+ if (comp->unit <= 26)
+ sprintf(s + len, "%c", 'A' + comp->unit - 1);
+ else
+ sprintf(s + len, "%c%c",
+ 'A' + (comp->unit - 1) / 26 - 1,
+ 'A' + (comp->unit - 1) % 26);
+ txt->s = s;
+ }
+
+ field->next = comp->fields;
+ comp->fields = field;
+
+ switch (hv) {
+ case 'H':
+ txt->rot = 0;
+ break;
+ case 'V':
+ txt->rot = 90;
+ break;
+ default:
+ assert(0);
+ }
+
+ decode_alignment(txt, hor, vert);
+
+ // @@@ decode font
+
+ return 1;
+}
+
+
+/* ----- Sheet field ------------------------------------------------------- */
+
+
+static enum dwg_shape decode_form(char form)
+{
+ switch (form) {
+ case 'O':
+ return dwg_in;
+ case 'I':
+ return dwg_out;
+ case 'B':
+ /* fall through */
+ case 'T':
+ return dwg_bidir;
+ case 'U':
+ return dwg_unspec;
+ default:
+ fprintf(stderr, "unknown form: \"%c\"\n", form);
+ exit(1);
+ }
+}
+
+
+static int decode_side(char side)
+{
+ switch (side) {
+ case 'L':
+ return 2; /* left */
+ case 'B':
+ return 1; /* up */
+ case 'R':
+ return 0; /* right */
+ case 'T':
+ return 3; /* down */
+ default:
+ fprintf(stderr, "unknown side: \"%c\"\n", side);
+ exit(1);
+ }
+}
+
+
+static bool parse_hsheet_field(struct sch_ctx *ctx, const char *line)
+{
+ struct sch_sheet *sheet = &ctx->obj.u.sheet;
+ char *s;
+ char form, side;
+ unsigned n, dim;
+ struct sheet_field *field;
+
+ if (sscanf(line, "F%d \"%m[^\"]\" %u", &n, &s, &dim) == 3) {
+ switch (n) {
+ case 0:
+ sheet->sheet = s;
+ sheet->sheet_dim = dim;
+ return 1;
+ case 1:
+ sheet->file = s;
+ sheet->file_dim = dim;
+ return 1;
+ default:
+ assert(0);
+ }
+ }
+
+ field = alloc_type(struct sheet_field);
+ if (sscanf(line, "F%d \"%m[^\"]\" %c %c %d %d %u",
+ &n, &field->s, &form, &side, &field->x, &field->y, &field->dim)
+ != 7) {
+ free(field);
+ return 0;
+ }
+ assert(n >= 2);
+
+ if (side == 'B' || side == 'T') {
+ /*
+ * This is beautiful: since there is no indication for rotation
+ * on the hsheet, or the sheet or file fields, we need to look
+ * at whether the imported sheet pins go left or right (no
+ * rotation) or whether they go top or bottom (rotation).
+ *
+ * A sheet with no imported pins lacks these hints, and is
+ * therefore always assumed to be without rotation.
+ *
+ * Eeschema is careful to be consistent, and does not allow
+ * sheets with no imported pins to be rotated. Even better, it
+ * flips rotated sheets where the last imported pin is deleted
+ * back.
+ */
+ sheet->rotated = 1;
+ }
+ field->shape = decode_form(form);
+ field->side = decode_side(side);
+
+ field->next = sheet->fields;
+ sheet->fields = field;
+
+ return 1;
+}
+
+
+/* ----- Schematics parser ------------------------------------------------- */
+
+
+static void submit_obj(struct sch_ctx *ctx, enum sch_obj_type type)
+{
+ struct sch_obj *obj;
+
+ obj = alloc_type(struct sch_obj);
+ *obj = ctx->obj;
+ obj->type = type;
+ obj->next = NULL;
+
+ *ctx->next_obj = obj;
+ ctx->next_obj = &obj->next;
+}
+
+
+static struct sheet *new_sheet(struct sch_ctx *ctx)
+{
+ struct sheet *sheet;
+
+ sheet = alloc_type(struct sheet);
+ sheet->objs = NULL;
+ sheet->next = NULL;
+
+ ctx->next_obj = &sheet->objs;
+
+ *ctx->next_sheet = sheet;
+ ctx->next_sheet = &sheet->next;
+
+ return sheet;
+}
+
+
+static bool parse_line(void *user, const char *line);
+
+
+static void recurse_sheet(struct sch_ctx *ctx)
+{
+ struct sch_obj **saved_next_obj = ctx->next_obj;
+ const char *parent = ctx->file->name;
+ char *tmp = NULL;
+ struct sch_file dsc = {
+ .name = ctx->obj.u.sheet.file,
+ .lineno = 0,
+ .parent = ctx->file,
+ };
+
+ /* @@@ clean this up */
+
+ if (access(dsc.name, R_OK)) {
+ const char *slash;
+
+ slash = strrchr(parent, '/');
+ if (slash) {
+ unsigned len = slash + 1 - parent;
+
+ tmp = alloc_size(len + strlen(dsc.name) + 1);
+ memcpy(tmp, parent, len);
+ strcpy(tmp + len, dsc.name);
+ dsc.name = tmp;
+ }
+ }
+
+ new_sheet(ctx);
+ ctx->file = &dsc;
+ ctx->state = sch_descr;
+ file_read(dsc.name, parse_line, ctx);
+ ctx->file = dsc.parent;
+ ctx->next_obj = saved_next_obj;
+ free(tmp);
+}
+
+
+static bool parse_line(void *user, const char *line)
+{
+ struct sch_ctx *ctx = user;
+ struct sch_obj *obj = &ctx->obj;
+ int n = 0;
+ char *s;
+
+ ctx->file->lineno++;
+
+ switch (ctx->state) {
+ case sch_basic:
+ if (sscanf(line, "$Comp%n", &n) == 0 && n) {
+ ctx->state = sch_comp;
+ obj->u.comp.fields = NULL;
+ return 1;
+ }
+ if (sscanf(line, "$Sheet%n", &n) == 0 && n) {
+ ctx->state = sch_sheet;
+ obj->u.sheet.sheet = NULL;
+ obj->u.sheet.file = NULL;
+ obj->u.sheet.rotated = 0;
+ obj->u.sheet.fields = NULL;
+ return 1;
+ }
+
+ /* Text */
+
+ struct sch_text *text = &obj->u.text;
+
+ if (sscanf(line, "Text Notes %d %d %d %d",
+ &obj->x, &obj->y, &text->dir, &text->dim) == 4) {
+ ctx->state = sch_text;
+ obj->u.text.fn = dwg_text;
+ return 1;
+ }
+ if (sscanf(line, "Text GLabel %d %d %d %d %ms",
+ &obj->x, &obj->y, &text->dir, &text->dim, &s) == 5) {
+ ctx->state = sch_text;
+ obj->u.text.fn = dwg_glabel;
+ obj->u.text.shape = decode_shape(s);
+ return 1;
+ }
+ if (sscanf(line, "Text HLabel %d %d %d %d %ms",
+ &obj->x, &obj->y, &text->dir, &text->dim, &s) == 5) {
+ ctx->state = sch_text;
+ obj->u.text.fn = dwg_hlabel;
+ obj->u.text.shape = decode_shape(s);
+ return 1;
+ }
+ if (sscanf(line, "Text Label %d %d %d %d",
+ &obj->x, &obj->y, &text->dir, &text->dim) == 4) {
+ ctx->state = sch_text;
+ obj->u.text.fn = dwg_label;
+ return 1;
+ }
+
+ /* Connection */
+
+ if (sscanf(line, "Connection ~ %d %d", &obj->x, &obj->y) == 2) {
+ submit_obj(ctx, sch_obj_junction);
+ return 1;
+ }
+
+ /* NoConn */
+
+ if (sscanf(line, "NoConn ~ %d %d", &obj->x, &obj->y) == 2) {
+ submit_obj(ctx, sch_obj_noconn);
+ return 1;
+ }
+
+ /* Wire */
+
+ if (sscanf(line, "Wire Wire Line%n", &n) == 0 && n) {
+ ctx->state = sch_wire;
+ obj->u.wire.fn = dwg_wire;
+ return 1;
+ }
+ if (sscanf(line, "Wire Bus Line%n", &n) == 0 && n) {
+ ctx->state = sch_wire;
+ obj->u.wire.fn = dwg_bus;
+ return 1;
+ }
+ if (sscanf(line, "Wire Notes Line%n", &n) == 0 && n) {
+ ctx->state = sch_wire;
+ obj->u.wire.fn = dwg_line;
+ return 1;
+ }
+
+ /* Entry */
+
+ /*
+ * Documentation mentions the following additional variants:
+ *
+ * - Entry Wire Line equivalent:
+ * Wire Wire Bus
+ * Entry Wire Bus
+ *
+ * - Entry Bus Bus equivalent:
+ * Wire Bus Bus
+ */
+ if (sscanf(line, "Entry Wire Line%n", &n) == 0 && n) {
+ ctx->state = sch_wire;
+ obj->u.wire.fn = dwg_wire;
+ return 1;
+ }
+ if (sscanf(line, "Entry Bus Bus%n", &n) == 0 && n) {
+ ctx->state = sch_wire;
+ obj->u.wire.fn = dwg_bus;
+ return 1;
+ }
+
+ /* EndSCHEMATC */
+
+ if (sscanf(line, "$EndSCHEMATC%n", &n) == 0 && n)
+ return 0;
+ break;
+ case sch_descr:
+ if (sscanf(line, "$EndDescr%n", &n) || !n)
+ return 1;
+ ctx->state = sch_basic;
+ return 1;
+ case sch_comp:
+ if (sscanf(line, "$EndComp%n", &n) == 0 && n) {
+ ctx->state = sch_basic;
+ submit_obj(ctx, sch_obj_comp);
+ return 1;
+ }
+ if (sscanf(line, "L %ms", &s) == 1) {
+ obj->u.comp.comp = lib_find(ctx->lib, s);
+ free(s);
+ return 1;
+ }
+ if (sscanf(line, "U %u", &obj->u.comp.unit) == 1)
+ return 1;
+ if (sscanf(line, "P %d %d", &obj->x, &obj->y) == 2)
+ return 1;
+ if (parse_field(ctx, line))
+ return 1;
+ if (sscanf(line, "AR %n", &n) == 0 && n)
+ return 1; /* @@@ what is "AR" ? */
+ n = sscanf(line, " %d %d %d %d",
+ obj->u.comp.m + 1, obj->u.comp.m + 2,
+ obj->u.comp.m + 4, obj->u.comp.m + 5);
+ if (n == 3)
+ return 1;
+ if (n == 4) {
+ obj->u.comp.m[0] = obj->x;
+ obj->u.comp.m[3] = obj->y;
+ return 1;
+ }
+ break;
+ case sch_sheet:
+ if (sscanf(line, "$EndSheet%n", &n) == 0 && n) {
+ submit_obj(ctx, sch_obj_sheet);
+ if (ctx->recurse)
+ recurse_sheet(ctx);
+ ctx->state = sch_basic;
+ return 1;
+ }
+ if (sscanf(line, "S %d %d %u %u",
+ &obj->x, &obj->y, &obj->u.sheet.w, &obj->u.sheet.h) == 4)
+ return 1;
+ if (sscanf(line, "U %*x%n", &n) == 0 && n)
+ return 1;
+ if (parse_hsheet_field(ctx, line))
+ return 1;
+ break;
+ case sch_text:
+ ctx->state = sch_basic;
+ {
+ const char *from;
+ char *to;
+
+ s = alloc_size(strlen(line) + 1);
+ from = line;
+ to = s;
+ while (*from) {
+ if (from[0] != '\\' || from[1] != 'n') {
+ *to++ = *from++;
+ continue;
+ }
+ *to++ = '\n';
+ from += 2;
+ }
+ *to = 0;
+ obj->u.text.s = s;
+ submit_obj(ctx, sch_obj_text);
+ }
+ return 1;
+ case sch_wire:
+ if (sscanf(line, "%d %d %d %d", &obj->x, &obj->y,
+ &obj->u.wire.ex, &obj->u.wire.ey) != 4)
+ break;
+ submit_obj(ctx, sch_obj_wire);
+ ctx->state = sch_basic;
+ return 1;
+ default:
+ abort();
+ }
+ fprintf(stderr, "%s:%u: cannot parse\n\"%s\"\n",
+ ctx->file->name, ctx->file->lineno, line);
+ exit(1);
+}
+
+
+void sch_parse(struct sch_ctx *ctx, const char *file, const struct lib *lib)
+{
+ struct sch_file dsc = {
+ .name = file,
+ .lineno = 0,
+ .parent = NULL,
+ };
+
+ ctx->file = &dsc;
+ ctx->lib = lib;
+ file_read(file, parse_line, ctx);
+}
+
+
+void sch_init(struct sch_ctx *ctx, bool recurse)
+{
+ ctx->state = sch_descr;
+ ctx->recurse = recurse;
+ ctx->sheets = NULL;
+ ctx->next_sheet = &ctx->sheets;
+ new_sheet(ctx);
+}
diff --git a/sch-render.c b/sch-render.c
new file mode 100644
index 0000000..b1b9311
--- /dev/null
+++ b/sch-render.c
@@ -0,0 +1,174 @@
+/*
+ * sch-render.c - Render schematics
+ *
+ * Written 2016 by Werner Almesberger
+ * Copyright 2016 by Werner Almesberger
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ */
+
+
+#define _GNU_SOURCE /* for asprintf */
+#include <stdio.h>
+#include <assert.h>
+
+#include "misc.h"
+#include "style.h"
+#include "gfx.h"
+#include "dwg.h"
+#include "lib.h"
+#include "sch.h"
+
+
+/* ----- Rendering --------------------------------------------------------- */
+
+
+static void dump_field(const struct comp_field *field, const int m[6])
+{
+ struct text txt = field->txt;
+ int dx, dy;
+
+ dx = txt.x - m[0];
+ dy = txt.y - m[3];
+ txt.x = mx(dx, dy, m);
+ txt.y = my(dx, dy, m);
+
+ text_rot(&txt, matrix_to_angle(m));
+
+ switch (txt.rot) {
+ case 180:
+ text_rot(&txt, 180);
+ txt.hor = text_flip(txt.hor);
+ txt.vert = text_flip(txt.vert);
+ break;
+ case 270:
+ text_rot(&txt, 180);
+ txt.vert = text_flip(txt.vert);
+ txt.hor = text_flip(txt.hor);
+ break;
+ default:
+ break;
+ }
+
+ if (matrix_is_mirrored(m)) {
+ if ((txt.rot % 180) == 0)
+ txt.hor = text_flip(txt.hor);
+ else
+ txt.vert = text_flip(txt.vert);
+ }
+
+ text_fig(&txt, COLOR_FIELD, LAYER_FIELD);
+}
+
+
+static void do_hsheet_text(const struct sch_obj *obj,
+ const struct sch_sheet *sheet)
+{
+ char *s;
+
+ assert(sheet->sheet && sheet->file);
+
+ struct text sheet_txt = {
+ .size = sheet->sheet_dim,
+ .x = obj->x,
+ .y = obj->y,
+ .rot = 0,
+ .hor = text_min,
+ .vert = text_min,
+ };
+ if (asprintf(&s, "Sheet: %s", sheet->sheet)) {}
+ sheet_txt.s = s; /* work around "const" mismatch */
+
+ struct text file_txt = {
+ .size = sheet->file_dim,
+ .x = obj->x,
+ .y = obj->y,
+ .rot = 0,
+ .hor = text_min,
+ .vert = text_max,
+ };
+ if (asprintf(&s, "File: %s", sheet->file)) {}
+ file_txt.s = s; /* work around "const" mismatch */
+
+ if (sheet->rotated) {
+ sheet_txt.rot = file_txt.rot = 90;
+ sheet_txt.x -= HSHEET_FIELD_OFFSET;
+ sheet_txt.y += sheet->h;
+ file_txt.x += sheet->w + HSHEET_FIELD_OFFSET;
+ file_txt.y += sheet->h;
+ } else {
+ sheet_txt.y -= HSHEET_FIELD_OFFSET;
+ file_txt.y += sheet->h + HSHEET_FIELD_OFFSET;
+ }
+
+ text_fig(&sheet_txt, COLOR_HSHEET_SHEET, LAYER_HSHEET_FIELD);
+ text_fig(&file_txt, COLOR_HSHEET_FILE, LAYER_HSHEET_FIELD);
+
+// free((void *) ctx->sheet);
+// free((void *) ctx->file);
+}
+
+
+static void render_sheet(const struct sch_obj *obj,
+ const struct sch_sheet *sheet)
+{
+ const struct sheet_field *field;
+
+ gfx_rect(obj->x, obj->y, obj->x + sheet->w, obj->y + sheet->h,
+ COLOR_HSHEET_BOX, COLOR_NONE, LAYER_HSHEET_BOX);
+ do_hsheet_text(obj, sheet);
+
+ for (field = sheet->fields; field; field = field->next)
+ dwg_hlabel(obj->x, obj->y, field->s,
+ field->side, field->dim,
+ field->shape);
+ // free(field->s)
+}
+
+
+void sch_render(const struct sheet *sheet)
+{
+ const struct sch_obj *obj;
+
+ for (obj = sheet->objs; obj; obj = obj->next)
+ switch (obj->type) {
+ case sch_obj_wire:
+ {
+ const struct sch_wire *wire = &obj->u.wire;
+
+ wire->fn(obj->x, obj->y, wire->ex, wire->ey);
+ }
+ break;
+ case sch_obj_junction:
+ dwg_junction(obj->x, obj->y);
+ break;
+ case sch_obj_noconn:
+ dwg_noconn(obj->x, obj->y);
+ break;
+ case sch_obj_text:
+ {
+ const struct sch_text *text = &obj->u.text;
+
+ text->fn(obj->x, obj->y, text->s, text->dir,
+ text->dim, text->shape);
+ }
+ break;
+ case sch_obj_comp:
+ {
+ const struct sch_comp *comp = &obj->u.comp;
+ const struct comp_field *field;
+
+ lib_render(comp->comp, comp->unit, comp->m);
+ for (field = comp->fields; field;
+ field = field->next)
+ dump_field(field, comp->m);
+ }
+ break;
+ case sch_obj_sheet:
+ render_sheet(obj, &obj->u.sheet);
+ break;
+ }
+}
diff --git a/sch.h b/sch.h
new file mode 100644
index 0000000..b1e2d70
--- /dev/null
+++ b/sch.h
@@ -0,0 +1,123 @@
+/*
+ * sch.h - Parse Eeschema .sch file
+ *
+ * Written 2016 by Werner Almesberger
+ * Copyright 2016 by Werner Almesberger
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ */
+
+
+#ifndef SCH_H
+#define SCH_H
+
+#include <stdbool.h>
+
+#include "dwg.h"
+#include "text.h"
+#include "lib.h"
+
+
+enum sch_state {
+ sch_basic, /* basic state */
+ sch_descr, /* prelude and description */
+ sch_comp, /* component */
+ sch_sheet, /* sub-sheet */
+ sch_text, /* text or label */
+ sch_wire, /* wire */
+};
+
+struct sch_obj {
+ enum sch_obj_type {
+ sch_obj_wire,
+ sch_obj_junction,
+ sch_obj_noconn,
+ sch_obj_text,
+ sch_obj_comp,
+ sch_obj_sheet,
+ } type;
+
+ int x, y;
+
+ union {
+ struct sch_wire {
+ void (*fn)(int sx, int sy, int ex, int ey);
+ int ex, ey;
+ } wire;
+ struct sch_text {
+ void (*fn)(int x, int y, const char *s,
+ int dir, int dim, enum dwg_shape shape);
+ const char *s;
+ int dir; /* orientation */
+ int dim; /* dimension */
+ enum dwg_shape shape;
+ } text;
+ struct sch_comp {
+ const struct comp *comp; /* current component */
+ unsigned unit; /* unit of current component */
+ struct comp_field {
+ struct text txt;
+ struct comp_field *next;
+ } *fields;
+ int m[6];
+ } comp;
+ struct sch_sheet {
+ unsigned h, w;
+ const char *sheet;
+ unsigned sheet_dim;
+ const char *file;
+ unsigned file_dim;
+ bool rotated;
+
+ struct sheet_field {
+ char *s;
+ int x, y;
+ unsigned dim;
+ enum dwg_shape shape;
+ unsigned side;
+ struct sheet_field *next;
+ } *fields;
+ } sheet;
+ } u;
+
+ struct sch_obj *next;
+};
+
+struct sheet {
+ struct sch_obj *objs;
+ struct sheet *next;
+};
+
+struct sch_file {
+ const char *name;
+ int lineno;
+ struct sch_file *parent;
+};
+
+struct sch_ctx {
+ enum sch_state state;
+
+ bool recurse;
+
+ struct sch_obj obj;
+ struct sch_obj **next_obj;
+
+ struct sheet *sheets;
+ struct sheet **next_sheet;
+
+ const struct lib *lib;
+
+ struct sch_file *file;
+};
+
+
+void decode_alignment(struct text *txt, char hor, char vert);
+
+void sch_render(const struct sheet *sheet);
+void sch_parse(struct sch_ctx *ctx, const char *file, const struct lib *lib);
+void sch_init(struct sch_ctx *ctx, bool recurse);
+
+#endif /* !SCH_H */
diff --git a/sch2pdf b/sch2pdf
new file mode 100755
index 0000000..8e3c275
--- /dev/null
+++ b/sch2pdf
@@ -0,0 +1,92 @@
+#!/bin/bash
+#
+# sch2pdf - Generate PDF from schematics, using eeshow
+#
+# Written 2016 by Werner Almesberger
+# Copyright 2016 by Werner Almesberger
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+
+#
+# Known bugs:
+# - expects first sheet to be index page
+# - only renders sub-sheets
+# - has all the limitations of eeshow (see TODO)
+#
+
+
+usage()
+{
+ cat <<EOF 1>&2
+usage: $0 [-n first_num] [-o output.pdf] [-q] [-t template.fig ]
+ file.lib ... file.sch
+EOF
+ exit 1
+}
+
+
+out=out.pdf
+quiet=false
+template=
+num=1
+while [ "$1" ]; do
+ case "$1" in
+ -n) num=$2
+ shift 2;;
+ -o) out=$2
+ shift 2;;
+ -q) quiet=true
+ shift;;
+ -t) template="-t $2"
+ shift 2;;
+ -*) usage;;
+ *) break;;
+ esac
+done
+
+[ "$1" ] || usage
+
+libs=
+while [ "$2" ]; do
+ libs="$libs $1"
+ shift
+done
+
+./eeshow $libs "$1" \
+ -- fig $template "TITLE=`basename \"$1\" .sch`" NUMBER=$num |
+ fig2dev -L pdf >"$out"
+
+sheet=false
+while read line; do
+ if ! $sheet; then
+ [ "${line#\$Sheet}" != "$line" ] && sheet=true
+ continue
+ else
+ if [ "${line#\$EndSheet}" != "$line" ]; then
+ sheet=false
+ continue
+ fi
+ fi
+
+ if [ "${line#F0 \"}" != "$line" ]; then
+ name=${line#F0 \"}
+ name=${name%%\"*}
+ fi
+ [ "${line#F1 \"}" = "$line" ] && continue
+ file=${line#F1 \"}
+ file=${file%%\"*}
+
+ num=`expr $num + 1`
+
+ $quiet || echo "$file" 1>&2
+ ./eeshow $libs `dirname "$1"`/$file \
+ -- fig $template "TITLE=$name" NUMBER=$num |
+ fig2dev -L pdf >_tmp.pdf
+ pdfunite "$out" _tmp.pdf _tmp2.pdf
+ mv _tmp2.pdf "$out"
+done <"$1"
+exit
diff --git a/style.c b/style.c
new file mode 100644
index 0000000..7f86798
--- /dev/null
+++ b/style.c
@@ -0,0 +1,31 @@
+/*
+ * style.c - Drawing style
+ *
+ * Written 2016 by Werner Almesberger
+ * Copyright 2016 by Werner Almesberger
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ */
+
+#include <stdint.h>
+
+#include "style.h"
+
+
+uint32_t color_rgb[] = {
+ [COLOR_BLACK] = 0x000000,
+ [COLOR_BLUE] = 0x0000ff,
+ [COLOR_YELLOW] = 0xffff00,
+ [COLOR_WHITE] = 0xffffff,
+ [COLOR_GREEN4] = 0x009000,
+ [COLOR_CYAN4] = 0x009090,
+ [COLOR_CYAN3] = 0x00b0b0,
+ [COLOR_RED4] = 0x900000,
+ [COLOR_RED3] = 0xb00000,
+ [COLOR_MAGENTA4] = 0x900090,
+ [COLOR_BROWN2] = 0xc06000,
+ [COLOR_DARK_YELLOW] = 0x848400,
+};
diff --git a/style.h b/style.h
new file mode 100644
index 0000000..95e5e39
--- /dev/null
+++ b/style.h
@@ -0,0 +1,91 @@
+/*
+ * style.h - Drawing style
+ *
+ * Written 2016 by Werner Almesberger
+ * Copyright 2016 by Werner Almesberger
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ */
+
+#ifndef STYLE_H
+#define STYLE_H
+
+#include <stdint.h>
+
+
+/* FIG colors */
+
+#define COLOR_NONE -1
+#define COLOR_BLACK 0
+#define COLOR_BLUE 1
+#define COLOR_YELLOW 6
+#define COLOR_WHITE 7
+#define COLOR_GREEN4 12
+#define COLOR_CYAN4 15
+#define COLOR_CYAN3 16
+#define COLOR_RED4 18
+#define COLOR_RED3 19
+#define COLOR_MAGENTA4 21
+#define COLOR_BROWN2 26
+
+#define COLOR_DARK_YELLOW 32 /* user-defined */
+
+#define COLOR_COMP_DWG COLOR_RED4
+#define COLOR_COMP_DWG_BG COLOR_YELLOW
+#define COLOR_SHEET_DWG COLOR_BLUE
+#define COLOR_TEXT COLOR_BLUE
+#define COLOR_WIRE COLOR_GREEN4
+#define COLOR_BUS COLOR_BLUE
+#define COLOR_NOCONN COLOR_BLUE
+#define COLOR_GLABEL COLOR_RED4
+#define COLOR_HLABEL COLOR_DARK_YELLOW
+#define COLOR_HSHEET_BOX COLOR_MAGENTA4
+#define COLOR_HSHEET_SHEET COLOR_FIELD
+#define COLOR_HSHEET_FILE COLOR_HLABEL
+#define COLOR_LABEL COLOR_BLACK
+#define COLOR_FIELD COLOR_CYAN4
+#define COLOR_PIN_NAME COLOR_FIELD
+#define COLOR_PIN_NUMBER COLOR_RED4
+
+#define FONT_HELVETICA_BOLD 18
+
+#define LAYER_GLABEL 20
+#define LAYER_HLABEL LAYER_GLABEL
+#define LAYER_LABEL LAYER_GLABEL
+#define LAYER_TEXT 30
+#define LAYER_NOCONN 40
+#define LAYER_WIRES 50
+#define LAYER_BUSSES LAYER_WIRES
+#define LAYER_FIELD 60
+#define LAYER_PIN_NAME LAYER_FIELD
+#define LAYER_PIN_NUMBER LAYER_FIELD
+#define LAYER_HSHEET_FIELD LAYER_FIELD
+#define LAYER_HSHEET_BOX 70
+#define LAYER_LINES 100
+#define LAYER_COMP_DWG 120
+#define LAYER_COMP_DWG_BG 200
+
+#define WIDTH_WIRE 2
+#define WIDTH_BUS WIDTH_WIRE
+#define WIDTH_LINE 2
+#define WIDTH_COMP_DWG 2
+
+#define JUNCTION_R 30
+
+#define NOCONN_LEN 25
+
+#define LABEL_OFFSET 15 // eeschema has more like 10
+#define GLABEL_OFFSET 20
+#define HLABEL_OFFSET_F 0.4 // * text size
+#define PIN_NUM_OFFSET 15 // eeschema has more like 10
+#define HSHEET_FIELD_OFFSET 10
+
+#define NEWLINE_SKIP 1.4 // * text size
+
+
+extern uint32_t color_rgb[];
+
+#endif /* !STYLE_H */
diff --git a/test.lib b/test.lib
new file mode 100644
index 0000000..7714415
--- /dev/null
+++ b/test.lib
@@ -0,0 +1,62 @@
+EESchema-LIBRARY Version 2.3
+#encoding utf-8
+#
+# TEST
+#
+DEF TEST U 0 40 Y Y 1 F N
+F0 "U" -475 675 60 H V C CNN
+F1 "TEST" -425 -675 60 H V C CNN
+F2 "" 150 200 60 H I C CNN
+F3 "" 150 200 60 H I C CNN
+DRAW
+T 900 1050 -550 100 0 0 0 BLT Normal 1 L T
+T 0 900 450 100 0 0 0 NCC Normal 0 C C
+T 900 925 -550 100 0 0 0 NCC Normal 0 C C
+T 0 900 550 100 0 0 0 NLB Normal 0 L B
+T 900 1275 -550 100 0 0 0 NRB Normal 0 R B
+T 900 800 -550 100 0 0 0 NRC Normal 0 R C
+T 0 900 350 100 0 0 0 NRT Normal 0 R T
+P 2 0 0 0 750 -550 1325 -550 N
+P 2 0 0 0 800 -425 800 -650 N
+P 2 0 0 0 925 -425 925 -650 N
+P 2 0 0 0 1050 -425 1050 -650 N
+P 2 0 0 0 1275 -425 1275 -650 N
+A 975 -50 150 1800 -900 0 1 0 N 825 -50 975 -200
+A 975 50 150 1800 900 0 1 0 N 825 50 975 200
+A 1075 -50 150 -900 0 0 1 0 N 1075 -200 1225 -50
+A 1075 50 150 900 0 0 1 0 N 1075 200 1225 50
+C 1025 0 150 0 1 0 N
+S -550 600 550 -600 0 1 0 N
+P 2 0 1 0 -100 -100 100 100 N
+P 2 0 1 0 -100 100 100 -100 N
+P 2 0 1 0 825 350 1150 350 N
+P 2 0 1 0 825 450 1150 450 N
+P 2 0 1 0 825 550 1150 550 N
+P 2 0 1 0 900 600 900 275 N
+P 3 0 1 0 250 550 450 550 450 400 N
+P 10 0 1 0 775 0 775 200 825 250 1225 250 1275 200 1275 -200 1225 -250 825 -250 775 -200 775 0 N
+X R_IN 1 -750 500 200 R 50 50 1 1 I
+X R_OUT 2 -750 400 200 R 50 50 1 1 O
+X R_BIDIR 3 -750 300 200 R 50 50 1 1 O
+X R_TRI 4 -750 200 200 R 50 50 1 1 T
+X R_PASS 5 -750 100 200 R 50 50 1 1 P
+X R_UNSPEC 6 -750 0 200 R 50 50 1 1 U
+X R_PIN 7 -750 -100 200 R 50 50 1 1 W
+X R_POUT 8 -750 -200 200 R 50 50 1 1 w
+X R_OC 9 -750 -300 200 R 50 50 1 1 w
+X R_OE 10 -750 -400 200 R 50 50 1 1 w
+X D_CLOW 20 0 800 200 D 50 50 1 1 I CL
+X R_NC 11 -750 -500 200 R 50 50 1 1 N
+X D_ILOW 21 -100 800 200 D 50 50 1 1 I L
+X U_LINE 12 -50 -800 200 U 50 50 1 1 I
+X U_INV 13 50 -800 200 U 50 50 1 1 I I
+X U_CLK 14 150 -800 200 U 50 50 1 1 I C
+X U_INVCLK 15 250 -800 200 U 50 50 1 1 I IC
+X LEFT 16 750 -325 200 L 50 50 1 1 I
+X D_NONLOGIC 17 300 800 200 D 50 50 1 1 I X
+X D_FALLING 18 200 800 200 D 50 50 1 1 I F
+X D_OLOW 19 100 800 200 D 50 50 1 1 I V
+ENDDRAW
+ENDDEF
+#
+#End Library
diff --git a/test.pro b/test.pro
new file mode 100644
index 0000000..4695c8c
--- /dev/null
+++ b/test.pro
@@ -0,0 +1,34 @@
+update=22/05/2015 07:44:53
+version=1
+last_client=kicad
+[general]
+version=1
+RootSch=
+BoardNm=
+[pcbnew]
+version=1
+LastNetListRead=
+UseCmpFile=1
+PadDrill=0.600000000000
+PadDrillOvalY=0.600000000000
+PadSizeH=1.500000000000
+PadSizeV=1.500000000000
+PcbTextSizeV=1.500000000000
+PcbTextSizeH=1.500000000000
+PcbTextThickness=0.300000000000
+ModuleTextSizeV=1.000000000000
+ModuleTextSizeH=1.000000000000
+ModuleTextSizeThickness=0.150000000000
+SolderMaskClearance=0.000000000000
+SolderMaskMinWidth=0.000000000000
+DrawSegmentWidth=0.200000000000
+BoardOutlineThickness=0.100000000000
+ModuleOutlineThickness=0.150000000000
+[cvpcb]
+version=1
+NetIExt=net
+[eeschema]
+version=1
+LibDir=
+[eeschema/libraries]
+LibName1=test
diff --git a/test.sch b/test.sch
new file mode 100644
index 0000000..11c5613
--- /dev/null
+++ b/test.sch
@@ -0,0 +1,372 @@
+EESchema Schematic File Version 2
+LIBS:test
+LIBS:test-cache
+EELAYER 25 0
+EELAYER END
+$Descr A4 11693 8268
+encoding utf-8
+Sheet 1 3
+Title ""
+Date ""
+Rev ""
+Comp ""
+Comment1 ""
+Comment2 ""
+Comment3 ""
+Comment4 ""
+$EndDescr
+Text Notes 1450 1300 0 100 ~ 0
+Text, Right, Normal, 100 mil
+Wire Notes Line
+ 1400 1300 1250 1300
+Wire Notes Line
+ 1450 1350 1450 1500
+Text Notes 3750 1500 2 100 ~ 20
+Text, Left, Bold, 100 mil
+Wire Notes Line
+ 3800 1500 3950 1500
+Wire Notes Line
+ 3750 1550 3750 1700
+Text Notes 1050 1550 1 100 Italic 0
+Up, Italic
+Wire Notes Line
+ 1100 1550 1250 1550
+Wire Notes Line
+ 1050 1600 1050 1750
+Text Notes 4300 700 3 100 Italic 20
+Down, Bold italic
+Wire Notes Line
+ 4350 700 4500 700
+Wire Notes Line
+ 4300 650 4300 500
+Wire Wire Line
+ 4800 1100 5300 1100
+Wire Wire Line
+ 5050 850 5050 1350
+Wire Wire Line
+ 5550 850 5550 1100
+Wire Wire Line
+ 5550 1100 5550 1350
+Wire Wire Line
+ 6050 1100 5550 1100
+Connection ~ 5550 1100
+Wire Bus Line
+ 6750 600 6750 650
+Wire Bus Line
+ 6750 650 6750 800
+Wire Bus Line
+ 6750 800 6750 900
+Wire Bus Line
+ 6750 900 6750 1000
+Wire Bus Line
+ 6750 1000 6750 1050
+Wire Bus Line
+ 6750 1050 6750 1100
+Wire Bus Line
+ 6750 1100 6750 1250
+Wire Bus Line
+ 6750 1250 6750 1350
+Wire Bus Line
+ 6750 1350 6750 1400
+Entry Bus Bus
+ 6650 1250 6750 1350
+Wire Bus Line
+ 6200 1250 6650 1250
+Wire Wire Line
+ 6650 950 6200 950
+Entry Wire Line
+ 6650 950 6750 1050
+NoConn ~ 5050 850
+NoConn ~ 5050 1350
+NoConn ~ 5300 1100
+NoConn ~ 4800 1100
+Wire Wire Line
+ 1300 2400 2800 2400
+Text Label 1300 2400 0 60 ~ 0
+LOCAL_RIGHT_NORMAL
+Wire Wire Line
+ 1300 2600 2800 2600
+Text Label 2800 2600 2 60 ~ 12
+LOCAL_LEFT_BOLD
+Wire Wire Line
+ 3400 1800 3400 2900
+Text Label 3400 2900 1 60 ~ 12
+LOCAL_UP_ITALIC
+Wire Wire Line
+ 3700 1800 3700 2900
+Text Label 3700 1800 3 60 Italic 12
+LOCAL_DOWN_BI
+Wire Wire Line
+ 4650 2400 5350 2400
+Wire Wire Line
+ 4650 2700 5350 2700
+Text GLabel 4650 2400 0 60 Input ~ 0
+G_R_IN
+Text GLabel 5350 2700 2 60 Input ~ 0
+G_L_IN
+Text GLabel 4650 2700 0 60 Output ~ 0
+G_R_OUT
+Text GLabel 5350 2400 2 60 Output ~ 0
+G_L_OUT
+Wire Wire Line
+ 6100 2200 6100 2900
+Wire Wire Line
+ 6400 2200 6400 2900
+Text GLabel 6100 2200 1 60 BiDi ~ 0
+G_UP_BI
+Wire Wire Line
+ 4650 3000 4850 3000
+Wire Wire Line
+ 4850 3000 5350 3000
+Text GLabel 4650 3000 0 60 BiDi ~ 0
+G_R_BIDIR
+Text GLabel 5350 3000 2 60 UnSpc ~ 0
+G_LEFT_PASS
+Text GLabel 4650 3150 0 60 UnSpc ~ 0
+G_R_TRI
+Wire Wire Line
+ 4650 3150 4850 3150
+Wire Wire Line
+ 4850 3150 4850 3000
+Connection ~ 4850 3000
+Text GLabel 6100 2900 3 60 Input ~ 0
+G_DOWN_IN
+Text GLabel 6400 2200 1 60 Output ~ 0
+G_UP_OUT
+Text GLabel 6400 2900 3 60 UnSpc ~ 0
+G_DOWN_PASS
+Wire Wire Line
+ 7250 2700 7950 2700
+Wire Wire Line
+ 8750 2200 8750 2900
+Wire Wire Line
+ 9050 2200 9050 2900
+Wire Wire Line
+ 7250 3150 7450 3150
+Wire Wire Line
+ 7450 3150 7450 3000
+Connection ~ 7450 3000
+Text HLabel 7250 2400 0 60 Input ~ 0
+H_R_IN
+Text HLabel 7250 2700 0 60 Output ~ 0
+H_R_OUT
+Text HLabel 7250 3000 0 60 BiDi ~ 0
+H_R_BIDIR
+Text HLabel 7250 3150 0 60 UnSpc ~ 0
+H_R_TRI
+Text HLabel 7950 2400 2 60 Output ~ 0
+H_L_OUT
+Text HLabel 7950 2700 2 60 Input ~ 0
+H_L_IN
+Wire Wire Line
+ 7250 2400 7950 2400
+Text HLabel 7950 3000 2 60 UnSpc ~ 0
+H_LEFT_PASS
+Text HLabel 8750 2200 1 60 BiDi ~ 0
+H_UP_BI
+Text HLabel 8750 2900 3 60 Input ~ 0
+H_DOWN_IN
+Text HLabel 9050 2200 1 60 Output ~ 0
+H_UP_OUT
+Text HLabel 9050 2900 3 60 UnSpc ~ 0
+H_DOWN_PASS
+Wire Wire Line
+ 7250 3000 7450 3000
+Wire Wire Line
+ 7450 3000 7950 3000
+Text Notes 1300 3400 0 60 ~ 0
+60 mil Text
+Wire Notes Line
+ 1300 3150 1300 3550
+Wire Notes Line
+ 1850 3550 1850 3150
+Wire Notes Line
+ 1150 3300 2000 3300
+Wire Notes Line
+ 1150 3400 2000 3400
+Text GLabel 1400 3750 2 60 Output ~ 0
+GLOBAL
+Text GLabel 1800 3950 0 60 Output ~ 0
+GLOBAL
+Wire Notes Line
+ 1400 3550 1400 4150
+Wire Notes Line
+ 1800 3550 1800 4150
+Wire Notes Line
+ 1250 3700 1950 3700
+Wire Notes Line
+ 1250 3800 1950 3800
+Wire Notes Line
+ 1250 3900 1950 3900
+Wire Notes Line
+ 1250 4000 1950 4000
+Text HLabel 2950 3600 0 60 Input ~ 0
+HIERARCHICAL
+Text HLabel 2250 3750 2 60 Input ~ 0
+HIERARCHICAL
+Wire Notes Line
+ 2250 3400 2250 3950
+Wire Notes Line
+ 2100 3700 3100 3700
+Wire Notes Line
+ 2100 3650 3100 3650
+Wire Notes Line
+ 2100 3550 3100 3550
+Wire Notes Line
+ 2100 3800 3100 3800
+Wire Notes Line
+ 2950 3400 2950 3950
+$Comp
+L TEST U?
+U 1 1 57933A17
+P 1650 5200
+F 0 "U?" H 1175 5875 60 0000 C CNN
+F 1 "TEST" H 1225 4525 60 0000 C CNN
+F 2 "" H 1800 5400 60 0001 C CNN
+F 3 "" H 1800 5400 60 0001 C CNN
+ 1 1650 5200
+ 1 0 0 -1
+$EndComp
+$Comp
+L TEST U?
+U 1 1 57933A5F
+P 4650 4750
+F 0 "U?" H 4175 5425 60 0000 C CNN
+F 1 "TEST" H 4225 4075 60 0000 C CNN
+F 2 "" H 4800 4950 60 0001 C CNN
+F 3 "" H 4800 4950 60 0001 C CNN
+ 1 4650 4750
+ 0 1 1 0
+$EndComp
+$Comp
+L TEST U?
+U 1 1 57933A97
+P 7450 4900
+F 0 "U?" H 6975 5575 60 0000 C CNN
+F 1 "TEST" H 7025 4225 60 0000 C CNN
+F 2 "" H 7600 5100 60 0001 C CNN
+F 3 "" H 7600 5100 60 0001 C CNN
+ 1 7450 4900
+ -1 0 0 1
+$EndComp
+$Comp
+L TEST U?
+U 1 1 57933ACD
+P 9750 5250
+F 0 "U?" H 9275 5925 60 0000 C CNN
+F 1 "TEST" H 9325 4575 60 0000 C CNN
+F 2 "" H 9900 5450 60 0001 C CNN
+F 3 "" H 9900 5450 60 0001 C CNN
+ 1 9750 5250
+ 0 -1 -1 0
+$EndComp
+$Comp
+L TEST U?
+U 1 1 57933B03
+P 2450 6950
+F 0 "U?" H 1975 7625 60 0000 C CNN
+F 1 "TEST" H 2025 6275 60 0000 C CNN
+F 2 "" H 2600 7150 60 0001 C CNN
+F 3 "" H 2600 7150 60 0001 C CNN
+ 1 2450 6950
+ 1 0 0 1
+$EndComp
+$Comp
+L TEST U?
+U 1 1 57933B47
+P 5950 6900
+F 0 "U?" H 5475 7575 60 0000 C CNN
+F 1 "TEST" H 5525 6225 60 0000 C CNN
+F 2 "" H 6100 7100 60 0001 C CNN
+F 3 "" H 6100 7100 60 0001 C CNN
+ 1 5950 6900
+ -1 0 0 -1
+$EndComp
+Text Notes 900 7000 0 60 ~ 0
+X-flip
+Text Notes 4250 6900 0 60 ~ 0
+Y-flip
+$Comp
+L TEST U?
+U 1 1 57933BAB
+P 10200 2400
+F 0 "U?" H 9725 3075 60 0000 C CNN
+F 1 "TEST" H 9775 1725 60 0000 C CNN
+F 2 "" H 10350 2600 60 0001 C CNN
+F 3 "" H 10350 2600 60 0001 C CNN
+ 1 10200 2400
+ 0 1 -1 0
+$EndComp
+Text Notes 10000 850 0 60 ~ 0
+R+X
+$Sheet
+S 7250 790 800 560
+U 579BE133
+F0 "Sheet579BE132" 60
+F1 "file579BE132.sch" 60
+$EndSheet
+$Sheet
+S 7190 1790 770 400
+U 579BE13D
+F0 "Sheet579BE13C" 60
+F1 "file579BE13C.sch" 60
+$EndSheet
+Wire Bus Line
+ 6200 1200 6650 1200
+Entry Bus Bus
+ 6650 1200 6750 1100
+Wire Wire Line
+ 6650 900 6200 900
+Entry Wire Line
+ 6650 900 6750 800
+Wire Wire Line
+ 6850 750 7100 750
+Wire Wire Line
+ 6850 800 7100 800
+Entry Wire Line
+ 6750 900 6850 800
+Entry Wire Line
+ 6750 650 6850 750
+Entry Bus Bus
+ 6750 1250 6850 1150
+Entry Bus Bus
+ 6750 1000 6850 1100
+Wire Bus Line
+ 7100 1100 6850 1100
+Wire Bus Line
+ 7100 1150 6850 1150
+Wire Bus Line
+ 4750 1950 5700 1950
+Wire Wire Line
+ 4850 1850 4850 1500
+Wire Bus Line
+ 4950 1850 4950 1500
+Wire Wire Line
+ 5400 1850 5400 1500
+Wire Bus Line
+ 5500 1500 5500 1850
+Wire Wire Line
+ 4800 2250 4800 2050
+Wire Wire Line
+ 5300 2250 5300 2050
+Wire Bus Line
+ 5450 2250 5450 2050
+Wire Bus Line
+ 4900 2250 4900 2050
+Entry Wire Line
+ 4850 1850 4950 1950
+Entry Wire Line
+ 5200 1950 5300 2050
+Entry Wire Line
+ 4800 2050 4900 1950
+Entry Wire Line
+ 5300 1950 5400 1850
+Entry Bus Bus
+ 4950 1850 5050 1950
+Entry Bus Bus
+ 4900 2050 5000 1950
+Entry Bus Bus
+ 5400 1950 5500 1850
+Entry Bus Bus
+ 5350 1950 5450 2050
+$EndSCHEMATC
diff --git a/test/README b/test/README
new file mode 100644
index 0000000..fdc81f2
--- /dev/null
+++ b/test/README
@@ -0,0 +1,24 @@
+Setup
+-----
+
+git clone https://neo900.org/git/ee.git neo900-ee
+cd neo900-ee
+git reset --hard 57eebdcf573311c049bc57527bc03a517aff0fef
+
+cd ..
+
+git clone git://projects.qi-hardware.com/kicad-libs.git
+cd kicad-libs
+git reset --hard 143fa7fe10cabbfe1cb12d010c7426d482d7e6f4
+
+cd ..
+
+./genpng ref
+
+
+Run test
+--------
+
+./genpng
+./comp
+qiv -t _diff*.png
diff --git a/test/comp b/test/comp
new file mode 100755
index 0000000..b406607
--- /dev/null
+++ b/test/comp
@@ -0,0 +1,29 @@
+#!/bin/sh
+
+usage()
+{
+ echo "usage: $0 [dir]" 1>&2
+ exit 1
+}
+
+
+[ "$2" ] && usage
+[ "${1#-}" != "$1" ] && usage
+
+dir=${1:-.}
+
+diffs=0
+rm -f $dir/_diff*png
+for n in $dir/out*.png; do
+ out=`basename "$n"`
+ ref=$dir/ref${out#out}
+ diff=$dir/_diff${out#out}
+ if ! compare -metric AE $ref $n - >/dev/null; then
+ diffs=`expr $diffs + 1`
+ compare -metric AE $ref $n $diff
+ fi
+done
+echo
+[ $diffs = 0 ] && exit 0
+echo "$diffs difference(s)" 1>&2
+exit 1
diff --git a/test/genpng b/test/genpng
new file mode 100755
index 0000000..0d73ec1
--- /dev/null
+++ b/test/genpng
@@ -0,0 +1,37 @@
+#!/bin/sh
+
+usage()
+{
+ echo "usage: $0 [[dir] prefix]" 1>&2
+ exit 1
+}
+
+
+[ "$3" ] && usage
+[ "${1#-}" != "$1" ] && usage
+
+dir=.
+if [ "$2" ]; then
+ dir=$1
+ shift
+fi
+prefix=${1:-out}
+
+sheet=1
+while [ $sheet -le 38 ]; do
+ echo -n .
+ sn=`printf '%02d' $sheet`
+ file=$dir/$prefix$sn.png
+ if [ $sheet = 1 ]; then
+ in=$dir/neo900-ee/hw/neo900.sch
+ else
+ in=$dir/neo900-ee/hw/neo900_SS_`expr $sheet - 1`.sch
+ fi
+ file=$dir/$prefix$sn.png
+ $dir/../eeshow $dir/neo900-ee/hw/neo900.lib \
+ $dir/kicad-libs/components/powered.lib "$in" \
+ -- fig -t $dir/frame.fig SHEET=$sn |
+ fig2dev -L png -m 2 >$file
+ sheet=`expr $sheet + 1`
+done
+echo
diff --git a/text.c b/text.c
new file mode 100644
index 0000000..68754c8
--- /dev/null
+++ b/text.c
@@ -0,0 +1,158 @@
+/*
+ * text.c - FIG text object
+ *
+ * Written 2016 by Werner Almesberger
+ * Copyright 2016 by Werner Almesberger
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ */
+
+
+#include <stddef.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+
+#include "util.h"
+#include "misc.h"
+#include "style.h"
+#include "gfx.h"
+#include "text.h"
+
+
+void text_init(struct text *txt)
+{
+ txt->s = NULL;
+ txt->size = 0;
+ txt->x = txt->y = 0;
+ txt->rot = 0;
+ txt->hor = text_mid;
+ txt->vert = text_mid;
+}
+
+
+void text_free(struct text *txt)
+{
+ free((void *) txt->s);
+ txt->s = NULL;
+}
+
+
+void text_set(struct text *txt, const char *s)
+{
+ free((void *) txt->s);
+ txt->s = stralloc(s);
+}
+
+
+void text_rot(struct text *txt, int deg)
+{
+ txt->rot = angle_add(txt->rot, deg);
+}
+
+
+enum text_align text_flip(enum text_align align)
+{
+ switch (align) {
+ case text_min:
+ return text_max;
+ case text_mid:
+ return text_mid;
+ case text_max:
+ return text_min;
+ default:
+ abort();
+ }
+}
+
+
+void text_flip_x(struct text *txt)
+{
+ txt->rot = angle_add(txt->rot, 180);
+ txt->hor = text_flip(txt->hor);
+ // @@@ flip vert, too ?
+}
+
+
+static int align(int dim, enum text_align align)
+{
+ switch (align) {
+ case text_min:
+ return 0;
+ case text_mid:
+ return dim / 2;
+ case text_max:
+ return dim;
+ default:
+ abort();
+ }
+}
+
+
+void text_fig(const struct text *txt, int color, unsigned layer)
+{
+ char *buf = stralloc(txt->s);
+ char *tmp = buf;
+ const char *s;
+ int x = txt->x;
+ int y = txt->y;
+
+ x += rx(0, align(txt->size, txt->vert), txt->rot);
+ y += ry(0, align(txt->size, txt->vert), txt->rot);
+ while (1) {
+ s = strtok(tmp, "\n");
+ if (!s)
+ break;
+ tmp = NULL;
+ gfx_text(x, y, s, txt->size, txt->hor, txt->rot, color, layer);
+ x += rx(0, NEWLINE_SKIP * txt->size, txt->rot);
+ y += ry(0, NEWLINE_SKIP * txt->size, txt->rot);
+ }
+ free(buf);
+}
+
+
+void text_rel(const struct text *txt, enum text_align xr, enum text_align yr,
+ int dx, int dy, int *res_x, int *res_y)
+{
+ int width = gfx_text_width(txt->s, txt->size);
+
+ dx -= align(width, txt->hor);
+ dy += align(txt->size, txt->vert);
+ dx += align(width, xr);
+ dy -= align(txt->size, yr);
+ if (res_x)
+ *res_x = txt->x + rx(dx, dy, txt->rot);
+ if (res_y)
+ *res_y = txt->y + ry(dx, dy, txt->rot);
+}
+
+
+void text_shift(struct text *txt, enum text_align xr, enum text_align yr,
+ int dx, int dy)
+{
+ text_rel(txt, xr, yr, dx, dy, &txt->x, &txt->y);
+}
+
+
+int text_rel_x(const struct text *txt, enum text_align xr, enum text_align yr,
+ int dx, int dy)
+{
+ int x;
+
+ text_rel(txt, xr, yr, dx, dy, &x, NULL);
+ return x;
+}
+
+
+int text_rel_y(const struct text *txt, enum text_align xr, enum text_align yr,
+ int dx, int dy)
+{
+ int y;
+
+ text_rel(txt, xr, yr, dx, dy, NULL, &y);
+ return y;
+}
diff --git a/text.h b/text.h
new file mode 100644
index 0000000..05f1ffb
--- /dev/null
+++ b/text.h
@@ -0,0 +1,58 @@
+/*
+ * text.h - FIG text object
+ *
+ * Written 2016 by Werner Almesberger
+ * Copyright 2016 by Werner Almesberger
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ */
+
+
+#ifndef TEXT_H
+#define TEXT_H
+
+
+/* use constants of FIG text sub_type */
+
+enum text_align {
+ text_min = 0, // left or bottom
+ text_mid = 1, // center
+ text_max = 2, // right or top
+};
+
+enum text_style {
+ text_normal,
+};
+
+struct text {
+ const char *s;
+ int size;
+ int x, y;
+ int rot;
+ enum text_align hor;
+ enum text_align vert;
+};
+
+void text_init(struct text *txt);
+void text_free(struct text *txt);
+
+void text_set(struct text *txt, const char *s);
+void text_rot(struct text *txt, int deg);
+void text_flip_x(struct text *txt);
+enum text_align text_flip(enum text_align align);
+
+void text_fig(const struct text *txt, int color, unsigned layer);
+
+void text_rel(const struct text *txt, enum text_align xr, enum text_align yr,
+ int dx, int dy, int *res_x, int *res_y);
+void text_shift(struct text *txt, enum text_align xr, enum text_align yr,
+ int dx, int dy);
+int text_rel_x(const struct text *txt, enum text_align xr, enum text_align yr,
+ int dx, int dy);
+int text_rel_y(const struct text *txt, enum text_align xr, enum text_align yr,
+ int dx, int dy);
+
+#endif /* !TEXT_H */
diff --git a/util.h b/util.h
new file mode 100644
index 0000000..ea40ce5
--- /dev/null
+++ b/util.h
@@ -0,0 +1,52 @@
+/*
+ * util.h - Common utility functions
+ *
+ * Written 2016 by Werner Almesberger
+ * Copyright 2016 Werner Almesberger
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ */
+
+
+#ifndef UTIL_H
+#define UTIL_H
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+
+#define alloc_size(s) \
+ ({ void *alloc_size_tmp = malloc(s); \
+ if (!alloc_size_tmp) { \
+ perror("malloc"); \
+ exit(1); \
+ } \
+ alloc_size_tmp; })
+
+#define alloc_type(t) ((t *) alloc_size(sizeof(t)))
+
+#define stralloc(s) \
+ ({ char *stralloc_tmp = strdup(s); \
+ if (!stralloc_tmp) { \
+ perror("strdup"); \
+ exit(1); \
+ } \
+ stralloc_tmp; })
+
+
+#define ARRAY_ELEMENTS(a) (sizeof(a) / sizeof(a[0]))
+#define ARRAY_END(a) ((a) + ARRAY_ELEMENTS(a))
+
+
+#define swap(a, b) \
+ ({ typeof(a) _tmp = (a); a = (b); b = _tmp; })
+
+
+#define unsupported(s) \
+ fprintf(stderr, __FILE__ ":%d: unsupported: " s "\n", __LINE__)
+
+#endif /* !UTIL_H */