Browse Source

Initial Commit

Billy Barrow 2 years ago
commit
ab05239787

+ 7 - 0
.gitignore

@@ -0,0 +1,7 @@
+/viewer
+/meson-*
+/lib
+.ninja_deps
+.ninja_log
+build.ninja
+compile_commands.json

+ 15 - 0
src/lib/NodeActions/Emphisis.vala

@@ -0,0 +1,15 @@
+namespace GtkCommonMark.NodeActions {
+
+    public class Emphisis : SimpleNodeAction {
+
+        public Emphisis(CMark.Node node, Gtk.TextMark start_mark) {
+            base(node, start_mark);
+        }
+
+        public override Gtk.TextTag get_tag(TagManager tags) {
+            return tags.emphisis;
+        }
+
+    }
+
+}

+ 17 - 0
src/lib/NodeActions/Heading.vala

@@ -0,0 +1,17 @@
+
+
+namespace GtkCommonMark.NodeActions {
+
+    public class Heading : BlockNodeAction {
+
+        public Heading(CMark.Node node, Gtk.TextMark start_mark) {
+            base(node, start_mark);
+        }
+
+        public override Gtk.TextTag get_tag(TagManager tags) {
+            return tags.get_heading(node.get_heading_level());
+        }
+
+    }
+
+}

+ 64 - 0
src/lib/NodeActions/NodeAction.vala

@@ -0,0 +1,64 @@
+
+namespace GtkCommonMark.NodeActions {
+
+    public abstract class NodeAction : Object {
+
+        public unowned CMark.Node node {get; set;}
+        public Gtk.TextMark start_mark {get; set;}
+
+        protected NodeAction(CMark.Node node, Gtk.TextMark start_mark) {
+            this.node = node;
+            this.start_mark = start_mark;
+        }
+
+        public abstract void at_start(Gtk.TextBuffer buffer, Gtk.TextIter iter);
+        public abstract void at_end(Gtk.TextBuffer buffer, Gtk.TextIter iter);
+        public abstract void at_after_end(Gtk.TextBuffer buffer, Gtk.TextIter iter);
+
+        public abstract void at_before_child(Gtk.TextBuffer buffer, Gtk.TextIter iter, CMark.Node child);
+        public abstract void at_after_child(Gtk.TextBuffer buffer, Gtk.TextIter iter, CMark.Node child);
+
+        public abstract Gtk.TextTag get_tag(TagManager tags);
+    }
+
+    public class SimpleNodeAction : NodeAction {
+
+        public SimpleNodeAction(CMark.Node node, Gtk.TextMark start_mark) {
+            base(node, start_mark);
+        }
+
+        public override void at_start(Gtk.TextBuffer buffer, Gtk.TextIter iter) {
+            var literal = node.get_literal();
+            if(literal != null) {
+                buffer.insert(ref iter, literal, -1);
+            }
+        }
+        public override void at_end(Gtk.TextBuffer buffer, Gtk.TextIter iter) {
+            // No-op
+        }
+        public override void at_after_end(Gtk.TextBuffer buffer, Gtk.TextIter iter) {
+            // No-op
+        }
+        public override void at_before_child(Gtk.TextBuffer buffer, Gtk.TextIter iter, CMark.Node child) {
+            // No-op
+        }
+        public override void at_after_child(Gtk.TextBuffer buffer, Gtk.TextIter iter, CMark.Node child) {
+            // No-op
+        }
+        public override Gtk.TextTag get_tag(TagManager tags) {
+                return tags.none;
+        }
+
+    }
+
+    public class BlockNodeAction : SimpleNodeAction {
+
+        public BlockNodeAction(CMark.Node node, Gtk.TextMark start_mark) {
+            base(node, start_mark);
+        }
+        public override void at_end(Gtk.TextBuffer buffer, Gtk.TextIter iter) {
+            buffer.insert(ref iter, "\n", -1);
+        }
+    }
+
+}

+ 15 - 0
src/lib/NodeActions/Strong.vala

@@ -0,0 +1,15 @@
+namespace GtkCommonMark.NodeActions {
+
+    public class Strong : SimpleNodeAction {
+
+        public Strong(CMark.Node node, Gtk.TextMark start_mark) {
+            base(node, start_mark);
+        }
+
+        public override Gtk.TextTag get_tag(TagManager tags) {
+            return tags.strong;
+        }
+
+    }
+
+}

+ 269 - 0
src/lib/Reader.vala

