From e11314b2e9fef34f569bb7b042b432aed661cdc0 Mon Sep 17 00:00:00 2001 From: Romain Forlot Date: Tue, 19 Sep 2017 16:43:41 +0200 Subject: Added documentation Change-Id: Iaa6bb0470652d3d0dc97c6320dbf210567ccec80 Signed-off-by: Romain Forlot --- docs/0-Doc-Revisions.md | 6 + docs/README.md | 22 ++ docs/SUMMARY.md | 9 + docs/_layouts/ebook/page.html | 36 ++ docs/_layouts/ebook/pdf_footer.html | 13 + docs/_layouts/ebook/pdf_header.html | 13 + docs/_layouts/ebook/summary.html | 58 ++++ docs/_layouts/layout.html | 28 ++ docs/cover.jpg | Bin 0 -> 254719 bytes docs/cover_small.jpg | Bin 0 -> 13255 bytes docs/part-1/0_Abstract.md | 9 + docs/part-1/1-Architecture.md | 44 +++ docs/part-1/2-Configuration.md | 75 ++++ docs/part-1/3-Plugins.md | 4 + docs/part-1/4-SignalComposerAPI.md | 33 ++ .../pictures/Global_Signaling_Architecture.png | Bin 0 -> 182698 bytes docs/part-1/pictures/iotbzh_logo_small.png | Bin 0 -> 6989 bytes docs/resources/cover.svg | 210 +++++++++++ docs/resources/ebook.css | 386 +++++++++++++++++++++ docs/resources/make_cover.sh | 27 ++ 20 files changed, 973 insertions(+) create mode 100644 docs/0-Doc-Revisions.md create mode 100644 docs/README.md create mode 100644 docs/SUMMARY.md create mode 100644 docs/_layouts/ebook/page.html create mode 100644 docs/_layouts/ebook/pdf_footer.html create mode 100644 docs/_layouts/ebook/pdf_header.html create mode 100644 docs/_layouts/ebook/summary.html create mode 100644 docs/_layouts/layout.html create mode 100644 docs/cover.jpg create mode 100644 docs/cover_small.jpg create mode 100644 docs/part-1/0_Abstract.md create mode 100644 docs/part-1/1-Architecture.md create mode 100644 docs/part-1/2-Configuration.md create mode 100644 docs/part-1/3-Plugins.md create mode 100644 docs/part-1/4-SignalComposerAPI.md create mode 100644 docs/part-1/pictures/Global_Signaling_Architecture.png create mode 100644 docs/part-1/pictures/iotbzh_logo_small.png create mode 100644 docs/resources/cover.svg create mode 100644 docs/resources/ebook.css create mode 100755 docs/resources/make_cover.sh (limited to 'docs') diff --git a/docs/0-Doc-Revisions.md b/docs/0-Doc-Revisions.md new file mode 100644 index 0000000..a2ae277 --- /dev/null +++ b/docs/0-Doc-Revisions.md @@ -0,0 +1,6 @@ +Document revisions +================== + +| Date | Version | Designation  | Author | +|-------------|---------|--------------------------------------|-------------------------| +| 19 Sep 2017 | 4.99-EERC1 | Initial release | R. Forlot [ Iot.bzh ] | diff --git a/docs/README.md b/docs/README.md new file mode 100644 index 0000000..337a14c --- /dev/null +++ b/docs/README.md @@ -0,0 +1,22 @@ +# Introduction + +This template is also a documentation that explains how to setup and write +your documentation with gitbook. + +
+
+
+
+
+ +| *Meta* | *Data* | +| -- | -- | +| **Title** | {{ config.title }} | +| **Author** | {{ config.author }} | +| **Description** | {{ config.description }} | +| **Keywords** | {{ config.keywords }} | +| **Language** | English | +| **Published** | Published {{ config.published }} as an electronic book | +| **Updated** | {{ gitbook.time }} | +| **Collection** | Open-source | +| **Website** | [{{ config.website }}]({{ config.website }}) | diff --git a/docs/SUMMARY.md b/docs/SUMMARY.md new file mode 100644 index 0000000..9d0f0a8 --- /dev/null +++ b/docs/SUMMARY.md @@ -0,0 +1,9 @@ +# Summary + +* [Document revisions](0-Doc-Revisions.md) + +* [Signal Composer](part-1/0_Abstract.md) + * [Architecture](part-1/1-Architecture.md) + * [Configuration](part-1/2-Configuration.md) + * [Plugin](part-1/3-Plugins.md) + * [Signal Composer API](part-1/4-SignalComposerAPI.md) diff --git a/docs/_layouts/ebook/page.html b/docs/_layouts/ebook/page.html new file mode 100644 index 0000000..bf325e9 --- /dev/null +++ b/docs/_layouts/ebook/page.html @@ -0,0 +1,36 @@ +{% extends "layout.html" %} + +{% block title %}{{ page.title }}{% endblock %} +{% block description %}{{ page.description }}{% endblock %} + +{% block style %} + {### Include theme css before plugins css ###} + {% if not fileExists(config.styles.print) %} + {% if options.format %} + + {% else %} + + {% endif %} + {% endif %} + + {{ super() }} + + {### Custom stylesheets for the book ###} + + {% for type, style in config.styles %} + {% if fileExists(style) and (type == "ebook" or type == "print" or type == options.format) %} + + {% endif %} + {% endfor %} +{% endblock %} + +{% block body %} +
+ {% block page %} +

