@rhizomatics/signalk-einklabel-plugin 0.5.0 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +6 -0
- package/README.md +39 -2
- package/dist/render/assets.d.ts +15 -0
- package/dist/render/assets.js +33 -0
- package/dist/render/binding.d.ts +7 -3
- package/dist/render/binding.js +14 -9
- package/dist/render/fonts.d.ts +13 -5
- package/dist/render/fonts.js +18 -6
- package/dist/render/svgRenderer.d.ts +8 -0
- package/dist/render/svgRenderer.js +69 -1
- package/docs/assets/screenshots/example_tidal_clock.png +0 -0
- package/package.json +2 -1
- package/resources/svg/lunar_phases/first_quarter.svg +51 -0
- package/resources/svg/lunar_phases/full_moon.svg +46 -0
- package/resources/svg/lunar_phases/new_moon.svg +43 -0
- package/resources/svg/lunar_phases/third_quarter.svg +52 -0
- package/resources/svg/lunar_phases/waning_crescent.svg +51 -0
- package/resources/svg/lunar_phases/waning_gibbous.svg +51 -0
- package/resources/svg/lunar_phases/waxing_crescent.svg +51 -0
- package/resources/svg/lunar_phases/waxing_gibbous.svg +51 -0
- package/templates/tide.svg +82 -96
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,9 @@
|
|
|
1
|
+
# 0.6.0
|
|
2
|
+
- Added ability to select images for inclusion in SVG templated based on SignalK value
|
|
3
|
+
- New image selection used to add lunar phase to tide clock example
|
|
4
|
+
- Requires a source for `environment.moonPhase.name`, for example the `dervived-data` plugin
|
|
5
|
+
# 0.5.1
|
|
6
|
+
- Work around `resvg-wasm` font limitations by overriding generic font family with matching font name prior to rendering
|
|
1
7
|
# 0.5.0
|
|
2
8
|
## Font Handling
|
|
3
9
|
- Update set of built-in fonts for consistent Roboto monospace and serif
|
package/README.md
CHANGED
|
@@ -10,6 +10,28 @@ Since they are designed to be used in large quantity in small shops, they are ch
|
|
|
10
10
|
|
|
11
11
|
Unlike some eInk projects, this plugin doesn't require any physical modification to the labels, or loading any new firmware. It can send an image to a supported shelf label fresh out of the box.
|
|
12
12
|
|
|
13
|
+
## Pre-requisites
|
|
14
|
+
|
|
15
|
+
Most of this is about making SignalK work with Bluetooth Low Energy, which is good thing to have anyway, since vendors like Victron, Switchbot, Ruuvi and others have BLE enabled hardware that's useful to have on a boat. [Direct BLE support](https://github.com/SignalK/signalk-server/issues/2411) in SignalK is being planned in 2026.
|
|
16
|
+
|
|
17
|
+
1. A SignalK server, preferably running Linux (MacOS does weird things with bluetooth)
|
|
18
|
+
2. A Bluetooth adapter, that can handle BLE (Bluetooth Low Energy), which is Bluetooth v4.0 or higher
|
|
19
|
+
- Bluetooth adapters for Linux can be tricky, TP-Link UB400 and Asus USB-BT500 are two well-known and available ones
|
|
20
|
+
- Some Raspberry Pi models come with suitable Bluetooth it built-in
|
|
21
|
+
- Don't worry about the very latest Bluetooth versions, 4.0 is basic, 5.0 is nice
|
|
22
|
+
- Home Assistant is massively more popular than SignalK, and often also run on Raspberry Pi and similar, so good source of advice
|
|
23
|
+
3. `bluez` package installed in Linux
|
|
24
|
+
- No need to do this if you have a Raspberry Pi with recent Raspian version, since bluez comes built in.
|
|
25
|
+
- If you're not running a Raspberry Pi, then ensure that the `dbus` package is installed
|
|
26
|
+
4. One or more supported Electronic Shelf Labels
|
|
27
|
+
- The label used for testing this is the [ZhunyCo 3.7 BRWY](https://www.aliexpress.com/item/1005010050104435.html)
|
|
28
|
+
5. Correct time zone set on server if local time is to be shown on display
|
|
29
|
+
- Use `raspi-config` on a Raspberry Pi, or `timedatectl` on a Linux server
|
|
30
|
+
- If not set, everything will work, but you may see the wrong zone or not have daylight savings applied
|
|
31
|
+
|
|
32
|
+
Once you have all of that, it may be worth also installing [signalk-victron-ble](https://github.com/stefanor/signalk-victron-ble) or [bt-sensors-plugin](https://github.com/naugehyde/bt-sensors-plugin-sk) to pull in data from other sensors and equipment.
|
|
33
|
+
|
|
34
|
+
|
|
13
35
|
## Installation
|
|
14
36
|
|
|
15
37
|
Look for **eInk Label Instrument** in the [SignalK AppStore]() on your
|
|
@@ -31,6 +53,9 @@ npm install @rhizomatics/signalk-einklabel-plugin
|
|
|
31
53
|
|
|
32
54
|
The tide clock needs the [signalk-tides](https://github.com/openwatersio/signalk-tides) plugin to be installed and publishing tides to the Resources API. The [tide.svg](https://github.com/rhizomatics/signalk-einklabel-plugin/blob/main/templates/tide.svg) can be customized to run with other APIs or take data only from SignalK data paths.
|
|
33
55
|
|
|
56
|
+
To show the lunar phase, the `environment.moonPhase.name` path is required, which can
|
|
57
|
+
be easily achieved by installing and configuring the `derived-data` plugin.
|
|
58
|
+
|
|
34
59
|
## Templating
|
|
35
60
|
|
|
36
61
|
Templates are simply SVG files, to which expressions can be added to use SignalK data, with options to make it easier to read, like rounding or simplifying dates and times. The template can have sample data in the placeholder, so is easy to layout and visualize.
|
|
@@ -79,9 +104,21 @@ Additionally, `round=n` can be used to round to limited decimal places.
|
|
|
79
104
|
|
|
80
105
|
These can all be combined as in `source=resources,resource=tides,path=extremes[2].level,category=depth,round=2`
|
|
81
106
|
|
|
107
|
+
### Non-Textual Fields (Images)
|
|
108
|
+
|
|
109
|
+
The same `<desc>` mechanism works on an `<image>` element instead of a `<text>` element, for a value that's better shown as a picture than as text - a moon phase icon, a wind direction arrow, a weather condition glyph, and so on. Rather than substituting text, the resolved value picks one of a directory of `.svg` files to embed, by an extra required `assets=` key naming that directory (resolved relative to the template file itself, so a bundled template and a user override both work the same way). For example, the tide clock's moon phase icon uses:
|
|
110
|
+
|
|
111
|
+
```
|
|
112
|
+
path=environment.moonPhase.name,assets=../resources/svg/lunar_phases
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
The resolved value (e.g. `"Waning Gibbous"`, as published by the [derived-data](https://www.npmjs.com/package/signalk-derived-data) plugin) is normalized to match a filename - lower-cased, punctuation and spaces collapsed to underscores - so `"Waning Gibbous"` picks `waning_gibbous.svg` out of that directory. If the underlying path has no value at all (e.g. the `derived-data` plugin isn't installed), or the value doesn't normalize to any file in the directory, the `<image>` element is simply omitted from that render - no broken image, no placeholder, nothing shown.
|
|
116
|
+
|
|
117
|
+
This is a general mechanism, not specific to moon phases - any `source`/`context`/`path`/`format` combination valid for a `<text>` binding works here too (a `source=resources` value, an explicit `category=`, etc.), the only difference is the required `assets=` directory and the "no match -> no image" behaviour instead of substituted text. To add your own, put a directory of `<value>.svg` files somewhere relative to your template, add an `<image>` element in your SVG editor at the size/position you want, and give it a `<desc>` the same way you would a text field.
|
|
118
|
+
|
|
82
119
|
### Fonts
|
|
83
120
|
|
|
84
|
-
Three font types are loaded by default, use the exact font name in the SVG editor and choose size and weight (bold, semi-bold etc). Some labels will make a decent attempt to gray scale. Use the simple pure red, yellow, white, black to match the label's limited colour choice (some labels only offer black and white, or black/white/red). If a font can't be matched it will default to (sans-serif) Roboto.
|
|
121
|
+
Three font types are loaded by default, use the generic font family, or exact font name, in the SVG editor and choose size and weight (bold, semi-bold etc). Some labels will make a decent attempt to gray scale. Use the simple pure red, yellow, white, black to match the label's limited colour choice (some labels only offer black and white, or black/white/red). If a font can't be matched it will default to (sans-serif) Roboto.
|
|
85
122
|
|
|
86
123
|
* `serif` - `Roboto Serif`
|
|
87
124
|
* `sans-serif` - `Roboto`
|
|
@@ -151,7 +188,7 @@ Placeholder text isn't necessary, and is ignored by the plugin, but makes it muc
|
|
|
151
188
|
|
|
152
189
|
Inkscape adds its own metadata to images, which can be stripped off by exporting a simple SVG, although can be left in place with no harm; main reason to simplify the SVG is manual changes in a text editor.
|
|
153
190
|
|
|
154
|
-
Due to a limitation in the `resvg-wasm` library used to turn SVGs into images, the font
|
|
191
|
+
Due to a limitation in the `resvg-wasm` library used to turn SVGs into images, the `font-family` is limited to `serif`,`sans-serif`,`monospace` or the exact name of one of the installed fonts - `Roboto` (sans serif), `Roboto Serif` or `Roboto Mono`. Inkscape has its own fonts, which won't match what's available in the SignalK plugin, so for more precise design, install [Roboto from Google](https://fonts.google.com/specimen/Roboto) via the web page, `brew` on MacOS or similar.
|
|
155
192
|
|
|
156
193
|
### Debugging Templates
|
|
157
194
|
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Turns a resolved binding value into a filename stem, e.g. `"Waning Gibbous"` -> `"waning_gibbous"` -
|
|
3
|
+
* so a `signalk`/`resources` value using SignalK's own display casing (title case, spaces) can match a
|
|
4
|
+
* file named the conventional snake_case way. Returns `undefined` for anything that can't sensibly name
|
|
5
|
+
* a file (non-string, empty, or all-punctuation), which callers treat as "no image".
|
|
6
|
+
*/
|
|
7
|
+
export declare function normalizeAssetKey(value: unknown): string | undefined;
|
|
8
|
+
/**
|
|
9
|
+
* Resolves an `assets=` binding to an actual `.svg` file, looked up as `<key>.svg` inside `assetsDir`
|
|
10
|
+
* resolved relative to the template's own directory (mirroring how a bundled template's relative asset
|
|
11
|
+
* references keep working whether the template itself is the bundled copy or a user override elsewhere) -
|
|
12
|
+
* `undefined` if no matching file exists, which callers treat as "no image" rather than an error, since an
|
|
13
|
+
* unmapped value (e.g. a phase name the asset set doesn't cover) is an expected, not exceptional, case.
|
|
14
|
+
*/
|
|
15
|
+
export declare function resolveAssetPath(templatePath: string, assetsDir: string, key: string): string | undefined;
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.normalizeAssetKey = normalizeAssetKey;
|
|
4
|
+
exports.resolveAssetPath = resolveAssetPath;
|
|
5
|
+
const fs_1 = require("fs");
|
|
6
|
+
const path_1 = require("path");
|
|
7
|
+
/**
|
|
8
|
+
* Turns a resolved binding value into a filename stem, e.g. `"Waning Gibbous"` -> `"waning_gibbous"` -
|
|
9
|
+
* so a `signalk`/`resources` value using SignalK's own display casing (title case, spaces) can match a
|
|
10
|
+
* file named the conventional snake_case way. Returns `undefined` for anything that can't sensibly name
|
|
11
|
+
* a file (non-string, empty, or all-punctuation), which callers treat as "no image".
|
|
12
|
+
*/
|
|
13
|
+
function normalizeAssetKey(value) {
|
|
14
|
+
if (typeof value !== 'string')
|
|
15
|
+
return undefined;
|
|
16
|
+
const normalized = value
|
|
17
|
+
.trim()
|
|
18
|
+
.toLowerCase()
|
|
19
|
+
.replace(/[^a-z0-9]+/g, '_')
|
|
20
|
+
.replace(/^_+|_+$/g, '');
|
|
21
|
+
return normalized || undefined;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Resolves an `assets=` binding to an actual `.svg` file, looked up as `<key>.svg` inside `assetsDir`
|
|
25
|
+
* resolved relative to the template's own directory (mirroring how a bundled template's relative asset
|
|
26
|
+
* references keep working whether the template itself is the bundled copy or a user override elsewhere) -
|
|
27
|
+
* `undefined` if no matching file exists, which callers treat as "no image" rather than an error, since an
|
|
28
|
+
* unmapped value (e.g. a phase name the asset set doesn't cover) is an expected, not exceptional, case.
|
|
29
|
+
*/
|
|
30
|
+
function resolveAssetPath(templatePath, assetsDir, key) {
|
|
31
|
+
const candidate = (0, path_1.join)((0, path_1.dirname)(templatePath), assetsDir, `${key}.svg`);
|
|
32
|
+
return (0, fs_1.existsSync)(candidate) ? candidate : undefined;
|
|
33
|
+
}
|
package/dist/render/binding.d.ts
CHANGED
|
@@ -17,6 +17,8 @@ export interface Binding {
|
|
|
17
17
|
/** Explicit unit-preferences category (e.g. `depth`, `speed`, `temperature`) for a numeric value with no path metadata of its own, e.g. a `source=resources` value - see `../unitCategories.ts`. */
|
|
18
18
|
category?: string;
|
|
19
19
|
round?: number;
|
|
20
|
+
/** Only meaningful on an `<image>` element's binding (see `SvgRenderer`) - a directory, resolved relative to the template file, of `<value>.svg` files to pick from by the resolved value (see `../assets.ts`'s `normalizeAssetKey`). */
|
|
21
|
+
assets?: string;
|
|
20
22
|
}
|
|
21
23
|
/**
|
|
22
24
|
* Parses a `<desc>` element's text content into a `Binding`, e.g.
|
|
@@ -29,9 +31,11 @@ export interface Binding {
|
|
|
29
31
|
*/
|
|
30
32
|
export declare function parseBinding(desc: string): Binding;
|
|
31
33
|
/**
|
|
32
|
-
* Parses every `<text>` element's `<desc>` binding out of raw SVG source - lets a caller
|
|
33
|
-
* data a template needs before fetching anything, with no separate config declaring it
|
|
34
|
-
* `assembleRawContext` in repaintScheduler.ts).
|
|
34
|
+
* Parses every `<text>` and `<image>` element's `<desc>` binding out of raw SVG source - lets a caller
|
|
35
|
+
* discover what data a template needs before fetching anything, with no separate config declaring it
|
|
36
|
+
* (see `assembleRawContext` in repaintScheduler.ts). `<image>` bindings (see `SvgRenderer`) resolve to a
|
|
37
|
+
* picked asset file rather than substituted text, but still need their underlying value fetched the same
|
|
38
|
+
* way as a `<text>` binding.
|
|
35
39
|
*/
|
|
36
40
|
export declare function findBindings(svgSource: string): Binding[];
|
|
37
41
|
/** Resolves a parsed `Binding` against the render context assembled by `assembleRawContext`. */
|
package/dist/render/binding.js
CHANGED
|
@@ -7,7 +7,7 @@ exports.renderBinding = renderBinding;
|
|
|
7
7
|
const xmldom_1 = require("@xmldom/xmldom");
|
|
8
8
|
const formatters_1 = require("./formatters");
|
|
9
9
|
const SOURCES = ['signalk', 'resources', 'einklabel'];
|
|
10
|
-
const KNOWN_KEYS = new Set(['source', 'context', 'resource', 'path', 'format', 'category', 'round']);
|
|
10
|
+
const KNOWN_KEYS = new Set(['source', 'context', 'resource', 'path', 'format', 'category', 'round', 'assets']);
|
|
11
11
|
/**
|
|
12
12
|
* Parses a `<desc>` element's text content into a `Binding`, e.g.
|
|
13
13
|
* `source=resources,resource=tides,path=extremes[0].level,category=depth,round=2` or, using the
|
|
@@ -56,21 +56,26 @@ function parseBinding(desc) {
|
|
|
56
56
|
format: fields.format,
|
|
57
57
|
category: fields.category,
|
|
58
58
|
round: fields.round !== undefined ? Number(fields.round) : undefined,
|
|
59
|
+
assets: fields.assets,
|
|
59
60
|
};
|
|
60
61
|
}
|
|
61
62
|
/**
|
|
62
|
-
* Parses every `<text>` element's `<desc>` binding out of raw SVG source - lets a caller
|
|
63
|
-
* data a template needs before fetching anything, with no separate config declaring it
|
|
64
|
-
* `assembleRawContext` in repaintScheduler.ts).
|
|
63
|
+
* Parses every `<text>` and `<image>` element's `<desc>` binding out of raw SVG source - lets a caller
|
|
64
|
+
* discover what data a template needs before fetching anything, with no separate config declaring it
|
|
65
|
+
* (see `assembleRawContext` in repaintScheduler.ts). `<image>` bindings (see `SvgRenderer`) resolve to a
|
|
66
|
+
* picked asset file rather than substituted text, but still need their underlying value fetched the same
|
|
67
|
+
* way as a `<text>` binding.
|
|
65
68
|
*/
|
|
66
69
|
function findBindings(svgSource) {
|
|
67
70
|
const doc = new xmldom_1.DOMParser().parseFromString(svgSource, 'image/svg+xml');
|
|
68
|
-
const elements = doc.getElementsByTagName('text');
|
|
69
71
|
const bindings = [];
|
|
70
|
-
for (
|
|
71
|
-
const
|
|
72
|
-
|
|
73
|
-
|
|
72
|
+
for (const tagName of ['text', 'image']) {
|
|
73
|
+
const elements = doc.getElementsByTagName(tagName);
|
|
74
|
+
for (let i = 0; i < elements.length; i++) {
|
|
75
|
+
const desc = elements.item(i)?.getElementsByTagName('desc').item(0);
|
|
76
|
+
if (desc?.textContent) {
|
|
77
|
+
bindings.push(parseBinding(desc.textContent));
|
|
78
|
+
}
|
|
74
79
|
}
|
|
75
80
|
}
|
|
76
81
|
return bindings;
|
package/dist/render/fonts.d.ts
CHANGED
|
@@ -6,11 +6,11 @@
|
|
|
6
6
|
* resvg-wasm does NOT route generic keywords (font-family:serif/sans-serif/monospace) to the
|
|
7
7
|
* "right" loaded font by metadata - a generic keyword, or any font-family value that doesn't
|
|
8
8
|
* exactly string-match a loaded font's own name, silently falls back to whichever loaded font
|
|
9
|
-
* happens to work for the requested glyphs (in practice: array order). Templates
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
9
|
+
* happens to work for the requested glyphs (in practice: array order). Templates may still use
|
|
10
|
+
* the generic keywords (Inkscape's default) - svgRenderer.ts rewrites them to the matching
|
|
11
|
+
* literal embedded name from GENERIC_FONT_FAMILY_MAP below before resvg ever sees the SVG - see
|
|
12
|
+
* project memory. Keep this to one font per bucket rather than adding e.g. bold variants - there's
|
|
13
|
+
* no reliable way to pick between two fonts sharing a literal name.
|
|
14
14
|
*
|
|
15
15
|
* Must be the static (non-variable) @fontsource builds, not @fontsource-variable - every
|
|
16
16
|
* @fontsource-variable/roboto-{mono,serif} subset renders zero visible glyphs under resvg-wasm
|
|
@@ -20,3 +20,11 @@
|
|
|
20
20
|
* Mono/Serif builds used here render correctly and keep the whole set in the Roboto super-family.
|
|
21
21
|
*/
|
|
22
22
|
export declare const DEFAULT_FONT_PATHS: string[];
|
|
23
|
+
/**
|
|
24
|
+
* Maps each CSS generic font-family keyword to the literal embedded name (nameID1) of the
|
|
25
|
+
* bundled font that backs it in DEFAULT_FONT_PATHS above. svgRenderer.ts rewrites every
|
|
26
|
+
* `font-family:sans-serif`/`serif`/`monospace` in a template to its literal name before handing
|
|
27
|
+
* the SVG to resvg-wasm, since resvg-wasm does not route generic keywords to a loaded font itself
|
|
28
|
+
* - see project memory. Keep these in sync with DEFAULT_FONT_PATHS if a bucket's font ever changes.
|
|
29
|
+
*/
|
|
30
|
+
export declare const GENERIC_FONT_FAMILY_MAP: Record<string, string>;
|
package/dist/render/fonts.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.DEFAULT_FONT_PATHS = void 0;
|
|
3
|
+
exports.GENERIC_FONT_FAMILY_MAP = exports.DEFAULT_FONT_PATHS = void 0;
|
|
4
4
|
/**
|
|
5
5
|
* One font per CSS generic family bucket (monospace/sans-serif/serif), bundled so rendering
|
|
6
6
|
* doesn't depend on the host having any fonts installed - resvg-wasm cannot see host fonts at
|
|
@@ -9,11 +9,11 @@ exports.DEFAULT_FONT_PATHS = void 0;
|
|
|
9
9
|
* resvg-wasm does NOT route generic keywords (font-family:serif/sans-serif/monospace) to the
|
|
10
10
|
* "right" loaded font by metadata - a generic keyword, or any font-family value that doesn't
|
|
11
11
|
* exactly string-match a loaded font's own name, silently falls back to whichever loaded font
|
|
12
|
-
* happens to work for the requested glyphs (in practice: array order). Templates
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
12
|
+
* happens to work for the requested glyphs (in practice: array order). Templates may still use
|
|
13
|
+
* the generic keywords (Inkscape's default) - svgRenderer.ts rewrites them to the matching
|
|
14
|
+
* literal embedded name from GENERIC_FONT_FAMILY_MAP below before resvg ever sees the SVG - see
|
|
15
|
+
* project memory. Keep this to one font per bucket rather than adding e.g. bold variants - there's
|
|
16
|
+
* no reliable way to pick between two fonts sharing a literal name.
|
|
17
17
|
*
|
|
18
18
|
* Must be the static (non-variable) @fontsource builds, not @fontsource-variable - every
|
|
19
19
|
* @fontsource-variable/roboto-{mono,serif} subset renders zero visible glyphs under resvg-wasm
|
|
@@ -28,3 +28,15 @@ exports.DEFAULT_FONT_PATHS = [
|
|
|
28
28
|
require.resolve('@fontsource/roboto-mono/files/roboto-mono-latin-400-normal.woff2'),
|
|
29
29
|
require.resolve('@fontsource/roboto-serif/files/roboto-serif-latin-400-normal.woff2'),
|
|
30
30
|
];
|
|
31
|
+
/**
|
|
32
|
+
* Maps each CSS generic font-family keyword to the literal embedded name (nameID1) of the
|
|
33
|
+
* bundled font that backs it in DEFAULT_FONT_PATHS above. svgRenderer.ts rewrites every
|
|
34
|
+
* `font-family:sans-serif`/`serif`/`monospace` in a template to its literal name before handing
|
|
35
|
+
* the SVG to resvg-wasm, since resvg-wasm does not route generic keywords to a loaded font itself
|
|
36
|
+
* - see project memory. Keep these in sync with DEFAULT_FONT_PATHS if a bucket's font ever changes.
|
|
37
|
+
*/
|
|
38
|
+
exports.GENERIC_FONT_FAMILY_MAP = {
|
|
39
|
+
'sans-serif': 'Roboto',
|
|
40
|
+
monospace: 'Roboto Mono',
|
|
41
|
+
serif: 'Roboto Serif 20pt',
|
|
42
|
+
};
|
|
@@ -16,6 +16,14 @@ import { Bitmap, Renderer, TemplateContext } from './types';
|
|
|
16
16
|
* `getElementsByTagName` is a live NodeList, so that previously truncated the
|
|
17
17
|
* whole tree and rendered blank.
|
|
18
18
|
*
|
|
19
|
+
* Non-textual inclusion: an `<image>` element with a `<desc>` child works the same way, except the
|
|
20
|
+
* resolved value picks one of a directory of `.svg` files (an `assets=` key, required, names that
|
|
21
|
+
* directory - see `./assets.ts`) rather than substituting text, and the element is dropped entirely
|
|
22
|
+
* (rather than showing an error placeholder) whenever the value is unavailable or doesn't map to a
|
|
23
|
+
* file - e.g. a device without the `derived-data` plugin installed simply shows no moon-phase icon.
|
|
24
|
+
* The matched file's contents are inlined as a `data:image/svg+xml;base64,...` URI so no filesystem
|
|
25
|
+
* access happens at resvg-wasm's render step.
|
|
26
|
+
*
|
|
19
27
|
* resvg-wasm cannot see the host's installed fonts (`loadSystemFonts`/`fontFiles`
|
|
20
28
|
* are silently no-ops under plain Node) - it only renders text if given font
|
|
21
29
|
* bytes directly via `fontBuffers`, read from disk by us. Without at least one
|
|
@@ -5,6 +5,7 @@ const promises_1 = require("fs/promises");
|
|
|
5
5
|
const xmldom_1 = require("@xmldom/xmldom");
|
|
6
6
|
const resvg_wasm_1 = require("@resvg/resvg-wasm");
|
|
7
7
|
const binding_1 = require("./binding");
|
|
8
|
+
const assets_1 = require("./assets");
|
|
8
9
|
const fonts_1 = require("./fonts");
|
|
9
10
|
let wasmReady;
|
|
10
11
|
function ensureWasmInitialized() {
|
|
@@ -13,6 +14,27 @@ function ensureWasmInitialized() {
|
|
|
13
14
|
}
|
|
14
15
|
return wasmReady;
|
|
15
16
|
}
|
|
17
|
+
// Only matches the CSS `font-family:` declaration inside a style="..." attribute, not the bare
|
|
18
|
+
// XML `font-family="..."` presentation attribute - rewriting the latter in place would require
|
|
19
|
+
// quote-aware handling of the surrounding XML attribute delimiter to avoid corrupting the
|
|
20
|
+
// document, and it's unnecessary: resvg-wasm (and any browser) prefers the style declaration
|
|
21
|
+
// whenever both are present, and every <text>/<tspan> in our templates sets one.
|
|
22
|
+
const GENERIC_FONT_FAMILY_PATTERN = /font-family\s*:\s*(['"]?)(sans-serif|serif|monospace)\1(?=\s*[;"])/g;
|
|
23
|
+
/**
|
|
24
|
+
* Rewrites CSS generic font-family keywords to the literal embedded name of the bundled font
|
|
25
|
+
* that backs each bucket (see GENERIC_FONT_FAMILY_MAP, ./fonts.ts), e.g. `font-family:sans-serif`
|
|
26
|
+
* becomes `font-family:'Roboto',sans-serif`. resvg-wasm only selects a font by exact name match -
|
|
27
|
+
* it does not route generic keywords to "the right" loaded font itself (see project memory) - so
|
|
28
|
+
* this lets templates use plain `serif`/`sans-serif`/`monospace` (as Inkscape writes by default)
|
|
29
|
+
* instead of every template author having to hardcode each bundled font's exact embedded name.
|
|
30
|
+
* The original generic keyword is kept as a trailing fallback so the rewritten value stays valid
|
|
31
|
+
* CSS for preview in e.g. Inkscape or a browser; it's inert as far as resvg-wasm is concerned.
|
|
32
|
+
*/
|
|
33
|
+
function expandGenericFontFamilies(svgSource) {
|
|
34
|
+
return svgSource.replace(GENERIC_FONT_FAMILY_PATTERN, (_match, _quote, generic) => {
|
|
35
|
+
return `font-family:'${fonts_1.GENERIC_FONT_FAMILY_MAP[generic]}',${generic}`;
|
|
36
|
+
});
|
|
37
|
+
}
|
|
16
38
|
/**
|
|
17
39
|
* Renders an SVG+binding template to a common RGBA bitmap.
|
|
18
40
|
*
|
|
@@ -30,6 +52,14 @@ function ensureWasmInitialized() {
|
|
|
30
52
|
* `getElementsByTagName` is a live NodeList, so that previously truncated the
|
|
31
53
|
* whole tree and rendered blank.
|
|
32
54
|
*
|
|
55
|
+
* Non-textual inclusion: an `<image>` element with a `<desc>` child works the same way, except the
|
|
56
|
+
* resolved value picks one of a directory of `.svg` files (an `assets=` key, required, names that
|
|
57
|
+
* directory - see `./assets.ts`) rather than substituting text, and the element is dropped entirely
|
|
58
|
+
* (rather than showing an error placeholder) whenever the value is unavailable or doesn't map to a
|
|
59
|
+
* file - e.g. a device without the `derived-data` plugin installed simply shows no moon-phase icon.
|
|
60
|
+
* The matched file's contents are inlined as a `data:image/svg+xml;base64,...` URI so no filesystem
|
|
61
|
+
* access happens at resvg-wasm's render step.
|
|
62
|
+
*
|
|
33
63
|
* resvg-wasm cannot see the host's installed fonts (`loadSystemFonts`/`fontFiles`
|
|
34
64
|
* are silently no-ops under plain Node) - it only renders text if given font
|
|
35
65
|
* bytes directly via `fontBuffers`, read from disk by us. Without at least one
|
|
@@ -52,7 +82,7 @@ class SvgRenderer {
|
|
|
52
82
|
}
|
|
53
83
|
async render(svgTemplatePath, context, width, height) {
|
|
54
84
|
const [, fontBuffers] = await Promise.all([ensureWasmInitialized(), this.loadFontBuffers()]);
|
|
55
|
-
const svgSource = await (0, promises_1.readFile)(svgTemplatePath, 'utf-8');
|
|
85
|
+
const svgSource = expandGenericFontFamilies(await (0, promises_1.readFile)(svgTemplatePath, 'utf-8'));
|
|
56
86
|
const doc = new xmldom_1.DOMParser().parseFromString(svgSource, 'image/svg+xml');
|
|
57
87
|
const elements = doc.getElementsByTagName('text');
|
|
58
88
|
for (let i = 0; i < elements.length; i++) {
|
|
@@ -73,6 +103,44 @@ class SvgRenderer {
|
|
|
73
103
|
element.textContent = 'ERROR';
|
|
74
104
|
}
|
|
75
105
|
}
|
|
106
|
+
// Collected up front, rather than iterated live like the `<text>` loop above - some of these get
|
|
107
|
+
// removed outright (see below), and removing from a live NodeList while iterating it by index would
|
|
108
|
+
// skip whatever shifts into the removed slot.
|
|
109
|
+
const imageElements = [];
|
|
110
|
+
const rawImageElements = doc.getElementsByTagName('image');
|
|
111
|
+
for (let i = 0; i < rawImageElements.length; i++) {
|
|
112
|
+
const element = rawImageElements.item(i);
|
|
113
|
+
if (element)
|
|
114
|
+
imageElements.push(element);
|
|
115
|
+
}
|
|
116
|
+
for (const element of imageElements) {
|
|
117
|
+
const descElement = element.getElementsByTagName('desc').item(0);
|
|
118
|
+
if (!descElement)
|
|
119
|
+
continue;
|
|
120
|
+
// Same one-field-at-a-time isolation as the `<text>` loop - and the same "no value" outcome
|
|
121
|
+
// (here, dropping the element rather than blanking to 'ERROR' text) for a value that isn't
|
|
122
|
+
// available at all as for one that doesn't map to any asset file, per the binding's contract.
|
|
123
|
+
try {
|
|
124
|
+
const binding = (0, binding_1.parseBinding)(descElement.textContent ?? '');
|
|
125
|
+
if (!binding.assets) {
|
|
126
|
+
throw new Error('an <image> binding requires an "assets" key naming the directory to pick a file from');
|
|
127
|
+
}
|
|
128
|
+
const key = (0, assets_1.normalizeAssetKey)((0, binding_1.resolveBinding)(binding, context));
|
|
129
|
+
const assetPath = key && (0, assets_1.resolveAssetPath)(svgTemplatePath, binding.assets, key);
|
|
130
|
+
if (!assetPath) {
|
|
131
|
+
element.parentNode?.removeChild(element);
|
|
132
|
+
continue;
|
|
133
|
+
}
|
|
134
|
+
const assetSource = await (0, promises_1.readFile)(assetPath, 'utf-8');
|
|
135
|
+
const dataUri = `data:image/svg+xml;base64,${Buffer.from(assetSource).toString('base64')}`;
|
|
136
|
+
const hrefAttr = element.getAttribute('href') !== null ? 'href' : 'xlink:href';
|
|
137
|
+
element.setAttribute(hrefAttr, dataUri);
|
|
138
|
+
}
|
|
139
|
+
catch (err) {
|
|
140
|
+
console.error(`image "${descElement.textContent}" failed to render: ${err.message}`);
|
|
141
|
+
element.parentNode?.removeChild(element);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
76
144
|
const svgOutput = new xmldom_1.XMLSerializer().serializeToString(doc);
|
|
77
145
|
const resvg = new resvg_wasm_1.Resvg(svgOutput, {
|
|
78
146
|
fitTo: { mode: 'width', value: width },
|
|
Binary file
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rhizomatics/signalk-einklabel-plugin",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.0",
|
|
4
4
|
"description": "SignalK plugin that renders selected SignalK data to eInk Electronic Shelf Labels",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
"files": [
|
|
11
11
|
"dist",
|
|
12
12
|
"templates",
|
|
13
|
+
"resources",
|
|
13
14
|
"docs",
|
|
14
15
|
"CHANGELOG.md",
|
|
15
16
|
"README.md"
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
|
2
|
+
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
|
3
|
+
|
|
4
|
+
<svg
|
|
5
|
+
version="1.0"
|
|
6
|
+
width="400"
|
|
7
|
+
height="400"
|
|
8
|
+
id="svg2"
|
|
9
|
+
sodipodi:docname="first_quarter.svg"
|
|
10
|
+
inkscape:version="1.3.2 (091e20e, 2023-11-25)"
|
|
11
|
+
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
|
12
|
+
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
|
13
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
14
|
+
xmlns:svg="http://www.w3.org/2000/svg">
|
|
15
|
+
<sodipodi:namedview
|
|
16
|
+
id="namedview1"
|
|
17
|
+
pagecolor="#ffffff"
|
|
18
|
+
bordercolor="#000000"
|
|
19
|
+
borderopacity="0.25"
|
|
20
|
+
inkscape:showpageshadow="2"
|
|
21
|
+
inkscape:pageopacity="0.0"
|
|
22
|
+
inkscape:pagecheckerboard="0"
|
|
23
|
+
inkscape:deskcolor="#d1d1d1"
|
|
24
|
+
inkscape:zoom="0.59"
|
|
25
|
+
inkscape:cx="200.84746"
|
|
26
|
+
inkscape:cy="200"
|
|
27
|
+
inkscape:window-width="1312"
|
|
28
|
+
inkscape:window-height="449"
|
|
29
|
+
inkscape:window-x="0"
|
|
30
|
+
inkscape:window-y="33"
|
|
31
|
+
inkscape:window-maximized="0"
|
|
32
|
+
inkscape:current-layer="svg2" />
|
|
33
|
+
<defs
|
|
34
|
+
id="defs4" />
|
|
35
|
+
<g
|
|
36
|
+
id="layer2">
|
|
37
|
+
<path
|
|
38
|
+
d="M 749.07161 200.5305 A 168.70027 168.70027 0 1 1 411.67107,200.5305 A 168.70027 168.70027 0 1 1 749.07161 200.5305 z"
|
|
39
|
+
transform="translate(-380.37134,-0.5305023)"
|
|
40
|
+
style="fill:#000000;fill-opacity:1;stroke:none;stroke-width:3.54330707;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
|
41
|
+
id="path6166" />
|
|
42
|
+
</g>
|
|
43
|
+
<g
|
|
44
|
+
id="layer3"
|
|
45
|
+
style="fill:#ffff00">
|
|
46
|
+
<path
|
|
47
|
+
d="M 199.86125,368.70045 C 292.83638,368.70045 368.20758,293.17071 368.20758,199.99995 C 368.20758,106.82922 292.83638,31.29945 199.86125,31.29945 C 199.12886,143.76656 199.72222,256.23321 199.86125,368.70045 z "
|
|
48
|
+
style="fill:#ffff00;fill-opacity:1;stroke:none"
|
|
49
|
+
id="path7307" />
|
|
50
|
+
</g>
|
|
51
|
+
</svg>
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
|
2
|
+
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
|
3
|
+
|
|
4
|
+
<svg
|
|
5
|
+
version="1.0"
|
|
6
|
+
width="400"
|
|
7
|
+
height="400"
|
|
8
|
+
id="svg2"
|
|
9
|
+
sodipodi:docname="full_moon.svg"
|
|
10
|
+
inkscape:version="1.3.2 (091e20e, 2023-11-25)"
|
|
11
|
+
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
|
12
|
+
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
|
13
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
14
|
+
xmlns:svg="http://www.w3.org/2000/svg">
|
|
15
|
+
<sodipodi:namedview
|
|
16
|
+
id="namedview1"
|
|
17
|
+
pagecolor="#ffffff"
|
|
18
|
+
bordercolor="#000000"
|
|
19
|
+
borderopacity="0.25"
|
|
20
|
+
inkscape:showpageshadow="2"
|
|
21
|
+
inkscape:pageopacity="0.0"
|
|
22
|
+
inkscape:pagecheckerboard="0"
|
|
23
|
+
inkscape:deskcolor="#d1d1d1"
|
|
24
|
+
inkscape:zoom="0.59"
|
|
25
|
+
inkscape:cx="200.84746"
|
|
26
|
+
inkscape:cy="200"
|
|
27
|
+
inkscape:window-width="1312"
|
|
28
|
+
inkscape:window-height="449"
|
|
29
|
+
inkscape:window-x="0"
|
|
30
|
+
inkscape:window-y="33"
|
|
31
|
+
inkscape:window-maximized="0"
|
|
32
|
+
inkscape:current-layer="svg2" />
|
|
33
|
+
<defs
|
|
34
|
+
id="defs4" />
|
|
35
|
+
<g
|
|
36
|
+
id="layer2"
|
|
37
|
+
style="fill:#ffff00">
|
|
38
|
+
<path
|
|
39
|
+
d="M 749.07161 200.5305 A 168.70027 168.70027 0 1 1 411.67107,200.5305 A 168.70027 168.70027 0 1 1 749.07161 200.5305 z"
|
|
40
|
+
transform="translate(-380.37134,-0.5305023)"
|
|
41
|
+
style="fill:#ffff00;fill-opacity:1;stroke:none;stroke-width:3.54330707;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
|
42
|
+
id="path6166" />
|
|
43
|
+
</g>
|
|
44
|
+
<g
|
|
45
|
+
id="layer3" />
|
|
46
|
+
</svg>
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
|
2
|
+
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
|
3
|
+
|
|
4
|
+
<svg
|
|
5
|
+
version="1.0"
|
|
6
|
+
width="400"
|
|
7
|
+
height="400"
|
|
8
|
+
id="svg2"
|
|
9
|
+
sodipodi:docname="new_moon.svg"
|
|
10
|
+
inkscape:version="1.3.2 (091e20e, 2023-11-25)"
|
|
11
|
+
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
|
12
|
+
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
|
13
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
14
|
+
xmlns:svg="http://www.w3.org/2000/svg">
|
|
15
|
+
<sodipodi:namedview
|
|
16
|
+
id="namedview1"
|
|
17
|
+
pagecolor="#ffffff"
|
|
18
|
+
bordercolor="#000000"
|
|
19
|
+
borderopacity="0.25"
|
|
20
|
+
inkscape:showpageshadow="2"
|
|
21
|
+
inkscape:pageopacity="0.0"
|
|
22
|
+
inkscape:pagecheckerboard="0"
|
|
23
|
+
inkscape:deskcolor="#d1d1d1"
|
|
24
|
+
inkscape:zoom="0.59"
|
|
25
|
+
inkscape:cx="200.84746"
|
|
26
|
+
inkscape:cy="200"
|
|
27
|
+
inkscape:window-width="1312"
|
|
28
|
+
inkscape:window-height="449"
|
|
29
|
+
inkscape:window-x="0"
|
|
30
|
+
inkscape:window-y="33"
|
|
31
|
+
inkscape:window-maximized="0"
|
|
32
|
+
inkscape:current-layer="svg2" />
|
|
33
|
+
<defs
|
|
34
|
+
id="defs4" />
|
|
35
|
+
<g
|
|
36
|
+
id="layer2">
|
|
37
|
+
<path
|
|
38
|
+
d="M 749.07161 200.5305 A 168.70027 168.70027 0 1 1 411.67107,200.5305 A 168.70027 168.70027 0 1 1 749.07161 200.5305 z"
|
|
39
|
+
transform="translate(-380.37134,-0.5305023)"
|
|
40
|
+
style="fill:#000000;fill-opacity:1;stroke:none;stroke-width:3.54330707;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
|
41
|
+
id="path6166" />
|
|
42
|
+
</g>
|
|
43
|
+
</svg>
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
|
2
|
+
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
|
3
|
+
|
|
4
|
+
<svg
|
|
5
|
+
version="1.0"
|
|
6
|
+
width="400"
|
|
7
|
+
height="400"
|
|
8
|
+
id="svg2"
|
|
9
|
+
sodipodi:docname="third_quarter.svg"
|
|
10
|
+
inkscape:version="1.3.2 (091e20e, 2023-11-25)"
|
|
11
|
+
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
|
12
|
+
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
|
13
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
14
|
+
xmlns:svg="http://www.w3.org/2000/svg">
|
|
15
|
+
<sodipodi:namedview
|
|
16
|
+
id="namedview1"
|
|
17
|
+
pagecolor="#ffffff"
|
|
18
|
+
bordercolor="#000000"
|
|
19
|
+
borderopacity="0.25"
|
|
20
|
+
inkscape:showpageshadow="2"
|
|
21
|
+
inkscape:pageopacity="0.0"
|
|
22
|
+
inkscape:pagecheckerboard="0"
|
|
23
|
+
inkscape:deskcolor="#d1d1d1"
|
|
24
|
+
inkscape:zoom="0.59"
|
|
25
|
+
inkscape:cx="200.84746"
|
|
26
|
+
inkscape:cy="200"
|
|
27
|
+
inkscape:window-width="1312"
|
|
28
|
+
inkscape:window-height="449"
|
|
29
|
+
inkscape:window-x="0"
|
|
30
|
+
inkscape:window-y="33"
|
|
31
|
+
inkscape:window-maximized="0"
|
|
32
|
+
inkscape:current-layer="svg2" />
|
|
33
|
+
<defs
|
|
34
|
+
id="defs4" />
|
|
35
|
+
<g
|
|
36
|
+
id="layer2">
|
|
37
|
+
<path
|
|
38
|
+
d="M 749.07161,200.5305 C 749.07161,293.70109 673.54193,369.23077 580.37134,369.23077 C 487.20075,369.23077 411.67107,293.70109 411.67107,200.5305 C 411.67107,107.35991 487.20075,31.83023 580.37134,31.83023 C 673.54193,31.83023 749.07161,107.35991 749.07161,200.5305 z "
|
|
39
|
+
transform="translate(-380.37134,-0.5305023)"
|
|
40
|
+
style="fill:#000000;fill-opacity:1;stroke:none;stroke-width:3.54330707;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
|
41
|
+
id="path6166" />
|
|
42
|
+
</g>
|
|
43
|
+
<g
|
|
44
|
+
transform="matrix(-1,0,0,1,399.50816,0)"
|
|
45
|
+
id="layer3"
|
|
46
|
+
style="fill:#ffff00">
|
|
47
|
+
<path
|
|
48
|
+
d="M 199.86125,368.70045 C 292.83638,368.70045 368.20758,293.17071 368.20758,199.99995 C 368.20758,106.82922 292.83638,31.29945 199.86125,31.29945 C 199.12886,143.76656 199.72222,256.23321 199.86125,368.70045 z "
|
|
49
|
+
style="fill:#ffff00;fill-opacity:1;stroke:none"
|
|
50
|
+
id="path7307" />
|
|
51
|
+
</g>
|
|
52
|
+
</svg>
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
|
2
|
+
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
|
3
|
+
|
|
4
|
+
<svg
|
|
5
|
+
version="1.0"
|
|
6
|
+
width="400"
|
|
7
|
+
height="400"
|
|
8
|
+
id="svg2"
|
|
9
|
+
sodipodi:docname="waning_crescent.svg"
|
|
10
|
+
inkscape:version="1.3.2 (091e20e, 2023-11-25)"
|
|
11
|
+
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
|
12
|
+
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
|
13
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
14
|
+
xmlns:svg="http://www.w3.org/2000/svg">
|
|
15
|
+
<sodipodi:namedview
|
|
16
|
+
id="namedview1"
|
|
17
|
+
pagecolor="#ffffff"
|
|
18
|
+
bordercolor="#000000"
|
|
19
|
+
borderopacity="0.25"
|
|
20
|
+
inkscape:showpageshadow="2"
|
|
21
|
+
inkscape:pageopacity="0.0"
|
|
22
|
+
inkscape:pagecheckerboard="0"
|
|
23
|
+
inkscape:deskcolor="#d1d1d1"
|
|
24
|
+
inkscape:zoom="0.59"
|
|
25
|
+
inkscape:cx="200.84746"
|
|
26
|
+
inkscape:cy="200"
|
|
27
|
+
inkscape:window-width="1312"
|
|
28
|
+
inkscape:window-height="449"
|
|
29
|
+
inkscape:window-x="0"
|
|
30
|
+
inkscape:window-y="33"
|
|
31
|
+
inkscape:window-maximized="0"
|
|
32
|
+
inkscape:current-layer="svg2" />
|
|
33
|
+
<defs
|
|
34
|
+
id="defs4" />
|
|
35
|
+
<g
|
|
36
|
+
id="layer2">
|
|
37
|
+
<path
|
|
38
|
+
d="M 749.07161,200.5305 C 749.07161,293.70109 673.54193,369.23077 580.37134,369.23077 C 487.20075,369.23077 411.67107,293.70109 411.67107,200.5305 C 411.67107,107.35991 487.20075,31.83023 580.37134,31.83023 C 673.54193,31.83023 749.07161,107.35991 749.07161,200.5305 z "
|
|
39
|
+
transform="translate(-380.37134,-0.5305023)"
|
|
40
|
+
style="fill:#00000f;fill-opacity:0.94117647;stroke:none;stroke-width:3.54330707;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
|
41
|
+
id="path6166" />
|
|
42
|
+
</g>
|
|
43
|
+
<g
|
|
44
|
+
id="layer3"
|
|
45
|
+
style="fill:#ffff00">
|
|
46
|
+
<path
|
|
47
|
+
d="M 199.97697,368.70045 C 106.80628,368.70045 31.27656,293.17073 31.27656,200 C 31.27656,106.8293 106.80628,31.299555 199.97697,31.299555 C 153.39106,31.299555 115.62676,106.8293 115.62676,200 C 115.62676,293.17073 153.39106,368.70045 199.97697,368.70045 z "
|
|
48
|
+
style="fill:#ffff00;fill-opacity:1;stroke:none"
|
|
49
|
+
id="path7307" />
|
|
50
|
+
</g>
|
|
51
|
+
</svg>
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
|
2
|
+
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
|
3
|
+
|
|
4
|
+
<svg
|
|
5
|
+
version="1.0"
|
|
6
|
+
width="400"
|
|
7
|
+
height="400"
|
|
8
|
+
id="svg2"
|
|
9
|
+
sodipodi:docname="waning_gibbous.svg"
|
|
10
|
+
inkscape:version="1.3.2 (091e20e, 2023-11-25)"
|
|
11
|
+
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
|
12
|
+
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
|
13
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
14
|
+
xmlns:svg="http://www.w3.org/2000/svg">
|
|
15
|
+
<sodipodi:namedview
|
|
16
|
+
id="namedview1"
|
|
17
|
+
pagecolor="#ffffff"
|
|
18
|
+
bordercolor="#000000"
|
|
19
|
+
borderopacity="0.25"
|
|
20
|
+
inkscape:showpageshadow="2"
|
|
21
|
+
inkscape:pageopacity="0.0"
|
|
22
|
+
inkscape:pagecheckerboard="0"
|
|
23
|
+
inkscape:deskcolor="#d1d1d1"
|
|
24
|
+
inkscape:zoom="0.59"
|
|
25
|
+
inkscape:cx="200.84746"
|
|
26
|
+
inkscape:cy="200"
|
|
27
|
+
inkscape:window-width="1312"
|
|
28
|
+
inkscape:window-height="449"
|
|
29
|
+
inkscape:window-x="0"
|
|
30
|
+
inkscape:window-y="33"
|
|
31
|
+
inkscape:window-maximized="0"
|
|
32
|
+
inkscape:current-layer="svg2" />
|
|
33
|
+
<defs
|
|
34
|
+
id="defs4" />
|
|
35
|
+
<g
|
|
36
|
+
id="layer2"
|
|
37
|
+
style="fill:#ffff00">
|
|
38
|
+
<path
|
|
39
|
+
d="M 749.07161 200.5305 A 168.70027 168.70027 0 1 1 411.67107,200.5305 A 168.70027 168.70027 0 1 1 749.07161 200.5305 z"
|
|
40
|
+
transform="translate(-380.37134,-0.5305023)"
|
|
41
|
+
style="fill:#ffff00;fill-opacity:1;stroke:none;stroke-width:3.54330707;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
|
42
|
+
id="path6166" />
|
|
43
|
+
</g>
|
|
44
|
+
<g
|
|
45
|
+
id="layer3">
|
|
46
|
+
<path
|
|
47
|
+
d="M 200.0278,368.70045 C 293.19849,368.70045 368.72822,293.17073 368.72822,200 C 368.72822,106.8293 293.19849,31.299555 200.0278,31.299555 C 246.61371,31.299555 284.37801,106.8293 284.37801,200 C 284.37801,293.17073 246.61371,368.70045 200.0278,368.70045 z "
|
|
48
|
+
style="fill:#00000f;fill-opacity:0.94117647;stroke:none"
|
|
49
|
+
id="path7307" />
|
|
50
|
+
</g>
|
|
51
|
+
</svg>
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
|
2
|
+
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
|
3
|
+
|
|
4
|
+
<svg
|
|
5
|
+
version="1.0"
|
|
6
|
+
width="400"
|
|
7
|
+
height="400"
|
|
8
|
+
id="svg2"
|
|
9
|
+
sodipodi:docname="waxing_crescent.svg"
|
|
10
|
+
inkscape:version="1.3.2 (091e20e, 2023-11-25)"
|
|
11
|
+
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
|
12
|
+
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
|
13
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
14
|
+
xmlns:svg="http://www.w3.org/2000/svg">
|
|
15
|
+
<sodipodi:namedview
|
|
16
|
+
id="namedview1"
|
|
17
|
+
pagecolor="#ffffff"
|
|
18
|
+
bordercolor="#000000"
|
|
19
|
+
borderopacity="0.25"
|
|
20
|
+
inkscape:showpageshadow="2"
|
|
21
|
+
inkscape:pageopacity="0.0"
|
|
22
|
+
inkscape:pagecheckerboard="0"
|
|
23
|
+
inkscape:deskcolor="#d1d1d1"
|
|
24
|
+
inkscape:zoom="0.59"
|
|
25
|
+
inkscape:cx="200.84746"
|
|
26
|
+
inkscape:cy="200"
|
|
27
|
+
inkscape:window-width="1312"
|
|
28
|
+
inkscape:window-height="449"
|
|
29
|
+
inkscape:window-x="0"
|
|
30
|
+
inkscape:window-y="33"
|
|
31
|
+
inkscape:window-maximized="0"
|
|
32
|
+
inkscape:current-layer="svg2" />
|
|
33
|
+
<defs
|
|
34
|
+
id="defs4" />
|
|
35
|
+
<g
|
|
36
|
+
id="layer2">
|
|
37
|
+
<path
|
|
38
|
+
d="M 749.07161 200.5305 A 168.70027 168.70027 0 1 1 411.67107,200.5305 A 168.70027 168.70027 0 1 1 749.07161 200.5305 z"
|
|
39
|
+
transform="translate(-380.37134,-0.5305023)"
|
|
40
|
+
style="fill:#000000;fill-opacity:1;stroke:none;stroke-width:3.54330707;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
|
41
|
+
id="path6166" />
|
|
42
|
+
</g>
|
|
43
|
+
<g
|
|
44
|
+
id="layer3"
|
|
45
|
+
style="fill:#ffff00">
|
|
46
|
+
<path
|
|
47
|
+
d="M 199.862,368.70045 C 293.03269,368.70045 368.56242,293.17073 368.56242,200 C 368.56242,106.8293 293.03269,31.299555 199.862,31.299555 C 246.44791,31.299555 284.21221,106.8293 284.21221,200 C 284.21221,293.17073 246.44791,368.70045 199.862,368.70045 z "
|
|
48
|
+
style="fill:#ffff00;fill-opacity:1;stroke:none"
|
|
49
|
+
id="path7307" />
|
|
50
|
+
</g>
|
|
51
|
+
</svg>
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
|
2
|
+
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
|
3
|
+
|
|
4
|
+
<svg
|
|
5
|
+
version="1.0"
|
|
6
|
+
width="400"
|
|
7
|
+
height="400"
|
|
8
|
+
id="svg2"
|
|
9
|
+
sodipodi:docname="waxing_gibbous.svg"
|
|
10
|
+
inkscape:version="1.3.2 (091e20e, 2023-11-25)"
|
|
11
|
+
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
|
12
|
+
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
|
13
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
14
|
+
xmlns:svg="http://www.w3.org/2000/svg">
|
|
15
|
+
<sodipodi:namedview
|
|
16
|
+
id="namedview1"
|
|
17
|
+
pagecolor="#ffffff"
|
|
18
|
+
bordercolor="#000000"
|
|
19
|
+
borderopacity="0.25"
|
|
20
|
+
inkscape:showpageshadow="2"
|
|
21
|
+
inkscape:pageopacity="0.0"
|
|
22
|
+
inkscape:pagecheckerboard="0"
|
|
23
|
+
inkscape:deskcolor="#d1d1d1"
|
|
24
|
+
inkscape:zoom="0.59"
|
|
25
|
+
inkscape:cx="200.84746"
|
|
26
|
+
inkscape:cy="200"
|
|
27
|
+
inkscape:window-width="1312"
|
|
28
|
+
inkscape:window-height="449"
|
|
29
|
+
inkscape:window-x="0"
|
|
30
|
+
inkscape:window-y="33"
|
|
31
|
+
inkscape:window-maximized="0"
|
|
32
|
+
inkscape:current-layer="svg2" />
|
|
33
|
+
<defs
|
|
34
|
+
id="defs4" />
|
|
35
|
+
<g
|
|
36
|
+
id="layer2"
|
|
37
|
+
style="fill:#ffff00">
|
|
38
|
+
<path
|
|
39
|
+
d="M 749.07161 200.5305 A 168.70027 168.70027 0 1 1 411.67107,200.5305 A 168.70027 168.70027 0 1 1 749.07161 200.5305 z"
|
|
40
|
+
transform="translate(-380.37134,-0.5305023)"
|
|
41
|
+
style="fill:#ffff00;fill-opacity:1;stroke:none;stroke-width:3.54330707;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
|
42
|
+
id="path6166" />
|
|
43
|
+
</g>
|
|
44
|
+
<g
|
|
45
|
+
id="layer3">
|
|
46
|
+
<path
|
|
47
|
+
d="M 199.97697,368.70045 C 106.80628,368.70045 31.27656,293.17073 31.27656,200 C 31.27656,106.8293 106.80628,31.299555 199.97697,31.299555 C 153.39106,31.299555 115.62676,106.8293 115.62676,200 C 115.62676,293.17073 153.39106,368.70045 199.97697,368.70045 z "
|
|
48
|
+
style="fill:#00000f;fill-opacity:0.94117647;stroke:none"
|
|
49
|
+
id="path7307" />
|
|
50
|
+
</g>
|
|
51
|
+
</svg>
|
package/templates/tide.svg
CHANGED
|
@@ -7,13 +7,13 @@
|
|
|
7
7
|
id="svg1"
|
|
8
8
|
sodipodi:docname="tide.svg"
|
|
9
9
|
inkscape:version="1.3.2 (091e20e, 2023-11-25)"
|
|
10
|
+
xml:space="preserve"
|
|
10
11
|
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
|
11
12
|
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
|
13
|
+
xmlns:xlink="http://www.w3.org/1999/xlink"
|
|
12
14
|
xmlns="http://www.w3.org/2000/svg"
|
|
13
|
-
xmlns:svg="http://www.w3.org/2000/svg"
|
|
14
|
-
|
|
15
|
-
id="defs1" />
|
|
16
|
-
<sodipodi:namedview
|
|
15
|
+
xmlns:svg="http://www.w3.org/2000/svg"><defs
|
|
16
|
+
id="defs1" /><sodipodi:namedview
|
|
17
17
|
id="namedview1"
|
|
18
18
|
pagecolor="#ffffff"
|
|
19
19
|
bordercolor="#000000"
|
|
@@ -23,16 +23,16 @@
|
|
|
23
23
|
inkscape:pagecheckerboard="0"
|
|
24
24
|
inkscape:deskcolor="#d1d1d1"
|
|
25
25
|
inkscape:zoom="1.3906433"
|
|
26
|
-
inkscape:cx="
|
|
27
|
-
inkscape:cy="
|
|
26
|
+
inkscape:cx="249.88435"
|
|
27
|
+
inkscape:cy="101.03238"
|
|
28
28
|
inkscape:window-width="1488"
|
|
29
29
|
inkscape:window-height="674"
|
|
30
30
|
inkscape:window-x="24"
|
|
31
31
|
inkscape:window-y="33"
|
|
32
32
|
inkscape:window-maximized="0"
|
|
33
33
|
inkscape:current-layer="svg1"
|
|
34
|
-
showgrid="true"
|
|
35
|
-
|
|
34
|
+
showgrid="true"
|
|
35
|
+
showguides="true"><inkscape:grid
|
|
36
36
|
id="grid1"
|
|
37
37
|
units="px"
|
|
38
38
|
originx="0"
|
|
@@ -47,240 +47,226 @@
|
|
|
47
47
|
dotted="false"
|
|
48
48
|
gridanglex="30"
|
|
49
49
|
gridanglez="30"
|
|
50
|
-
visible="true"
|
|
51
|
-
</sodipodi:namedview>
|
|
52
|
-
<rect
|
|
50
|
+
visible="true" /></sodipodi:namedview><rect
|
|
53
51
|
x="1.0169492"
|
|
54
52
|
y="2.0798673e-08"
|
|
55
53
|
width="416"
|
|
56
54
|
height="240"
|
|
57
55
|
fill="#ffffff"
|
|
58
|
-
id="rect1"
|
|
59
|
-
<rect
|
|
56
|
+
id="rect1" /><rect
|
|
60
57
|
style="fill:#ffff00;stroke:#000000;stroke-width:0.614045"
|
|
61
58
|
id="rect2"
|
|
62
59
|
width="416.94916"
|
|
63
60
|
height="18.410578"
|
|
64
61
|
x="-24.928873"
|
|
65
62
|
y="-242.40596"
|
|
66
|
-
transform="matrix(1,0,-0.1068988,-0.99426991,0,0)"
|
|
67
|
-
<text
|
|
63
|
+
transform="matrix(1,0,-0.1068988,-0.99426991,0,0)" /><text
|
|
68
64
|
id="station.name"
|
|
69
65
|
x="11.235578"
|
|
70
66
|
y="26.851856"
|
|
71
67
|
font-size="18"
|
|
72
68
|
font-family="serif"
|
|
73
69
|
fill="yellow"
|
|
74
|
-
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:32px;font-family:
|
|
70
|
+
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:32px;font-family:serif;-inkscape-font-specification:'Roboto Serif 20pt';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;fill:#ffff00;stroke:#000000;stroke-width:1.2278;stroke-opacity:1"
|
|
75
71
|
transform="scale(0.89274878,1.1201359)">Tidal Station Name<desc
|
|
76
|
-
id="desc1">source=resources,resource=tides,path=station.name</desc></text
|
|
77
|
-
<text
|
|
72
|
+
id="desc1">source=resources,resource=tides,path=station.name</desc></text><text
|
|
78
73
|
id="source"
|
|
79
74
|
x="347.95166"
|
|
80
75
|
y="226.1548"
|
|
81
76
|
font-size="18"
|
|
82
77
|
font-family="serif"
|
|
83
78
|
fill="black"
|
|
84
|
-
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:13.3333px;font-family:
|
|
79
|
+
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:13.3333px;font-family:sans-serif;-inkscape-font-specification:'Roboto',sans-serif;font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;fill:#ff0000;stroke-width:3.80045"
|
|
85
80
|
transform="scale(0.95907863,1.0426674)"><desc
|
|
86
81
|
id="desc21">source=resources,resource=tides,path=station.source.name</desc><tspan
|
|
87
82
|
sodipodi:role="line"
|
|
88
83
|
id="tspan20"
|
|
89
84
|
x="347.95166"
|
|
90
85
|
y="226.1548"
|
|
91
|
-
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:13.3332995px;font-family:
|
|
92
|
-
<text
|
|
86
|
+
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:13.3332995px;font-family:sans-serif;-inkscape-font-specification:'Roboto',sans-serif;font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;stroke-width:3.80045">SOURCE</tspan></text><text
|
|
93
87
|
id="last_repaint"
|
|
94
88
|
x="4.3477793"
|
|
95
89
|
y="201.9895"
|
|
96
90
|
font-size="18"
|
|
97
91
|
font-family="serif"
|
|
98
92
|
fill="black"
|
|
99
|
-
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:13.3333px;font-family:
|
|
93
|
+
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:13.3333px;font-family:sans-serif;-inkscape-font-specification:'Roboto',sans-serif;font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;fill:#000000;stroke-width:1.19309"
|
|
100
94
|
transform="scale(0.85412558,1.170788)"><desc
|
|
101
95
|
id="desc12">source=einklabel,path=repainted,format=local_datetime_short</desc><tspan
|
|
102
96
|
sodipodi:role="line"
|
|
103
|
-
id="tspan3">27 Jun 26 18:05</tspan></text
|
|
104
|
-
<text
|
|
97
|
+
id="tspan3">27 Jun 26 18:05</tspan></text><text
|
|
105
98
|
id="extremes.0"
|
|
106
99
|
x="8.6851931"
|
|
107
100
|
y="57.123634"
|
|
108
101
|
font-size="14"
|
|
109
102
|
font-family="monospace"
|
|
110
103
|
fill="black"
|
|
111
|
-
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:22.6667px;font-family:
|
|
112
|
-
id="desc13">source=resources,resource=tides,path=extremes[0].label</desc>High or Low</text
|
|
113
|
-
<text
|
|
104
|
+
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:22.6667px;font-family:sans-serif;-inkscape-font-specification:'Roboto, Normal',sans-serif;font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;fill:#ff0000"><desc
|
|
105
|
+
id="desc13">source=resources,resource=tides,path=extremes[0].label</desc>High or Low</text><text
|
|
114
106
|
id="extremes.1"
|
|
115
107
|
x="7.6000967"
|
|
116
108
|
y="117.85719"
|
|
117
109
|
font-size="14"
|
|
118
110
|
font-family="monospace"
|
|
119
111
|
fill="black"
|
|
120
|
-
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:22.6667px;font-family:
|
|
121
|
-
id="desc14">source=resources,resource=tides,path=extremes[1].label</desc>High or Low</text
|
|
122
|
-
<text
|
|
112
|
+
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:22.6667px;font-family:sans-serif;-inkscape-font-specification:'Roboto, Normal',sans-serif;font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;fill:#ff0000"><desc
|
|
113
|
+
id="desc14">source=resources,resource=tides,path=extremes[1].label</desc>High or Low</text><text
|
|
123
114
|
id="extremes.2"
|
|
124
115
|
x="7.2470098"
|
|
125
116
|
y="176.12112"
|
|
126
117
|
font-size="14"
|
|
127
118
|
font-family="monospace"
|
|
128
119
|
fill="red"
|
|
129
|
-
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:22.6667px;font-family:
|
|
130
|
-
id="desc15">source=resources,resource=tides,path=extremes[2].label</desc>High or Low</text
|
|
131
|
-
<text
|
|
120
|
+
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:22.6667px;font-family:sans-serif;-inkscape-font-specification:'Roboto, Normal',sans-serif;font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;fill:#ff0000"><desc
|
|
121
|
+
id="desc15">source=resources,resource=tides,path=extremes[2].label</desc>High or Low</text><text
|
|
132
122
|
id="environment.time.timezoneRegion"
|
|
133
|
-
x="
|
|
123
|
+
x="239.09819"
|
|
134
124
|
y="204.67754"
|
|
135
125
|
font-size="10.373px"
|
|
136
126
|
font-family="sans-serif"
|
|
137
127
|
fill="#ffff00"
|
|
138
|
-
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:13.3333px;font-family:
|
|
128
|
+
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:13.3333px;font-family:sans-serif;-inkscape-font-specification:'Roboto, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;direction:rtl;fill:#000000;stroke-width:0.864419"
|
|
139
129
|
transform="scale(0.86441854,1.156847)">UTC+01:00<desc
|
|
140
|
-
id="desc11">source=einklabel,path=local_zone</desc></text
|
|
141
|
-
<text
|
|
130
|
+
id="desc11">source=einklabel,path=local_zone</desc></text><text
|
|
142
131
|
id="lat"
|
|
143
|
-
x="
|
|
132
|
+
x="282.65424"
|
|
144
133
|
y="24.967674"
|
|
145
134
|
font-size="12px"
|
|
146
135
|
font-family="sans-serif"
|
|
147
136
|
fill="#ffff00"
|
|
148
|
-
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:21.3333px;font-family:
|
|
137
|
+
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:21.3333px;font-family:sans-serif;-inkscape-font-specification:'\'Roboto, Normal\', sans-serif';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;fill:#1a1a1a;stroke:#fefe54;stroke-opacity:1"><desc
|
|
149
138
|
id="desc18">source=resources,resource=tides,path=station.datums.LAT,category=depth,round=1</desc><tspan
|
|
150
139
|
sodipodi:role="line"
|
|
151
140
|
id="tspan17"
|
|
152
|
-
x="
|
|
153
|
-
y="24.967674">0.
|
|
154
|
-
<text
|
|
141
|
+
x="282.65424"
|
|
142
|
+
y="24.967674">0.0m</tspan></text><text
|
|
155
143
|
id="hat"
|
|
156
|
-
x="
|
|
157
|
-
y="
|
|
144
|
+
x="360.77148"
|
|
145
|
+
y="24.967674"
|
|
158
146
|
font-size="12px"
|
|
159
147
|
font-family="sans-serif"
|
|
160
148
|
fill="#ffff00"
|
|
161
|
-
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:21.3333px;font-family:
|
|
149
|
+
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:21.3333px;font-family:sans-serif;-inkscape-font-specification:'\'Roboto, Normal\', sans-serif';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;fill:#1a1a1a;stroke:#fefe54;stroke-opacity:1"><desc
|
|
162
150
|
id="desc19">source=resources,resource=tides,path=station.datums.HAT,category=depth,round=1</desc><tspan
|
|
163
151
|
sodipodi:role="line"
|
|
164
152
|
id="tspan18"
|
|
165
|
-
x="
|
|
166
|
-
y="
|
|
167
|
-
<text
|
|
153
|
+
x="360.77148"
|
|
154
|
+
y="24.967674">9.9m</tspan></text><text
|
|
168
155
|
id="extremes.2.level"
|
|
169
|
-
x="
|
|
170
|
-
y="
|
|
156
|
+
x="322.52386"
|
|
157
|
+
y="158.08005"
|
|
171
158
|
font-size="14"
|
|
172
159
|
font-family="monospace"
|
|
173
160
|
fill="red"
|
|
174
|
-
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:37.3333px;font-family:
|
|
161
|
+
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:37.3333px;font-family:monospace;-inkscape-font-specification:'\'Roboto Mono, Bold\', monospace';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;fill:#000000;stroke-width:1.78893"
|
|
175
162
|
transform="scale(0.7732368,1.2932649)"
|
|
176
163
|
inkscape:transform-center-x="11.578953"
|
|
177
164
|
inkscape:transform-center-y="-96.464172"><tspan
|
|
178
165
|
sodipodi:role="line"
|
|
179
166
|
id="tspan2-0"
|
|
180
|
-
x="
|
|
181
|
-
y="
|
|
182
|
-
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:37.3333px;font-family:
|
|
183
|
-
id="desc6">source=resources,resource=tides,path=extremes[2].level,category=depth</desc></text
|
|
184
|
-
<text
|
|
167
|
+
x="322.52386"
|
|
168
|
+
y="158.08005"
|
|
169
|
+
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:37.3333px;font-family:monospace;-inkscape-font-specification:'\'Roboto Mono, Bold\', monospace';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;stroke-width:1.78893">3.3m</tspan><desc
|
|
170
|
+
id="desc6">source=resources,resource=tides,path=extremes[2].level,category=depth</desc></text><text
|
|
185
171
|
id="extremes.2.time"
|
|
186
172
|
x="194.47879"
|
|
187
173
|
y="146.1584"
|
|
188
174
|
font-size="14"
|
|
189
175
|
font-family="monospace"
|
|
190
176
|
fill="red"
|
|
191
|
-
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:37.3333px;font-family:
|
|
177
|
+
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:37.3333px;font-family:monospace;-inkscape-font-specification:'Roboto Mono, Bold',monospace;font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;fill:#000000;stroke-width:1.51352"
|
|
192
178
|
transform="scale(0.70226239,1.4239692)">15:53<desc
|
|
193
|
-
id="desc10">source=resources,resource=tides,path=extremes[2].time,format=local_time</desc></text
|
|
194
|
-
<text
|
|
179
|
+
id="desc10">source=resources,resource=tides,path=extremes[2].time,format=local_time</desc></text><text
|
|
195
180
|
id="extremes.1.level"
|
|
196
|
-
x="
|
|
197
|
-
y="
|
|
181
|
+
x="328.79895"
|
|
182
|
+
y="109.17664"
|
|
198
183
|
font-size="14"
|
|
199
184
|
font-family="monospace"
|
|
200
185
|
fill="red"
|
|
201
|
-
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:37.3333px;font-family:
|
|
186
|
+
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:37.3333px;font-family:monospace;-inkscape-font-specification:'\'Roboto Mono, Bold\', monospace';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;fill:#000000;stroke-width:1.701"
|
|
202
187
|
transform="scale(0.75163533,1.3304324)"
|
|
203
188
|
inkscape:transform-center-y="-140.38618"
|
|
204
189
|
inkscape:transform-center-x="-0.00038058817"><tspan
|
|
205
190
|
sodipodi:role="line"
|
|
206
191
|
id="tspan2-1"
|
|
207
|
-
x="
|
|
208
|
-
y="
|
|
209
|
-
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:37.3333px;font-family:
|
|
210
|
-
id="desc5">source=resources,resource=tides,path=extremes[1].level,category=depth</desc></text
|
|
211
|
-
<text
|
|
192
|
+
x="328.79895"
|
|
193
|
+
y="109.17664"
|
|
194
|
+
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:37.3333px;font-family:monospace;-inkscape-font-specification:'\'Roboto Mono, Bold\', monospace';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;stroke-width:1.701">1.6m</tspan><desc
|
|
195
|
+
id="desc5">source=resources,resource=tides,path=extremes[1].level,category=depth</desc></text><text
|
|
212
196
|
id="extremes.1.time"
|
|
213
197
|
x="187.55594"
|
|
214
198
|
y="108.65867"
|
|
215
199
|
font-size="14"
|
|
216
200
|
font-family="monospace"
|
|
217
201
|
fill="red"
|
|
218
|
-
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:37.3333px;font-family:
|
|
202
|
+
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:37.3333px;font-family:monospace;-inkscape-font-specification:'Roboto Mono, Bold',monospace;font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;fill:#000000;stroke-width:1.48364"
|
|
219
203
|
transform="scale(0.7345432,1.3613903)">09:36<desc
|
|
220
|
-
id="desc8">source=resources,resource=tides,path=extremes[1].time,format=local_time</desc></text
|
|
221
|
-
<text
|
|
204
|
+
id="desc8">source=resources,resource=tides,path=extremes[1].time,format=local_time</desc></text><text
|
|
222
205
|
id="extremes.0.time"
|
|
223
206
|
x="184.40854"
|
|
224
207
|
y="64.242691"
|
|
225
208
|
font-size="14"
|
|
226
209
|
font-family="monospace"
|
|
227
210
|
fill="red"
|
|
228
|
-
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:37.3333px;font-family:
|
|
211
|
+
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:37.3333px;font-family:monospace;-inkscape-font-specification:'Roboto Mono, Bold',monospace;font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;fill:#000000;stroke-width:1.55397"
|
|
229
212
|
transform="scale(0.74654856,1.3394976)">03:27<desc
|
|
230
|
-
id="desc3">source=resources,resource=tides,path=extremes[0].time,format=local_time</desc></text
|
|
231
|
-
<text
|
|
213
|
+
id="desc3">source=resources,resource=tides,path=extremes[0].time,format=local_time</desc></text><text
|
|
232
214
|
id="extremes.0.time-8"
|
|
233
215
|
x="10.326324"
|
|
234
216
|
y="74.630478"
|
|
235
217
|
font-size="14"
|
|
236
218
|
font-family="monospace"
|
|
237
219
|
fill="red"
|
|
238
|
-
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:22.6667px;font-family:
|
|
220
|
+
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:22.6667px;font-family:sans-serif;-inkscape-font-specification:'Roboto, Normal',sans-serif;font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;fill:#666666;stroke-width:1.32646"
|
|
239
221
|
transform="scale(0.82428216,1.2131768)"><desc
|
|
240
|
-
id="desc2">source=resources,resource=tides,path=extremes[0].time,format=day_mon</desc>27 Jun</text
|
|
241
|
-
<text
|
|
222
|
+
id="desc2">source=resources,resource=tides,path=extremes[0].time,format=day_mon</desc>27 Jun</text><text
|
|
242
223
|
id="extremes.0.time-8-5"
|
|
243
224
|
x="9.9649849"
|
|
244
225
|
y="123.34451"
|
|
245
226
|
font-size="14"
|
|
246
227
|
font-family="monospace"
|
|
247
228
|
fill="red"
|
|
248
|
-
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:22.6667px;font-family:
|
|
229
|
+
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:22.6667px;font-family:sans-serif;-inkscape-font-specification:'Roboto, Normal',sans-serif;font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;fill:#666666;stroke-width:1.32646"
|
|
249
230
|
transform="scale(0.82428216,1.2131768)"><desc
|
|
250
|
-
id="desc7">source=resources,resource=tides,path=extremes[1].time,format=day_mon</desc>27 Jun</text
|
|
251
|
-
<text
|
|
231
|
+
id="desc7">source=resources,resource=tides,path=extremes[1].time,format=day_mon</desc>27 Jun</text><text
|
|
252
232
|
id="extremes.0.time-8-9"
|
|
253
233
|
x="12.934491"
|
|
254
234
|
y="156.77794"
|
|
255
235
|
font-size="14"
|
|
256
236
|
font-family="monospace"
|
|
257
237
|
fill="red"
|
|
258
|
-
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:22.6667px;font-family:
|
|
238
|
+
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:22.6667px;font-family:sans-serif;-inkscape-font-specification:'Roboto, Normal',sans-serif;font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;fill:#666666;stroke-width:1.4652"
|
|
259
239
|
transform="scale(0.74623156,1.3400666)"><desc
|
|
260
|
-
id="desc9">source=resources,resource=tides,path=extremes[2].time,format=day_mon</desc>27 Jun</text
|
|
261
|
-
<text
|
|
240
|
+
id="desc9">source=resources,resource=tides,path=extremes[2].time,format=day_mon</desc>27 Jun</text><text
|
|
262
241
|
id="extremes.0.level"
|
|
263
|
-
x="
|
|
264
|
-
y="
|
|
242
|
+
x="322.34219"
|
|
243
|
+
y="64.82357"
|
|
265
244
|
font-size="14"
|
|
266
245
|
font-family="monospace"
|
|
267
246
|
fill="red"
|
|
268
|
-
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:37.3333px;font-family:
|
|
247
|
+
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:37.3333px;font-family:monospace;-inkscape-font-specification:'\'Roboto Mono, Bold\', monospace';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;fill:#000000;stroke-width:1.71992"
|
|
269
248
|
transform="scale(0.7703174,1.2981662)"><tspan
|
|
270
249
|
sodipodi:role="line"
|
|
271
250
|
id="tspan2"
|
|
272
|
-
x="
|
|
273
|
-
y="
|
|
274
|
-
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:37.3333px;font-family:
|
|
275
|
-
id="desc4">source=resources,resource=tides,path=extremes[0].level,category=depth</desc></text
|
|
276
|
-
<text
|
|
251
|
+
x="322.34219"
|
|
252
|
+
y="64.82357"
|
|
253
|
+
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:37.3333px;font-family:monospace;-inkscape-font-specification:'\'Roboto Mono, Bold\', monospace';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;stroke-width:1.71992">3.4m</tspan><desc
|
|
254
|
+
id="desc4">source=resources,resource=tides,path=extremes[0].level,category=depth</desc></text><text
|
|
277
255
|
xml:space="preserve"
|
|
278
|
-
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:21.3333px;font-family:
|
|
279
|
-
x="
|
|
280
|
-
y="25.
|
|
256
|
+
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:21.3333px;font-family:sans-serif;-inkscape-font-specification:'\'Roboto, Normal\', sans-serif';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;fill:#1a1a1a;stroke:#fefe54;stroke-width:0.612283;stroke-opacity:1"
|
|
257
|
+
x="337.254"
|
|
258
|
+
y="25.327612"
|
|
281
259
|
id="text19"><tspan
|
|
282
260
|
sodipodi:role="line"
|
|
283
261
|
id="tspan19"
|
|
284
|
-
x="
|
|
285
|
-
y="25.
|
|
286
|
-
|
|
262
|
+
x="337.254"
|
|
263
|
+
y="25.327612">-></tspan></text><image
|
|
264
|
+
preserveAspectRatio="none"
|
|
265
|
+
inkscape:svg-dpi="96"
|
|
266
|
+
width="71.375114"
|
|
267
|
+
height="70.656021"
|
|
268
|
+
xlink:href="data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiIHN0YW5kYWxvbmU9Im5vIj8+Cjwh LS0gQ3JlYXRlZCB3aXRoIElua3NjYXBlIChodHRwOi8vd3d3Lmlua3NjYXBlLm9yZy8pIC0tPgo8 c3ZnCiAgIHhtbG5zOnN2Zz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciCiAgIHhtbG5zPSJo dHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIKICAgdmVyc2lvbj0iMS4wIgogICB3aWR0aD0iNDAw IgogICBoZWlnaHQ9IjQwMCIKICAgaWQ9InN2ZzIiPgogIDxkZWZzCiAgICAgaWQ9ImRlZnM0IiAv PgogIDxnCiAgICAgaWQ9ImxheWVyMSI+CiAgICA8cmVjdAogICAgICAgd2lkdGg9IjQwMCIKICAg ICAgIGhlaWdodD0iNDAwIgogICAgICAgeD0iMCIKICAgICAgIHk9Ii0xLjU0NTQ0MzVlLTAxNCIK ICAgICAgIHN0eWxlPSJmaWxsOiMwMDAwYmI7ZmlsbC1vcGFjaXR5OjE7c3Ryb2tlOm5vbmU7c3Ry b2tlLXdpZHRoOjMuNTQzMzA3MDc7c3Ryb2tlLW1pdGVybGltaXQ6NDtzdHJva2UtZGFzaGFycmF5 Om5vbmU7c3Ryb2tlLW9wYWNpdHk6MSIKICAgICAgIGlkPSJyZWN0MzIzOSIgLz4KICA8L2c+CiAg PGcKICAgICBpZD0ibGF5ZXIyIj4KICAgIDxwYXRoCiAgICAgICBkPSJNIDc0OS4wNzE2MSAyMDAu NTMwNSBBIDE2OC43MDAyNyAxNjguNzAwMjcgMCAxIDEgIDQxMS42NzEwNywyMDAuNTMwNSBBIDE2 OC43MDAyNyAxNjguNzAwMjcgMCAxIDEgIDc0OS4wNzE2MSAyMDAuNTMwNSB6IgogICAgICAgdHJh bnNmb3JtPSJ0cmFuc2xhdGUoLTM4MC4zNzEzNCwtMC41MzA1MDIzKSIKICAgICAgIHN0eWxlPSJm aWxsOiNmZmZmZmY7ZmlsbC1vcGFjaXR5OjE7c3Ryb2tlOm5vbmU7c3Ryb2tlLXdpZHRoOjMuNTQz MzA3MDc7c3Ryb2tlLW1pdGVybGltaXQ6NDtzdHJva2UtZGFzaGFycmF5Om5vbmU7c3Ryb2tlLW9w YWNpdHk6MSIKICAgICAgIGlkPSJwYXRoNjE2NiIgLz4KICA8L2c+CiAgPGcKICAgICBpZD0ibGF5 ZXIzIj4KICAgIDxwYXRoCiAgICAgICBkPSJNIDIwMC4wMjc4LDM2OC43MDA0NSBDIDI5My4xOTg0 OSwzNjguNzAwNDUgMzY4LjcyODIyLDI5My4xNzA3MyAzNjguNzI4MjIsMjAwIEMgMzY4LjcyODIy LDEwNi44MjkzIDI5My4xOTg0OSwzMS4yOTk1NTUgMjAwLjAyNzgsMzEuMjk5NTU1IEMgMjQ2LjYx MzcxLDMxLjI5OTU1NSAyODQuMzc4MDEsMTA2LjgyOTMgMjg0LjM3ODAxLDIwMCBDIDI4NC4zNzgw MSwyOTMuMTcwNzMgMjQ2LjYxMzcxLDM2OC43MDA0NSAyMDAuMDI3OCwzNjguNzAwNDUgeiAiCiAg ICAgICBzdHlsZT0iZmlsbDojMDAwMDBmO2ZpbGwtb3BhY2l0eTowLjk0MTE3NjQ3O3N0cm9rZTpu b25lIgogICAgICAgaWQ9InBhdGg3MzA3IiAvPgogIDwvZz4KPC9zdmc+Cg== "
|
|
269
|
+
id="moon_phase"
|
|
270
|
+
x="340.3974"
|
|
271
|
+
y="90.153488"><desc
|
|
272
|
+
id="desc22">path=environment.moonPhase.name,assets=../resources/svg/lunar_phases</desc></image></svg>
|