@@ -0,0 +1,269 @@
+
+namespace GtkCommonMark {
+
+
+    public class Reader : Object {
+
+        private TagManager tags;
+        private Gtk.TextBuffer buffer;
+
+        private CMark.Iter cm_iter = null;
+        private Gtk.TextIter tb_iter;
+        private List<NodeActions.NodeAction> node_actions;
+
+        public Reader(Gtk.TextBuffer buffer, TagManager tag_manager) {
+            this.tags = tag_manager;
+            this.buffer = buffer;
+        }
+
+        public void append_node(CMark.Node root) {
+            cm_iter = new CMark.Iter(root);
+            buffer.get_end_iter(out tb_iter);
+            node_actions = new List<NodeActions.NodeAction>();
+
+            CMark.EVENT event = CMark.EVENT.DONE;
+            while((event = cm_iter.next()) != CMark.EVENT.DONE) {
+                if(event == CMark.EVENT.EXIT) {
+                    pop_and_finalise_node_action();
+                    continue;
+                }
+
+                unowned var node = cm_iter.get_node();
+                var start_mark = buffer.create_mark(null, tb_iter, true);
+                var action = build_action_for_node(node, start_mark);
+                node_actions.prepend(action);
+
+                prepare_for_child();
+                action.at_start(buffer, tb_iter);
+
+                if(is_leaf(node.get_type())) {
+                    pop_and_finalise_node_action();
+                }
+            }
+
+        }
+
+        private void pop_and_finalise_node_action() {
+            var action = node_actions.nth_data(0);
+            Gtk.TextIter start_iter;
+            buffer.get_iter_at_mark(out start_iter, action.start_mark);
+            buffer.apply_tag(action.get_tag(tags), start_iter, tb_iter);
+            buffer.delete_mark(action.start_mark);
+            action.at_end(buffer, tb_iter);
+            finalise_after_child();
+            node_actions.remove(action);
+        }
+
+        private void prepare_for_child() {
+            if(node_actions.length() < 2) {
+                return;
+            }
+            var child_action = node_actions.nth_data(0);
+            var parent_action = node_actions.nth_data(1);
+            parent_action.at_before_child(buffer, tb_iter, child_action.node);
+        }
+
+        private void finalise_after_child() {
+            if(node_actions.length() < 2) {
+                return;
+            }
+            var child_action = node_actions.nth_data(0);
+            var parent_action = node_actions.nth_data(1);
+            parent_action.at_after_child(buffer, tb_iter, child_action.node);
+        }
+
+
+        private bool is_block(CMark.NODE_TYPE type) {
+            switch (type) {
+                case CMark.NODE_TYPE.DOCUMENT:
+                case CMark.NODE_TYPE.BLOCK_QUOTE:
+                case CMark.NODE_TYPE.LIST:
+                case CMark.NODE_TYPE.ITEM:
+                case CMark.NODE_TYPE.CODE_BLOCK:
+                case CMark.NODE_TYPE.HTML_BLOCK:
+                case CMark.NODE_TYPE.CUSTOM_BLOCK:
+                case CMark.NODE_TYPE.PARAGRAPH:
+                case CMark.NODE_TYPE.HEADING:
+                case CMark.NODE_TYPE.THEMATIC_BREAK:
+                    return true;
+                case CMark.NODE_TYPE.TEXT:
+                case CMark.NODE_TYPE.SOFTBREAK:
+                case CMark.NODE_TYPE.LINEBREAK:
+                case CMark.NODE_TYPE.CODE:
+                case CMark.NODE_TYPE.HTML_INLINE:
+                case CMark.NODE_TYPE.CUSTOM_INLINE:
+                case CMark.NODE_TYPE.EMPH:
+                case CMark.NODE_TYPE.STRONG:
+                case CMark.NODE_TYPE.LINK:
+                case CMark.NODE_TYPE.IMAGE:
+                case CMark.NODE_TYPE.NONE:
+                default:
+                    return false;
+            }
+            
+        }
+
+        private bool is_leaf(CMark.NODE_TYPE type) {
+            switch (type) {
+                case CMark.NODE_TYPE.HTML_BLOCK:
+                case CMark.NODE_TYPE.THEMATIC_BREAK:
+                case CMark.NODE_TYPE.CODE_BLOCK:
+                case CMark.NODE_TYPE.TEXT:
+                case CMark.NODE_TYPE.SOFTBREAK:
+                case CMark.NODE_TYPE.LINEBREAK:
+                case CMark.NODE_TYPE.CODE:
+                case CMark.NODE_TYPE.HTML_INLINE:
+                    return true;
+                case CMark.NODE_TYPE.DOCUMENT:
+                case CMark.NODE_TYPE.BLOCK_QUOTE:
+                case CMark.NODE_TYPE.LIST:
+                case CMark.NODE_TYPE.ITEM:
+                case CMark.NODE_TYPE.CUSTOM_BLOCK:
+                case CMark.NODE_TYPE.PARAGRAPH:
+                case CMark.NODE_TYPE.HEADING:
+                case CMark.NODE_TYPE.CUSTOM_INLINE:
+                case CMark.NODE_TYPE.EMPH:
+                case CMark.NODE_TYPE.STRONG:
+                case CMark.NODE_TYPE.LINK:
+                case CMark.NODE_TYPE.IMAGE:
+                case CMark.NODE_TYPE.NONE:
+                default:
+                    return false;
+            }
+            
+        }
+
+        protected virtual NodeActions.NodeAction build_action_for_node(CMark.Node node, Gtk.TextMark start_mark) {
+            var type = node.get_type();
+            switch (type) {
+                case CMark.NODE_TYPE.BLOCK_QUOTE:
+                    return build_action_for_block_quote(node, start_mark);
+                case CMark.NODE_TYPE.LIST:
+                    return build_action_for_list(node, start_mark);
+                case CMark.NODE_TYPE.ITEM:
+                    return build_action_for_item(node, start_mark);
+                case CMark.NODE_TYPE.CODE_BLOCK:
+                    return build_action_for_code_block(node, start_mark);
+                case CMark.NODE_TYPE.HTML_BLOCK:
+                    return build_action_for_html_block(node, start_mark);
+                case CMark.NODE_TYPE.CUSTOM_BLOCK:
+                    return build_action_for_custom_block(node, start_mark);
+                case CMark.NODE_TYPE.PARAGRAPH:
+                    return build_action_for_paragraph(node, start_mark);
+                case CMark.NODE_TYPE.HEADING:
+                    return build_action_for_heading(node, start_mark);
+                case CMark.NODE_TYPE.THEMATIC_BREAK:
+                    return build_action_for_thematic_break(node, start_mark);
+                case CMark.NODE_TYPE.TEXT:
+                    return build_action_for_text(node, start_mark);
+                case CMark.NODE_TYPE.SOFTBREAK:
+                    return build_action_for_soft_break(node, start_mark);
+                case CMark.NODE_TYPE.LINEBREAK:
+                    return build_action_for_line_break(node, start_mark);
+                case CMark.NODE_TYPE.CODE:
+                    return build_action_for_code(node, start_mark);
+                case CMark.NODE_TYPE.HTML_INLINE:
+                    return build_action_for_inline_html(node, start_mark);
+                case CMark.NODE_TYPE.CUSTOM_INLINE:
+                    return build_action_for_inline_custom(node, start_mark);
+                case CMark.NODE_TYPE.EMPH:
+                    return build_action_for_emphisis(node, start_mark);
+                case CMark.NODE_TYPE.STRONG:
+                    return build_action_for_bold(node, start_mark);
+                case CMark.NODE_TYPE.LINK:
+                    return build_action_for_link(node, start_mark);
+                case CMark.NODE_TYPE.IMAGE:
+                    return build_action_for_embed(node, start_mark);
+
+                default:
+                    return build_default_action(node, start_mark);
+            }
+        }
+
+
+        protected virtual NodeActions.NodeAction build_action_for_block_quote(CMark.Node node, Gtk.TextMark start_mark) {
+            return build_default_action(node, start_mark);
+        }
+
+        protected virtual NodeActions.NodeAction build_action_for_list(CMark.Node node, Gtk.TextMark start_mark) {
+            return build_default_action(node, start_mark);
+        }
+
+        protected virtual NodeActions.NodeAction build_action_for_item(CMark.Node node, Gtk.TextMark start_mark) {
+            return build_default_action(node, start_mark);
+        }
+
+        protected virtual NodeActions.NodeAction build_action_for_code_block(CMark.Node node, Gtk.TextMark start_mark) {
+            return build_default_action(node, start_mark);
+        }
+
+        protected virtual NodeActions.NodeAction build_action_for_html_block(CMark.Node node, Gtk.TextMark start_mark) {
+            return build_default_action(node, start_mark);
+        }
+
+        protected virtual NodeActions.NodeAction build_action_for_custom_block(CMark.Node node, Gtk.TextMark start_mark) {
+            return build_default_action(node, start_mark);
+        }
+
+        protected virtual NodeActions.NodeAction build_action_for_paragraph(CMark.Node node, Gtk.TextMark start_mark) {
+            return build_default_action(node, start_mark);
+        }
+
+        protected virtual NodeActions.NodeAction build_action_for_heading(CMark.Node node, Gtk.TextMark start_mark) {
+            return new NodeActions.Heading(node, start_mark);
+        }
+
+        protected virtual NodeActions.NodeAction build_action_for_thematic_break(CMark.Node node, Gtk.TextMark start_mark) {
+            return build_default_action(node, start_mark);
+        }
+
+        // Inline Elements
+        protected virtual NodeActions.NodeAction build_action_for_text(CMark.Node node, Gtk.TextMark start_mark) {
+            return build_default_action(node, start_mark);
+        }
+
+        protected virtual NodeActions.NodeAction build_action_for_soft_break(CMark.Node node, Gtk.TextMark start_mark) {
+            return build_default_action(node, start_mark);
+        }
+
+        protected virtual NodeActions.NodeAction build_action_for_line_break(CMark.Node node, Gtk.TextMark start_mark) {
+            return build_default_action(node, start_mark);
+        }
+
+        protected virtual NodeActions.NodeAction build_action_for_code(CMark.Node node, Gtk.TextMark start_mark) {
+            return build_default_action(node, start_mark);
+        }
+
+        protected virtual NodeActions.NodeAction build_action_for_inline_html(CMark.Node node, Gtk.TextMark start_mark) {
+            return build_default_action(node, start_mark);
+        }
+
+        protected virtual NodeActions.NodeAction build_action_for_inline_custom(CMark.Node node, Gtk.TextMark start_mark) {
+            return build_default_action(node, start_mark);
+        }
+
+        protected virtual NodeActions.NodeAction build_action_for_emphisis(CMark.Node node, Gtk.TextMark start_mark) {
+            return new NodeActions.Emphisis(node, start_mark);
+        }
+
+        protected virtual NodeActions.NodeAction build_action_for_bold(CMark.Node node, Gtk.TextMark start_mark) {
+            return new NodeActions.Strong(node, start_mark);
+        }
+
+        protected virtual NodeActions.NodeAction build_action_for_link(CMark.Node node, Gtk.TextMark start_mark) {
+            return build_default_action(node, start_mark);
+        }
+
+        protected virtual NodeActions.NodeAction build_action_for_embed(CMark.Node node, Gtk.TextMark start_mark) {
+            return build_default_action(node, start_mark);
+        }
+
+        protected virtual NodeActions.NodeAction build_default_action(CMark.Node node, Gtk.TextMark start_mark) {
+            if(is_block(node.get_type())) {
+                return new NodeActions.BlockNodeAction(node, start_mark);
+            }
+            return new NodeActions.SimpleNodeAction(node, start_mark);
+        }
+        
+    }
+}