{{ page.title }}

+
+ {{ page.content|safe }} +
+ {% endblock %} +
+{% endblock %} diff --git a/docs/_layouts/ebook/pdf_footer.html b/docs/_layouts/ebook/pdf_footer.html new file mode 100644 index 0000000..679e562 --- /dev/null +++ b/docs/_layouts/ebook/pdf_footer.html @@ -0,0 +1,13 @@ +{% extends "./page.html" %} +{% block body %} + + + + +{% endblock %} diff --git a/docs/_layouts/ebook/pdf_header.html b/docs/_layouts/ebook/pdf_header.html new file mode 100644 index 0000000..ef49641 --- /dev/null +++ b/docs/_layouts/ebook/pdf_header.html @@ -0,0 +1,13 @@ +{% extends "./page.html" %} +{% block body %} +
+ IoT.Bzh + {{ config.title }} +
+ + + + +{% endblock %} \ No newline at end of file diff --git a/docs/_layouts/ebook/summary.html b/docs/_layouts/ebook/summary.html new file mode 100644 index 0000000..be328a4 --- /dev/null +++ b/docs/_layouts/ebook/summary.html @@ -0,0 +1,58 @@ +{% extends "./page.html" %} + +{% block title %}{{ "SUMMARY"|t }}{% endblock %} + +{% macro articles(_articles) %} + {% for article in _articles %} +
  • + + {% if article.path or article.url %} + {% if article.path %} + {{ article.title }} + {% else %} + {{ article.title }} + {% endif %} + {% else %} + {{ article.title }} + {% endif %} + {% if 1 %} + {{ article.level }} + {% endif %} + + {% if article.articles.length > 0 %} +
      + {{ articles(article.articles) }} +
    + {% endif %} +
  • + {% endfor %} +{% endmacro %} + +{% block page %} +
    +

    {{ "SUMMARY"|t }}

    +
      + {% for part in summary.parts %} + {% if part.title %} +
    1. +

      {{ part.title }}

      +
    2. + {% endif %} + {{ articles(part.articles) }} + + {% if not loop.last %} +
    3. + {% endif %} + {% endfor %} + + {% if glossary.path %} +
    4. + + {{ "GLOSSARY"|t }} + +
    5. + {% endif %} +
    +
    +{% endblock %} + diff --git a/docs/_layouts/layout.html b/docs/_layouts/layout.html new file mode 100644 index 0000000..3d5aca6 --- /dev/null +++ b/docs/_layouts/layout.html @@ -0,0 +1,28 @@ + + + + + + {% block title %}{{ config.title|d("GitBook", true) }}{% endblock %} + + + + {% if config.author %}{% endif %} + {% if config.isbn %}{% endif %} + {% block style %} + {% for resource in plugins.resources.css %} + {% if resource.url %} + + {% else %} + + {% endif %} + {% endfor %} + {% endblock %} + + {% block head %}{% endblock %} + + + {% block body %}{% endblock %} + {% block javascript %}{% endblock %} + + diff --git a/docs/cover.jpg b/docs/cover.jpg new file mode 100644 index 0000000..668f253 Binary files /dev/null and b/docs/cover.jpg differ diff --git a/docs/cover_small.jpg b/docs/cover_small.jpg new file mode 100644 index 0000000..216b4bb Binary files /dev/null and b/docs/cover_small.jpg differ diff --git a/docs/part-1/0_Abstract.md b/docs/part-1/0_Abstract.md new file mode 100644 index 0000000..beb610a --- /dev/null +++ b/docs/part-1/0_Abstract.md @@ -0,0 +1,9 @@ +# Signal Composer binding: A high level signal handler + +## Abstract + +You will find in this documentation a presentation about Signal Composer +architecture. Signal Composer is an Application Framework binding meant to +facilitate handling signaling, by compose, divide and create new virtuals +signals from RAW signals coming from _low level_ binding that handle read/write, +encode/decode on different protocols that a car could use (CAN, LIN, Ethernet, GPS, i2c, ...). diff --git a/docs/part-1/1-Architecture.md b/docs/part-1/1-Architecture.md new file mode 100644 index 0000000..a907882 --- /dev/null +++ b/docs/part-1/1-Architecture.md @@ -0,0 +1,44 @@ +# Signal Composer + +## Architecture + +Here is a quick picture about the signaling architecture : + +![GlobalArchitecture] + +Key here are on both layer, **low** and **high**. + +- **Low level** are in charge of handle a data exchange protocol to decode/encode and retransmit + with an AGL compatible format, most convenient way using **Application Framework** event. These are divised in two parts, which are : + - Transport Layer plug-in that read/write 1 protocol. + - Decoding/Encoding part that expose signals and a way to access them. +- **High level signal composer** gathers multiple **low level** signaling sources and creates new + virtuals signals from the **raw** signals defined (eg. signal made from gps latitude and + longitude that calcul the heading of vehicle). It is modular and each signal source should be + handled by specific plugins which take care of get the underlying event from **low level** or + define signaling composition with simple or complex operation to output value from **raw** signals + +There are three main parts with **Signal Composer**: + +- Configuration files which could be splitted in differents files. That will define: + - metadata with name of **signal composer** api name + - additionnals configurations files + - plugins used if so, **low level** signals sources + - signals definitions +- Dedicated plugins +- Signal composer API + +## Terminology + +Here is a little terminology guide to set the vocabulary: + +- **sources** +- **signals** +- **api** +- **event** +- **callbacks** +- **action** +- **virtual signals** +- **raw signals** + +[GlobalArchitecture]: pictures/Global_Signaling_Architecture.png "Global architecture" diff --git a/docs/part-1/2-Configuration.md b/docs/part-1/2-Configuration.md new file mode 100644 index 0000000..a68fd62 --- /dev/null +++ b/docs/part-1/2-Configuration.md @@ -0,0 +1,75 @@ +# Configuration + +Configuration defines all in **Signal composer** each signals and sources has to be defined in it. +At start, configuration will be searched in default binding configuration directory which should be +_/var/lib/afm/applications/signal-composer/1.0/etc/_. Binding search for a file name as _init-daemon*.json_. Others files could be used to split sections and don't have 1 big fat definitions file. + +Saying that you have 4 sections to define: + +- **metadata**: Main parts and the only one that can't be in a separates configuration. This must appears in the main configuration file. +- **plugins** (optionnal): Declare plugins that will be used by *sources* and *signals* for the subscription and composition. +- **sources**: Declare **low level** signals sources (eg. low-can, gps, mraa, ...). +- **signals**: Declare signals, virtuals and raw. + +## Metadata + +Here we define some metadata about **signal composer** binding. Fields to configure +are : + +- **label**: self-explanatory +- **version** (optionnal): self-explanatory +- **api**: name that the binding will be initialized to and later be accessible by **Application Framework** +- **info** (optionnal): self-explanatory +- **require** (optionnal): list of required external apis. +- **files** (optionnal): list of additionnals files. **ONLY NAME** or part of it without extension. These + files are loaded to find sections: *plugins*, *sources* and *signals*. + +## Plugins + +This section is the only which is optionnal, it is needed if you develop specifics C/C++ plugins +to be used with signal-composer. LUA and API consumption does not need plugins. + +Default path to search for a plugin is in the binding library directory in a subdirectory _plugins_ +_/var/lib/afm/applications/signal-composer/1.0/lib/plugins_. Else you could use the environment variable _CONTROL_PLUGIN_PATH_ with a semicolon separated list of +directory. + +Fields are: + +- **label**: Define the plugin name. This is that label that will be used on **sources** and + **signals** callbacks. +- **version** (optionnal): self-explanatory +- **info** (optionnal): self-explanatory +- **basename**: shared library file name **without** the extension. + +## Sources + +A source is a **low level** API that will be consume by the **signal composer** +to be able to expose those signals with additionnals treatments, filtering, +thinning,... and create new virtuals signals composed with basic raw signals. + +A source is defined with following fields: + +- **api**: Name of the source API. +- **info** (optionnal): self-explanatory +- **init** (optionnal): an **action** to take to initialize a source. May you have to call a verb from that API, of create a files etc. +- **getSignals** (optionnal); an **action** to take to get signals from that + source. These callback will be used for each signals defined later in the **signals** section. Dedicated arguments for each signal could be defined in + **signals**. + +## Signals + +A signal definition could be either a **raw** one or a **virtual** one. A **virtual signal** is a set of existing **raw signals** associated to an **action** +on reception which will compute the value of the signal. + +- **id**: Unique identifier used inside **signal composer**, used to compose virtual signals. +- **event**: specify a **raw signal** coming from **low level** sources. Couldn't + be used with **depends** field, only one of them is possible. +- **depends**: specify others signals **id** that compose it (eg: heading is + composed with longitude+latitude signals.). Couldn't be used with **event** field + at same time. +- **unit** (optionnal): Unit used to exprime the signal +- **frequency** (optionnal): Frequency maximum at which the signal could be + requested or sent. This is a thinning made at **high level** so not best suited for performance. Used **low level** native filtering capabilities when possible. +- **getSignalsArgs**: a JSON object used at subscription time. Meant to enabled + filtering capabilities at subscription and to be able to customize in general a subcription request by signal if needed. +- **onReceived**: an **action** to take when this signal is received! diff --git a/docs/part-1/3-Plugins.md b/docs/part-1/3-Plugins.md new file mode 100644 index 0000000..1cfb580 --- /dev/null +++ b/docs/part-1/3-Plugins.md @@ -0,0 +1,4 @@ +# Plugins + +Plugins are C/C++ shared library that is loaded by the binding to execute some simple routine. Routine could be on reception of a new signal or at sources +initialization time or signal subscription with the respective JSON field **onReceived**, **init** and **getSignals**. diff --git a/docs/part-1/4-SignalComposerAPI.md b/docs/part-1/4-SignalComposerAPI.md new file mode 100644 index 0000000..6e53800 --- /dev/null +++ b/docs/part-1/4-SignalComposerAPI.md @@ -0,0 +1,33 @@ +# Signal Composer API + +## get + +You can get a signal value be requesting the API with the verb *get*: + +```json +signal-composer get {"signal": "vehicle_speed", "options": {"average": 10}} +signal-composer get {"signal": "vehicle_speed", "options": {"minimum": 10}} +signal-composer get {"signal": "vehicle_speed", "options": {"maximum": 10}} +signal-composer get {"signal": "vehicle_speed"} +``` + +You apply apply some simple mathematical function by default present in the binding +, by default **last** is used: + +- **average**: make an average on X latest seconds. +- **minimum**: return the minimum value found in the X latest seconds. +- **maximum**: return the maximum value found in the X latest seconds. +- **last**: return the latest value. + +## list + +Verb **list** will output the list of defined signals. + +## loadConf + +Verb **loadConf** let you add new files to be able to add new **sources** or +**signals**. + +```json +signal-composer loadConf {"filepath": "/path/to/your/json/file.json"} +``` diff --git a/docs/part-1/pictures/Global_Signaling_Architecture.png b/docs/part-1/pictures/Global_Signaling_Architecture.png new file mode 100644 index 0000000..7934d3f Binary files /dev/null and b/docs/part-1/pictures/Global_Signaling_Architecture.png differ diff --git a/docs/part-1/pictures/iotbzh_logo_small.png b/docs/part-1/pictures/iotbzh_logo_small.png new file mode 100644 index 0000000..6a98c60 Binary files /dev/null and b/docs/part-1/pictures/iotbzh_logo_small.png differ diff --git a/docs/resources/cover.svg b/docs/resources/cover.svg new file mode 100644 index 0000000..6726de7 --- /dev/null +++ b/docs/resources/cover.svg @@ -0,0 +1,210 @@ + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + {title} + + {subtitle} + + {version} {date} + diff --git a/docs/resources/ebook.css b/docs/resources/ebook.css new file mode 100644 index 0000000..39f126c --- /dev/null +++ b/docs/resources/ebook.css @@ -0,0 +1,386 @@ +/* IoT.Bzh theaming */ + +h1 { + color: #330066; + border-bottom: 2px solid #330066; +} + +h2 { + color: #330066; +} + +h3 { + color: #330066; +} + +h4 { + color: #330066; +} + + +/* GENERAL ELEMENTS */ + +/* clear both */ + +.clear { + clear: both; +} + +.section> :last-child { + margin-bottom: 0 !important; +} + +.section> :first-child { + margin-top: 0 !important; +} + + +/* SPECIAL ELEMENTS */ + + +/* page break always after element on pdf/print definition */ + +div.pagebreak { + page-break-after: always; +} + + +/* no page break inside element on pdf/print definition */ + +div.nopb { + page-break-inside: avoid !important; + margin: 4px 0 4px 0; +} + + +/* note blocks */ + +div.note { + background: #FCF8E3 none repeat scroll 0% 0%; + color: #8A6D3B; + padding: 15px; + margin-bottom: 10px; + border-bottom: 5px solid #DDD; + border-color: #FAEBCC; + page-break-inside: avoid; +} + +div.note p { + padding-bottom: 0; + margin-bottom: 0; +} + + +/* images, figures and captions */ + +p img { + /* center all images */ + display: block; + margin: 0 auto; + padding: 10px 0; +} + +figure { + margin: 1.0em 0px; + padding: 10px 0; + text-align: center; + page-break-inside: avoid; + display: block; +} + +figure img { + display: block; + margin: 0 auto; +} + +figcaption { + clear: left; + margin: 1.0em 0 0 0; + text-align: center; + font-style: italic; + line-height: 1.5em; + font-size: 80%; + color: #666; + display: block; +} + +.page .section p img { + margin-top: 10px; +} + + +/* ul, ol list margin fix */ + +.page .section ol, +.page .section ul { + margin-bottom: 10px; +} + + +/* blockquotes */ + +.page .section blockquote { + margin: 0 0 0 5%; + font-style: italic; +} + + +/* PAGE SPECIFIC */ + + +/* set summary page to right side of the paper */ + +.page .toc h1 { + page-break-before: right; +} + +.page .section.toc { + page-break-inside: always; +} + +/* table headers */ + +div#README\.md table { + margin-top: 30px; + font-size: 95%; +} + +div#README\.md table thead { + display: none; +} + + + +/* CITATION AND IMAGES */ + + +/* math image styles */ + +.page .section p img.svg, +.page .section p img.png { + margin-top: 0px; + margin-bottom: -2px; +} + +.page .section p img.math { + vertical-align: middle; + height: auto; + width: auto; + margin-top: -4px; + max-height: 15px; +} + +.page .section p img.math.line1 { + margin-top: -7px; + max-height: 19px; +} + +.page .section p img.math.line2 { + margin-top: -1px; + max-height: 30px; +} + + +/* credits page */ + +.page .section ul.pictures { + margin-left: -30px; +} + +.page .section ul.pictures li { + list-style: outside none none; +} + +.page .section ul.pictures li a { + float: left; +} + +.page .section ul.pictures li span { + display: block; + margin-left: 100px; +} + + + +/* sub and super script */ + +.page .section sub { + font-size: 80%; + margin-left: 1px; +} + + +/* citations and references */ + +.page .section sup { + margin-left: -1px; + margin-right: 2px; + font-size: 80%; +} + +.page .section sup:before { + content: " "; +} + +.page .section ul.citations, +.page .section ul.references { + margin-left: -30px; +} + + +.page .section ul.citations li:nth-child(1) { + margin-top: 20px; + padding-top: 20px; + border-top: 1px solid #BBB; +} + +.page .section ul.citations li, +.page .section ul.references li { + list-style: outside none none; +} + +.page .section ul.citations li { + font-size: 80%; +} + +.page .section ul.citations li>span:nth-child(1), +.page .section ul.references li>span:nth-child(1) { + display: block; + float: left; + text-align: left; + width: 70px +} + +.page .section ul.citations li>span:nth-child(1) { + width: 50px +} + +.page .section ul.references li div { + margin-left: 70px; +} + +.page .section ul.citations li div { + margin-left: 50px; +} + +.page .section a[href="#"], +.page .section a[href="#"]:link, +.page .section a[href="#"]:visited, +.page .section a[href="#"]:hover, +.page .section a[href="#"]:focus { + text-decoration: none; + color: inherit; + cursor: text; + font-style: italic; +} + + +/* self referential footnotes */ + +.page .section div[type="selfref"] a[href="#"], +.page .section div[type="selfref"] a[href="#"]:link, +.page .section div[type="selfref"] a[href="#"]:visited, +.page .section div[type="selfref"] a[href="#"]:hover, +.page .section div[type="selfref"] a[href="#"]:focus { + font-style: normal; +} + +.page .section div[type="selfref"] span:nth-child(1) { + display: none; +} + + +/* page break always after element on pdf/print definition */ + +div.page-break { + page-break-inside: always; +} + +div.page-break:before { + content: ' '; +} + + +/* no page break inside element on pdf/print definition */ + +div.nopb { + page-break-inside: avoid; +} + +/* justify text */ +p { + text-align: justify; +} + +/* page header and footer */ + +.pdf-footer, +.pdf-header { + margin-top: 20px; + color: #aaa; +} + +.pdf-header .header-left { + float: left; + margin-left: 2em; + margin-right: auto; +} + +.pdf-header .header-right { + display: table; + margin-left: auto; + margin-right: 2em; +} + +.pdf-footer .sub { + padding-top: 8px; + font-size: 70%; +} + +.pdf-header .sub { + padding-top: 2px; + font-size: 70%; +} + +.pdf-footer { + padding-top: 10px; + border-top: 1px solid #eee; +} + +.pdf-footer .footer-left { + float: left; + margin-left: 2em; + margin-right: auto; +} + +.pdf-footer .footer-center { + display: table; + margin-left: auto; + margin-right: auto; +} + +.pdf-footer .footer-right { + float: right; + margin-left: auto; + margin-right: 2em; +} + +.pdf-header { + padding-bottom: 10px; + border-bottom: 1px solid #eee; +} + +.pdf-header .header-pages-count { + float: right; + text-align: right; +} + +.pdf-header .header-pages-count a, +.pdf-header .header-pages-count a:visited, +.pdf-header .header-pages-count a:active, +.pdf-header .header-pages-count a:focus, +.pdf-header .header-pages-count a:link { + text-decoration: none; + color: #aaa; + cursor: text; +} diff --git a/docs/resources/make_cover.sh b/docs/resources/make_cover.sh new file mode 100755 index 0000000..1026ecb --- /dev/null +++ b/docs/resources/make_cover.sh @@ -0,0 +1,27 @@ +#!/bin/bash +DOCS_DIR=$(cd $(dirname $0)/.. && pwd) +BOOKFILE=$DOCS_DIR/../book.json + +TITLE=$(grep '"title":' $BOOKFILE | cut -d'"' -f 4) +SUBTITLE=$(grep '"subtitle":' $BOOKFILE | cut -d'"' -f 4) +VERSION="Version $(grep '"version":' $BOOKFILE | cut -d'"' -f 4)" +DATE=$(grep '"published":' $BOOKFILE | cut -d'"' -f 4) + +[ -z "$TITLE" ] && { echo "Error TITLE not set!" ; exit 1; } +[ -z "$VERSION" ] && { echo "Error VERSION not set!" ; exit 1; } +[ -z "$DATE" ] && { echo "Error DATE not set!" ; exit 1; } + + +cat $(dirname $0)/cover.svg | sed -e "s/{title}/$TITLE/g" \ + -e "s/font-size:87.5px/font-size:54px/g" \ + -e "s/{subtitle}/$SUBTITLE/g" \ + -e "s/font-size:62.5px/font-size:40px/g" \ + -e "s/{version}/$VERSION/g" \ + -e "s/{date}/$DATE/g" \ + > /tmp/cover.svg + +# use imagemagick convert tool (cover size must be 1800x2360) +convert -resize "1600x2160!" -border 100 -bordercolor white -background white \ + -flatten -quality 100 /tmp/cover.svg $DOCS_DIR/cover.jpg + +convert -resize "200x262!" $DOCS_DIR/cover.jpg $DOCS_DIR/cover_small.jpg -- cgit 1.2.3-korg