+ 96 - 0
src/lib/TagManager.vala

@@ -0,0 +1,96 @@
+
+namespace GtkCommonMark {
+
+
+    public class TagManager {
+
+        private Gtk.TextBuffer buffer;
+
+        public TagManager(Gtk.TextBuffer buffer) {
+            this.buffer = buffer;
+            build_tags();
+            update_tags();
+        }
+
+        private void build_tags() {
+            none = buffer.create_tag(null);
+
+            document = buffer.create_tag(null);
+
+            heading_l1 = buffer.create_tag(null);
+            heading_l2 = buffer.create_tag(null);
+            heading_l3 = buffer.create_tag(null);
+            heading_l4 = buffer.create_tag(null);
+            heading_l5 = buffer.create_tag(null);
+            heading_l6 = buffer.create_tag(null);
+
+            emphisis = buffer.create_tag(null);
+            strong = buffer.create_tag(null);
+
+            code = buffer.create_tag(null);
+            code_block = buffer.create_tag(null);
+
+            quote = buffer.create_tag(null);
+        }
+
+        private void update_tags() {
+            configure_heading(heading_l1, 1);
+            configure_heading(heading_l2, 2);
+            configure_heading(heading_l3, 3);
+            configure_heading(heading_l4, 4);
+            configure_heading(heading_l5, 5);
+            configure_heading(heading_l6, 6);
+
+            emphisis.style = Pango.Style.ITALIC;
+            strong.weight = 800;
+
+        }
+
+        public Gtk.TextTag none {get; private set;}
+
+        public Gtk.TextTag document {get; private set;}
+
+        public Gtk.TextTag heading_l1 {get; private set;}
+        public Gtk.TextTag heading_l2 {get; private set;}
+        public Gtk.TextTag heading_l3 {get; private set;}
+        public Gtk.TextTag heading_l4 {get; private set;}
+        public Gtk.TextTag heading_l5 {get; private set;}
+        public Gtk.TextTag heading_l6 {get; private set;}
+
+        public Gtk.TextTag emphisis {get; private set;}
+        public Gtk.TextTag strong {get; private set;}
+        public Gtk.TextTag underline {get; private set;}
+
+        public Gtk.TextTag code {get; private set;}
+        public Gtk.TextTag code_block {get; private set;}
+
+        public Gtk.TextTag quote {get; private set;}
+
+
+        public Gtk.TextTag get_heading(int level) {
+            switch (level) {
+                case 1:
+                    return heading_l1;
+                case 2:
+                    return heading_l2;
+                case 3:
+                    return heading_l3;
+                case 4:
+                    return heading_l4;
+                case 5:
+                    return heading_l5;
+                case 6:
+                    return heading_l6;
+
+                default:
+                    assert_not_reached();
+            }
+        }
+
+        private void configure_heading (Gtk.TextTag tag, int level) {
+            tag.size_points = 26 - (level * 2);
+        }
+
+    }
+
+}

+ 39 - 0
src/lib/meson.build

@@ -0,0 +1,39 @@
+vapi_dir = meson.current_source_dir() / 'vapi'
+
+add_project_arguments(['--disable-warnings', '--enable-checking','--vapidir', vapi_dir], language: 'vala')
+
+dependencies = [
+    dependency('glib-2.0'),
+    dependency('gobject-2.0'),
+    dependency('gio-2.0'),
+    dependency('gtk4'),
+    meson.get_compiler('vala').find_library('libcmark', dirs: vapi_dir),
+    meson.get_compiler('c').find_library('cmark'),
+]
+
+sources = files('Reader.vala')
+sources += files('TagManager.vala')
+sources += files('NodeActions/NodeAction.vala')
+sources += files('NodeActions/Heading.vala')
+sources += files('NodeActions/Emphisis.vala')
+sources += files('NodeActions/Strong.vala')
+
+libgtkcmark = shared_library('gtkcommonmark', sources,
+    dependencies: dependencies,
+    install: true,
+    vala_gir: 'GtkCommonMark-1.0.gir',
+    install_dir: [true, true, true, true]
+)
+libgtkcmark_dep = declare_dependency(link_with: libgtkcmark, include_directories: include_directories('.'))
+
+pkg = import('pkgconfig')
+pkg.generate(libgtkcmark,
+    version : '0.1',
+    name : 'libgtkcommonmark',)
+    
+g_ir_compiler = find_program('g-ir-compiler')
+custom_target('gtkcommonmark typelib', command: [g_ir_compiler, '--shared-library=libgtkcommonmark.so', '--output', '@OUTPUT@', meson.current_build_dir() / 'GtkCommonMark-1.0.gir'],
+              output: 'GtkCommonMark-1.0.typelib',
+              depends: libgtkcmark,
+              install: true,
+              install_dir: get_option('libdir') / 'girepository-1.0')

+ 563 - 0
src/lib/vapi/libcmark.vapi

@@ -0,0 +1,563 @@
+/*
+ * Copyright (c) 2018 Fabio Comuni <fabrix.xm@gmail.com>
+ *
+ * This library is released under BSD2 license
+ *
+ * https://github.com/commonmark/cmark
+ */
+
+ [CCode (cprefix = "cmark_", cheader_filename = "cmark.h")]
+ namespace CMark {
+ 
+     /**
+      * Options
+      *
+      * TODO: separate rendering and parsing related options
+      */
+     [CCode (cname = "int", cprefix = "CMARK_OPT_", has_type_id = false)]
+     [Flags]
+     public enum OPT {
+         DEFAULT,
+ 
+         /**
+         * Include a ``data−sourcepos`` attribute on all block elements.
+         */
+         SOURCEPOS,
+         /**
+         * Render ``softbreak`` elements as hard line breaks.
+         */
+         HARDBREAKS,
+         /**
+         * Suppress raw HTML and unsafe links
+         *
+         * Suppress (javascript:, vbscript:, file:, and data:,
+         * except for image/png, image/gif, image/jpeg, or image/webp mime types).
+         * Raw HTML is replaced by a placeholder HTML comment.
+         * Unsafe links are replaced by empty strings.
+         */
+         SAFE,
+         /**
+         * Render ``softbreak`` elements as spaces.
+         */
+         NOBREAKS,
+ 
+         /**
+         * Legacy option (no effect).
+         */
+         NORMALIZE,
+         /**
+         * Validate UTF−8 in the input before parsing, replacing illegal sequences with the replacement character U+FFFD.
+         */
+         VALIDATE_UTF8,
+         /**
+         * Convert straight quotes to curly, −−− to em dashes, −− to en dashes.
+         */
+         SMART
+     }
+ 
+ 
+     [CCode (cname = "cmark_node_type", cprefix = "CMARK_NODE_", has_type_id = false)]
+     public enum NODE_TYPE {
+       /* Error status */
+       NONE,
+ 
+       /* Block */
+       DOCUMENT,
+       BLOCK_QUOTE,
+       LIST,
+       ITEM,
+       CODE_BLOCK,
+       HTML_BLOCK,
+       CUSTOM_BLOCK,
+       PARAGRAPH,
+       HEADING,
+       THEMATIC_BREAK,
+ 
+       FIRST_BLOCK, // = DOCUMENT,
+       LAST_BLOCK, // = THEMATIC_BREAK,
+ 
+       /* Inline */
+       TEXT,
+       SOFTBREAK,
+       LINEBREAK,
+       CODE,
+       HTML_INLINE,
+       CUSTOM_INLINE,
+       EMPH,
+       STRONG,
+       LINK,
+       IMAGE,
+ 
+       FIRST_INLINE, // = TEXT,
+       LAST_INLINE // = IMAGE,
+     }
+ 
+ 
+     [CCode (cname = "cmark_list_type", has_type_id = false)]
+     public enum LIST_TYPE {
+         [CCode (cname="CMARK_NO_LIST")]
+         NO_LIST,
+         [CCode (cname="CMARK_BULLET_LIST")]
+         BULLET,
+         [CCode (cname="CMARK_ORDERED_LIST")]
+         ORDERED
+     }
+ 
+     [CCode (cname = "cmark_delim_type", has_type_id = false)]
+     public enum DELIM_TYPE {
+         [CCode (cname="CMARK_NO_DELIM")]
+         NO_DELIM,
+         [CCode (cname="CMARK_PERION_DELIM")]
+         PERIOD,
+         [CCode (cname="CMARK_PAREN_DELIM")]
+         PAREN
+     }
+ 
+     [CCode (cname = "cmark_event_type", cprefix = "CMARK_EVENT_", has_type_id = false)]
+     public enum EVENT {
+         NONE,
+         DONE,
+         ENTER,
+         EXIT
+     }
+ 
+     /**
+     * The library version as integer for runtime checks.
+     *
+     *
+     *  * Bits 16−23 contain the major version.
+     *  * Bits 8−15 contain the minor version.
+     *  * Bits 0−7 contain the patchlevel.
+     *
+     * In hexadecimal format, the number 0x010203 represents version 1.2.3.
+     */
+     public int version();
+ 
+     /**
+     * The library version string for runtime checks.
+     */
+     public unowned string version_string();
+ 
+     /**
+     * Convert text from CommonMark Markdown to HTML.
+     *
+     * Convert text (assumed to be a UTF−8 encoded string) from CommonMark Markdown to HTML,
+     * returning a null−terminated, UTF−8−encoded string.
+     * It is the caller’s responsibility to free the returned buffer.
+     */
+     [CCode (cname="cmark_markdown_to_html")]
+     public unowned string to_html([CCode (type="const char*", array_length_type = "size_t")] uint8[] text, OPT options);
+ 
+ 
+     /**
+     *
+     */
+     [Compact, CCode (cname = "struct cmark_node", cprefix = "cmark_node_", free_function = "cmark_node_free")]
+     public class Node {
+         /**
+         * Parse a CommonMark document in buffer
+         *
+         * Returns a pointer to a tree of nodes.
+         */
+         [CCode(cname = "cmark_parse_document")]
+         public static Node? parse_document([CCode (type="const char*", array_length_type = "size_t")] uint8[] text, OPT options);
+ 
+         /**
+         * Parse a CommonMark document in file.
+         *
+         * Returns a pointer to a tree of nodes.
+         */
+         [CCode(cname = "cmark_parse_file")]
+         public static Node? parse_file(GLib.FileStream f, OPT options);
+ 
+         /**
+         * Creates a new node of type ``type``.
+         *
+         * Note that the node may have other required properties, which it is the caller’s responsibility to assign.
+         */
+         [CCode(cname = "cmark_node_new")]
+         public Node(NODE_TYPE type);
+ 
+         /**
+         * Frees the memory allocated for a node and any children.
+         */
+         public void free();
+ 
+         // Tree Traversal
+         /**
+         * Returns the next node in the sequence after node, or NULL if there is none
+         */
+         public unowned Node? next();
+         /**
+         * Returns the previous node in the sequence after node, or NULL if there is none.
+         */
+         public unowned Node? previous();
+         /**
+         * Returns the parent of node, or NULL if there is none.
+         */
+         public unowned Node? parent();
+         /**
+         * Returns the first child of node, or NULL if node has no children.
+         */
+         public unowned Node? first_child();
+         /**
+         * Returns the last child of node, or NULL if node has no children.
+         */
+         public unowned Node? last_child();
+ 
+         // Accessors
+         /**
+         * Returns the user data of node.
+         */
+         [CCode (simple_generics = true)]
+         public T? get_user_data<T>();
+         /**
+         * Sets arbitrary user data for node.
+         *
+         * Returns 1 on success, 0 on failure.
+         */
+         [CCode (simple_generics = true)]
+         public int set_user_data<T>(T user_data);
+         /**
+         * Returns the type of node, or ``NODE_TYPE.NONE`` on error.
+         */
+         public NODE_TYPE get_type();
+         /**
+         * Like cmark_node_get_type, but returns a string representation of the type, or "<unknown>".
+         */
+         public unowned string get_type_string();
+         /**
+         * Returns the string contents of node, or an empty string if none is set.
+         *
+         * Returns NULL if called on a node that does not have string content.
+         */
+         public unowned string get_literal();
+         /**
+         * Sets the string contents of node.
+         *
+         * Returns 1 on success, 0 on failure.
+         */
+         public int set_literal(string content);
+         /**
+         * Returns the heading level of node, or 0 if node is not a heading.
+         */
+         public int get_heading_level();
+         /**
+         * Sets the heading level of node, returning 1 on success and 0 on error.
+         */
+         public int set_heading_level(int level);
+         /**
+         * Returns the list type of node, or ``LIST_TYPE.NO_LIST`` if node is not a list.
+         */
+         public LIST_TYPE get_list_type();
+         /**
+         * Sets the list type of node, returning 1 on success and 0 on error.
+         */
+         public int set_list_type(LIST_TYPE type);
+         /**
+         * Returns the list delimiter type of node, or ``DELIM_TYPE.NO_DELIM`` if node is not a list.
+         */
+         public DELIM_TYPE get_list_delim();
+         /**
+         * Sets the list delimiter type of node, returning 1 on success and 0 on error.
+         */
+         public int set_list_delim(DELIM_TYPE delim);
+         /**
+         * Returns starting number of node, if it is an ordered list, otherwise 0.
+         */
+         public int get_list_start();
+         /**
+         * Sets starting number of node, if it is an ordered list.
+         *
+         * Returns 1 on success, 0 on failure.
+         */
+         public int set_list_start(int start);
+         /**
+         * Returns 1 if node is a tight list, 0 otherwise.
+         */
+         public int get_list_tight();
+         /**
+         * Sets the "tightness" of a list.
+         *
+         * Returns 1 on success, 0 on failure.
+         */
+         public int set_list_tight(int tight);
+         /**
+         * Returns the info string from a fenced code block.
+         */
+         public unowned string get_fence_info();
+         /**
+         * Sets the info string in a fenced code block, returning 1 on success and 0 on failure.
+         */
+         public int set_fence_info(string info);
+         /**
+         * Returns the URL of a link or image node, or an empty string if no URL is set.
+         *
+         * Returns NULL if called on a node that is not a link or image.
+         */
+         public unowned string? get_url();
+         /**
+         * Sets the URL of a link or image node. Returns 1 on success, 0 on failure.
+         */
+         public int set_url(string url);
+         /**
+         * Returns the title of a link or image node, or an empty string if no title is set.
+         *
+         * Returns NULL if called on a node that is not a link or image.
+         */
+         public unowned string? get_title();
+         /**
+         * Sets the title of a link or image node.
+         *
+         * Returns 1 on success, 0 on failure.
+         */
+         public int set_title(string title);
+         /**
+         * Returns the literal "on enter" text for a custom node, or an empty string if no on_enter is set.
+         *
+         * Returns NULL if called on a non−custom node.
+         */
+         public unowned string? get_on_enter();
+         /**
+         * Sets the literal text to render "on enter" for a custom node.
+         *
+         * Any children of the node will be rendered after this text.
+         * Returns 1 on success 0 on failure.
+         */
+         public int set_on_enter(string on_enter);
+         /**
+         * Returns the literal "on exit" text for a custom node, or an empty string if no on_exit is set.
+         *
+         * Returns NULL if called on a non−custom node.
+         */
+         public unowned string? get_on_exit();
+         /**
+         * Sets the literal text to render "on exit" for a custom node.
+         *
+         * Any children of the node will be rendered before this text.
+         * Returns 1 on success 0 on failure.
+         */
+         public int set_on_exit(string on_exit);
+         /**
+         * Returns the line on which node begins.
+         */
+         public int get_start_line();
+         /**
+         * Returns the column at which node begins.
+         */
+         public int get_start_column();
+         /**
+         * Returns the line on which node ends.
+         */
+         public int get_end_line();
+         /**
+         * Returns the column at which node ends.
+         */
+         public int get_end_column();
+ 
+         // Manupulate tree
+         /**
+         * Unlinks a node
+         *
+         * Unlinks a node, removing it from the tree, but not freeing its memory. (Use cmark_node_free for that.)
+         */
+         public void unlink();
+         /**
+         * Inserts sibling before node. Returns 1 on success, 0 on failure.
+         */
+         public int insert_before(Node sibling);
+         /**
+         * Inserts sibling after node. Returns 1 on success, 0 on failure.
+         */
+         public int insert_after(Node sibling);
+         /**
+         * Replaces oldnode with newnode and unlinks oldnode (but does not free its memory). Returns 1 on success, 0 on failure.
+         */
+         public int replace(Node newnode);
+         /**
+         * Adds child to the beginning of the children of node. Returns 1 on success, 0 on failure.
+         */
+         public int prepend_child(Node child);
+         /**
+         * Adds child to the end of the children of node. Returns 1 on success, 0 on failure.
+         */
+         public int append_child(Node child);
+         /**
+         * Consolidates adjacent text nodes.
+         */
+         [CCode(cname="cmark_consolidate_text_nodes")]
+         public void consolidate_text_nodes();
+ 
+         // Rendering
+         /**
+         * Render a node tree as XML.
+         *
+         * It is the caller’s responsibility to free the returned buffer.
+         */
+         [CCode (cname="cmark_render_xml")]
+         public unowned string render_xml(OPT options);
+         /**
+         * Render a node tree as an HTML fragment.
+         *
+         * It is up to the user to add an appropriate header and footer.
+         * It is the caller’s responsibility to free the returned buffer.
+         */
+         [CCode (cname="cmark_render_html")]
+         public unowned string render_html(OPT options);
+         /**
+         * Render a node tree as a groff man page, without the header.
+         *
+         * It is the caller’s responsibility to free the returned buffer.
+         */
+         [CCode (cname="cmark_render_man")]
+         public unowned string render_man(OPT options, int width);
+         /**
+         * Render a node tree as a commonmark document.
+         *
+         *  It is the caller’s responsibility to free the returned buffer.
+         */
+         [CCode (cname="cmark_render_commonmark")]
+         public unowned string render_commonmark(OPT options, int width);
+         /**
+         * Render a node tree as a LaTeX document.
+         *
+         * It is the caller’s responsibility to free the returned buffer.
+         */
+         [CCode (cname="cmark_render_latex")]
+         public unowned string render_latex(OPT options, int width);
+ 
+ 
+         // TODO : more methods
+     }
+ 
+     /**
+     * Iterator
+     *
+     * An iterator will walk through a tree of nodes, starting from a root node,
+     * returning one node at a time, together with information about whether the
+     * node is being entered or exited.
+     *
+     * The iterator will first descend to a child node, if there is one.
+     * When there is no child, the iterator will go to the next sibling.
+     * When there is no next sibling, the iterator will return to the parent
+     * (but with an event type of EVENT.EXIT).
+     * The iterator will return EVENT.DONE when it reaches the root node again.
+     * One natural application is an HTML renderer, where an ENTER event outputs
+     * an open tag and an EXIT event outputs a close tag.
+     * An iterator might also be used to transform an AST in some systematic way,
+     * for example, turning all level−3 headings into regular paragraphs.
+     *
+     * {{{
+     *   void
+     *   usage_example(Node root) {
+     *       Iter iter = new Iter(node);
+     *       EVENT evt;
+     *       while ((evt = iter.next()) != EVENT.DONE) {
+     *           unowned Node? cur = iter.get_node();
+     *           // Do something with ‘cur‘ and ‘evt‘
+     *       }
+     *   }
+     * }}}
+     *
+     * Iterators will never return EXIT events for leaf nodes, which are nodes of type:
+     *
+     *  * NODE_TYPE.HTML_BLOCK
+     *  * NODE_TYPE.THEMATIC_BREAK
+     *  * NODE_TYPE.CODE_BLOCK
+     *  * NODE_TYPE.TEXT
+     *  * NODE_TYPE.SOFTBREAK
+     *  * NODE_TYPE.LINEBREAK
+     *  * NODE_TYPE.CODE
+     *  * NODE_TYPE.HTML_INLINE
+     *
+     * Nodes must only be modified after an EXIT event, or an ENTER event for leaf nodes.
+     */
+     [Compact, CCode (cname = "struct cmark_iter", cprefix = "cmark_iter_", free_function="cmark_iter_free")]
+     public class Iter {
+         [CCode(cname = "cmark_iter_new")]
+         /**
+         * Creates a new iterator starting at root.
+         *
+         * The current node and event type are undefined until cmark_iter_next is called for the first time.
+         */
+         public Iter(Node? root);
+         /**
+         * Frees the memory allocated for an iterator.
+         */
+         public void free();
+ 
+         /**
+         * Advances to the next node.
+         *
+         * Returns the event type (EVENT.ENTER, EVENT.EXIT or EVENT.DONE).
+         */
+         public EVENT next();
+ 
+         /**
+         *.Returns the current node.
+         */
+         public unowned Node? get_node();
+         /**
+         * Returns the current event type.
+         */
+         public EVENT get_event_type();
+         /**
+         * Returns the root node.
+         */
+         public unowned Node? get_root();
+ 
+         /**
+         * Resets the iterator.
+         *
+         * Resets the iterator so that the current node is current and the event type is event_type.
+         * The new current node must be a descendant of the root node or the root node itself.
+         */
+         public void reset(Node current, EVENT event_type);
+     }
+ 
+ 
+ 
+     /**
+     * Parsing
+     *
+     * Simple interface:
+     *
+     * {{{
+     *   var node = Node.parse_document("Hello *world*".data, OPT.DEFAULT);
+     * }}}
+     *
+     * Streaming interface:
+     *
+     * {{{
+     *    var parser = new CMark.Parser(CMark.OPT.DEFAULT);
+     *    var fp = GLib.FileStream.open("myfile.md", "rb");
+     *    while ((bytes = fp.read(buf,1)) > 0) {
+     *        parser.feed(buf, bytes);
+     *    }
+     *    var node = parser.finish();
+     * }}}
+     */
+     [Compact, CCode (cname = "struct cmark_parser", cprefix = "cmark_parser_", free_function="cmark_parser_free")]
+     public class Parser {
+         [CCode(cname = "cmark_parser_new")]
+         /**
+         * Creates a new parser object.
+         */
+         public Parser(OPT options);
+ 
+         /**
+         * Frees memory allocated for a parser object.
+         */
+         void cmark_parser_free();
+ 
+         /**
+         * Feeds a string of length ``len`` to parser.
+         */
+         public void feed([CCode (type="const char*", array_length = false)] uint8[] buffer, size_t len);
+         /**
+         * Finish parsing and return a pointer to a tree of nodes.
+         */
+         public Node? finish();
+ 
+     }
+ }

+ 4 - 0
src/meson.build

@@ -0,0 +1,4 @@
+project('GtkCommonMark', 'vala', 'c')
+
+subdir('lib')
+subdir('viewer')

+ 50 - 0
src/viewer/Main.vala

@@ -0,0 +1,50 @@
+using Gtk;
+
+int main (string[] argv) {
+    // Create a new application
+    var app = new Gtk.Application ("com.example.GtkApplication", GLib.ApplicationFlags.FLAGS_NONE);
+    app.activate.connect (() => {
+        var window = new Gtk.ApplicationWindow (app);
+        window.width_request = 250;
+        window.height_request = 100;
+        window.title = "Not Loopy";
+
+        var text_view = new TextView();
+        text_view.set_wrap_mode (WrapMode.WORD_CHAR);
+        //  text_view.set_editable (false);
+
+        var buffer = text_view.get_buffer();
+        var tags = new GtkCommonMark.TagManager(buffer);
+
+        //  buffer.begin_irreversible_action();
+
+        //  TextIter iter;
+        //  buffer.get_iter_at_offset (out iter, 0);
+
+        //  buffer.insert_with_tags (ref iter, "Oh My!\n", -1, tags.heading_l1);
+        //  buffer.insert(ref iter, "Hello world ", -1);
+        //  buffer.insert_with_tags (ref iter, "this is my link", -1, tags.emphisis, tags.strong);
+        //  buffer.insert(ref iter, ", what do you think?\n", -1);
+        //  buffer.insert_with_tags (ref iter, "What a quote though\nI don't know what to think!", -1, tags.strong);
+
+        //  buffer.end_irreversible_action();
+
+        var reader = new GtkCommonMark.Reader(buffer, tags);
+        var file = GLib.FileStream.open ("/home/bbarrow/Documents/Recipes/canned_spaghetti_toast/recipe.md", "r");
+        var root = CMark.Node.parse_file (file, CMark.OPT.DEFAULT);
+        reader.append_node (root);
+
+        text_view.top_margin = 18;
+        text_view.bottom_margin = 18;
+        text_view.left_margin = 18;
+        text_view.right_margin = 18;
+
+        var srcolled_window = new Gtk.ScrolledWindow ();
+        srcolled_window.set_policy (PolicyType.NEVER, PolicyType.AUTOMATIC);
+        srcolled_window.set_child(text_view);
+        window.set_child (srcolled_window);
+
+        window.present ();
+    });
+    return app.run (argv);
+}

+ 14 - 0
src/viewer/meson.build

@@ -0,0 +1,14 @@
+
+sources = files('Main.vala')
+
+dependencies = [
+    dependency('glib-2.0'),
+    dependency('gobject-2.0'),
+    dependency('gio-2.0'),
+    dependency('gtk4'),
+    meson.get_compiler('vala').find_library('libcmark', dirs: vapi_dir),
+    meson.get_compiler('c').find_library('cmark'),
+    libgtkcmark_dep
+]
+
+executable('GtkCommonMarkViewer', sources, dependencies: dependencies)