Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 22 additions & 0 deletions docs/docs/api/style.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,12 @@ typedef struct tile57_mariner {
double sounding_size_scale; /* extra size multiplier for SOUNDINGS, on top of
* size_scale (scales each digit + spacing together).
* 1.0 = none; 0 reads as 1.0. */
uint8_t soundings; /* spot soundings, independent of the display
* category. 0 = follow the category, 1 = always
* show, 2 = always hide. S-52 files SOUNDG under
* OTHER, so 0 means a host enables the whole OTHER
* category to get soundings, and takes the seabed
* and the cables with it. */
double device_scale; /* device px per reference px — the HiDPI density the
* SURFACE paths are drawn at (2.0 on a Retina backing
* store). Describes the DISPLAY where size_scale
Expand All @@ -52,6 +58,22 @@ typedef struct tile57_mariner {
* it (that density is already in the requested
* width/height). 1.0 = a 1x framebuffer; 0 reads as
* 1.0. */
bool chart_over_image; /* CHART OVER PICTURE: a raster chart is drawn
* beneath this one, so the DEPARE / DRGARE / UNSARE
* / LNDARE fills and the no-data background drop
* out and the picture shows through. Contours,
* symbols, lights, soundings, text and boundaries
* already drawn as lines or patterns stay. Engages
* the S-52 §10.3.4.2 DisplayPlane precedence, which
* the clause conditions on an image beneath the
* chart; radar_overlay satisfies the same gate. */
char preferred_language[4]; /* the mariner's label language, an ISO 639-2 code
* such as "zho", or "" for the portrayed name. A code
* the chart states draws that language's name; any
* other code still draws an S-57 national name
* (NOBJNM), which records no language. The bake
* stores each language beside the portrayed name, so
* this switches without a re-bake. */
} tile57_mariner;

void tile57_mariner_defaults(tile57_mariner *m); /* canonical defaults, date_view = "" */
Expand Down
18 changes: 18 additions & 0 deletions docs/docs/limitations.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,12 @@ result is **best effort**:
- **Missing S-101 content stays missing.** S-101 attributes and features with
no S-57 source are never invented; rules that test them take their fallback
branch.
- **A national name has no language.** S-57 stores one `NOBJNM` per feature and
records only the lexical level of the text (part 3 clause 2.4), which is a
character repertoire rather than a language. The adapter converts it to a
second `featureName` tagged ISO 639-2 `und`, undetermined, so the
mariner's language setting reaches it whatever code they ask for. S-101 permits several `featureName` entries with distinct
languages. An S-57 source yields at most one.
- **Unconvertible objects fall back or drop.** An S-57 object class with no
S-101 equivalent portrays as the S-52 question mark (QUESMRK1) — or, where
the S-65 guidance says the object is simply not carried into S-101 (e.g. an
Expand Down Expand Up @@ -95,6 +101,18 @@ result is **best effort**:

## Display / style gaps

- **The embedded font covers Latin, Greek and Cyrillic.** A label in another
script draws with the bundled Noto Sans, which has no glyphs for it, and comes
out as boxes. Point `TILE57_FONT_FALLBACK` at a TrueType file or collection
holding the script, and the pixel outputs draw each codepoint the bundled face
lacks from that one. The PDF and vector outputs embed a single face per label,
so a label mixing scripts still draws its fallback glyphs from the bundled
face there. A host that supplies its own face through the surface callbacks is
unaffected.
- **A chart states more than four languages.** The bake runs a portrayal pass
per language and stores a text property per language, so it keeps the first
four it finds and drops the rest.

- **Overscale hatch occlusion is tile-path only.** The S-52 §10.1.10 overscale
indication (`OVERSC01`, see [architecture](./architecture.md)) is gated
correctly everywhere, but only the generated MapLibre style sandwiches the
Expand Down
2 changes: 1 addition & 1 deletion docs/docs/rendering.md
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,7 @@ tile57 png chart.pmtiles --view -76.48,38.974,15.1 --size 1024x768 -o out.png

# Mariner settings
tile57 png ... --safety 5 --safety-depth 5 --feet --palette night \
--no-names --plain --simplified --dq --scale 1.5
--no-names --language zho --plain --simplified --dq --scale 1.5
```

### From C (and therefore Go, Python, C++, …)
Expand Down
3 changes: 2 additions & 1 deletion docs/docs/tile-schema.md
Original file line number Diff line number Diff line change
Expand Up @@ -146,11 +146,12 @@ Depth soundings, drawn as digit glyph strings (SNDFRM digit composition).
### text

Text labels (the name typically derives from `OBJNAM` via the `featureName`
attribute).
attribute; a feature named in other languages also gets a `text_<lang>` for each).

| Field | Type | Meaning |
| --- | --- | --- |
| `text` | string | The label text. |
| `text_<lang>` | string | The same label in one of the languages the chart states, keyed by ISO 639-2 code, one property per language. An S-57 national name (`NOBJNM`) is keyed `text_und`, because S-57 records no language for it. A client selects with the mariner's language without a re-bake. |
| `font_size_px` | number | Font size in pixels. |
| `color_token` | string | Text color name. |
| `halo_color_token` | string | Halo color name (`""` = no halo). |
Expand Down
9 changes: 9 additions & 0 deletions include/tile57.h
Original file line number Diff line number Diff line change
Expand Up @@ -720,6 +720,15 @@ typedef struct tile57_mariner {
* specified values and blending them produces colours the
* spec does not name.
* Appended for ABI-append-safety; a zeroed struct is off. */
char preferred_language[4]; /* The mariner's label language, an ISO 639-2
* code such as "zho", or "" for the portrayed name. A
* code the chart states draws that language's name. Any
* other code still draws an S-57 national name (NOBJNM),
* because S-57 records no language for it.
* The bake stores each language the chart states beside
* the portrayed name, so this switches without a
* re-bake. Appended for ABI-append-safety; a zeroed
* struct keeps the portrayed name. */
} tile57_mariner;

/* Fill *m with the canonical default mariner settings (so a host needn't
Expand Down
15 changes: 15 additions & 0 deletions src/capi.zig
Original file line number Diff line number Diff line change
Expand Up @@ -2219,6 +2219,14 @@ export fn tile57_assets_free(out: ?*CAssets) callconv(.c) void {

// ---- chart-style generation (mirrors tile57_mariner in tile57.h) -----------

/// The mariner language as the ABI's fixed field. A code longer than the field
/// holds is dropped, which reads as the portrayed name.
fn langCode(s: []const u8) [4]u8 {
var out = [_]u8{0} ** 4;
if (s.len < out.len) @memcpy(out[0..s.len], s);
return out;
}

const CMariner = extern struct {
scheme: c_int,
shallow_contour: f64,
Expand Down Expand Up @@ -2280,6 +2288,11 @@ const CMariner = extern struct {
// background so a raster chart beneath shows through, and engage the
// DisplayPlane precedence. See tile57.h. Appended for ABI-append-safety.
chart_over_image: bool,
// The mariner's label language, an ISO 639-2 code, or "" for the portrayed
// name. The bake stores each language the chart states beside that name, so
// this switches without a re-bake. Appended for ABI-append-safety; a zeroed
// struct keeps the portrayed name.
preferred_language: [4]u8,
};

/// The tri-state `soundings` field as the engine's optional bool.
Expand Down Expand Up @@ -2342,6 +2355,7 @@ fn marinerFromC(cm: *const CMariner) mariner.Settings {
.sounding_size_scale = if (cm.sounding_size_scale > 0) cm.sounding_size_scale else 1.0,
.device_scale = if (cm.device_scale > 0) cm.device_scale else 1.0,
.chart_over_image = cm.chart_over_image,
.preferred_language = std.mem.sliceTo(&cm.preferred_language, 0),
.viewing_groups_off = if (cm.viewing_groups_off != null and cm.viewing_groups_off_len > 0)
cm.viewing_groups_off[0..cm.viewing_groups_off_len]
else
Expand Down Expand Up @@ -2526,6 +2540,7 @@ export fn tile57_mariner_defaults(cm: ?*CMariner) callconv(.c) void {
.sounding_size_scale = d.sounding_size_scale,
.device_scale = d.device_scale,
.chart_over_image = d.chart_over_image,
.preferred_language = langCode(d.preferred_language),
};
}

Expand Down
22 changes: 21 additions & 1 deletion src/chart.zig
Original file line number Diff line number Diff line change
Expand Up @@ -230,6 +230,7 @@ const CellBackend = struct {
portrayal_plain: ?[]const ?[]const u8 = null, // PlainBoundaries variant (areas)
portrayal_simplified: ?[]const ?[]const u8 = null, // SimplifiedSymbols variant (points)
portrayal_lights: ?[]const ?[]const u8 = null, // FullLightLines variant (sectored lights)
portrayal_national: []const scene.LangStreams = &.{}, // one pass per national language
portray_arena: ?*std.heap.ArenaAllocator = null,
coverage: []const []const []const s57.LonLat = &.{}, // M_COVR (in portray_arena)
cscl: i32 = 0, // compilation scale (DSPM CSCL, 1:N)
Expand Down Expand Up @@ -267,6 +268,7 @@ fn cellRef(cb: *CellBackend) scene.CellRef {
.portrayal_plain = cb.portrayal_plain,
.portrayal_simplified = cb.portrayal_simplified,
.portrayal_lights = cb.portrayal_lights,
.portrayal_national = cb.portrayal_national,
.geo = cb.geo,
.geo_world = cb.geo_world,
.feat_bbox = cb.feat_bbox,
Expand All @@ -290,6 +292,7 @@ const LazyCell = struct {
portrayal_plain: ?[]const ?[]const u8 = null,
portrayal_simplified: ?[]const ?[]const u8 = null,
portrayal_lights: ?[]const ?[]const u8 = null,
portrayal_national: []const scene.LangStreams = &.{},
arena: ?*std.heap.ArenaAllocator = null,
tick: u64 = 0, // LRU: last tile that used this cell
// M_COVR(CATCOV=1) coverage polygons, assembled once from `cell` for best-band
Expand Down Expand Up @@ -587,6 +590,14 @@ fn parseAnyCell(base: []const u8, updates: []const []const u8) ?CellLoad {

/// Portray a cell three ways, using the native adapted set (S-101) when present,
/// else the S-57 adapter.
/// The portrayal's national passes as the scene's own type. The two modules
/// state the same pair, and scene has no dependency on portray.
fn nationalPasses(a: std.mem.Allocator, passes: []const portray.LangStreams) []const scene.LangStreams {
const out = a.alloc(scene.LangStreams, passes.len) catch return &.{};
for (passes, out) |p, *o| o.* = .{ .lang = p.lang, .streams = p.streams };
return out;
}

fn portrayVariantsAny(arena: std.mem.Allocator, cell: *const s57.Cell, adapted: ?[]const s101.adapter.Adapted, dir: []const u8) !portray.CellPortrayal {
if (adapted) |ad| return portray.portrayCellVariantsAdapted(arena, cell, ad, dir);
return portray.portrayCellVariants(arena, cell, dir);
Expand All @@ -610,6 +621,7 @@ fn lazyEnsureLoaded(ls: *LazySource, lc: *LazyCell) void {
lc.portrayal_plain = cp.plain;
lc.portrayal_simplified = cp.simplified;
lc.portrayal_lights = cp.lights;
lc.portrayal_national = nationalPasses(p.allocator(), cp.national);
lc.arena = p;
} else |_| {
p.deinit();
Expand Down Expand Up @@ -639,6 +651,7 @@ fn lazyUnload(lc: *LazyCell) void {
lc.portrayal_plain = null;
lc.portrayal_simplified = null;
lc.portrayal_lights = null;
lc.portrayal_national = &.{};
if (lc.arena) |p| {
p.deinit();
gpa.destroy(p);
Expand Down Expand Up @@ -780,6 +793,7 @@ fn buildCellBackend(base: []const u8, updates: []const []const u8, dir: []const
cb.portrayal_plain = cp.plain;
cb.portrayal_simplified = cp.simplified;
cb.portrayal_lights = cp.lights;
cb.portrayal_national = nationalPasses(pa.allocator(), cp.national);
} else |_| {}
// Assemble geometry + its projection + per-feature bboxes ONCE (the baker's
// per-cell caches) so live per-view rendering reuses them across the view's tiles
Expand Down Expand Up @@ -3104,6 +3118,7 @@ pub const Chart = struct {
.portrayal_plain = cb.portrayal_plain,
.portrayal_simplified = cb.portrayal_simplified,
.portrayal_lights = cb.portrayal_lights,
.portrayal_national = cb.portrayal_national,
}};
return scene.generateView(&ps, a, gpa, &one, lon, lat, zoom, self.pick_attrs) catch error.TileGen;
},
Expand Down Expand Up @@ -3366,6 +3381,7 @@ pub const Chart = struct {
.portrayal_plain = cb2.portrayal_plain,
.portrayal_simplified = cb2.portrayal_simplified,
.portrayal_lights = cb2.portrayal_lights,
.portrayal_national = cb2.portrayal_national,
.geo = cb2.geo,
.geo_world = cb2.geo_world,
.feat_bbox = cb2.feat_bbox,
Expand Down Expand Up @@ -3528,6 +3544,7 @@ pub const Chart = struct {
.portrayal_plain = cb2.portrayal_plain,
.portrayal_simplified = cb2.portrayal_simplified,
.portrayal_lights = cb2.portrayal_lights,
.portrayal_national = cb2.portrayal_national,
}};
scene.appendTile(surf, a, &one, z, qt.tx, qt.ty, self.pick_attrs) catch continue;
},
Expand Down Expand Up @@ -3581,6 +3598,7 @@ pub const Chart = struct {
.portrayal_plain = cb.portrayal_plain,
.portrayal_simplified = cb.portrayal_simplified,
.portrayal_lights = cb.portrayal_lights,
.portrayal_national = cb.portrayal_national,
}};
return scene.generateView(&as, a, gpa, &one, lon, lat, zoom, self.pick_attrs) catch error.TileGen;
},
Expand Down Expand Up @@ -4156,6 +4174,7 @@ const BakeWork = struct {
var portrayal_plain: ?[]const ?[]const u8 = null;
var portrayal_simplified: ?[]const ?[]const u8 = null;
var portrayal_lights: ?[]const ?[]const u8 = null;
var portrayal_national: []const scene.LangStreams = &.{};
var geo: ?scene.GeoParts = null;
var geo_world: ?scene.GeoWorld = null;
var feat_bbox: ?[]const ?[4]f64 = null;
Expand All @@ -4167,6 +4186,7 @@ const BakeWork = struct {
portrayal_plain = cp.plain;
portrayal_simplified = cp.simplified;
portrayal_lights = cp.lights;
portrayal_national = nationalPasses(p.allocator(), cp.national);
} else |_| {}
// Build the geometry cache for EVERY cell, unconditionally.
// `build_geo` (cacheGeoForBand) gated it to the finer bands, but coarse cells are
Expand Down Expand Up @@ -4205,7 +4225,7 @@ const BakeWork = struct {
// Sector-figure reach (exact, from the portrayal streams): buildTileMap
// addresses the neighbouring tiles the cell's light legs/arcs cross.
const lr = scene.collectLightReach(&cell, portrayal);
c.outs[i] = .{ .cell = cell, .portrayal = portrayal, .portrayal_plain = portrayal_plain, .portrayal_simplified = portrayal_simplified, .portrayal_lights = portrayal_lights, .geo = geo, .geo_world = geo_world, .feat_bbox = feat_bbox, .bounds = b, .cscl = cscl, .coverage = coverage, .scamins = scamins, .light_bbox = lr.bbox, .light_range_m = lr.range_m };
c.outs[i] = .{ .cell = cell, .portrayal = portrayal, .portrayal_plain = portrayal_plain, .portrayal_simplified = portrayal_simplified, .portrayal_lights = portrayal_lights, .portrayal_national = portrayal_national, .geo = geo, .geo_world = geo_world, .feat_bbox = feat_bbox, .bounds = b, .cscl = cscl, .coverage = coverage, .scamins = scamins, .light_bbox = lr.bbox, .light_range_m = lr.range_m };
c.arenas[i] = pa;
}
};
Expand Down
10 changes: 8 additions & 2 deletions src/portray/lua_shim.c
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,10 @@ typedef struct tg_portray_ctx {
double shallow_contour;
double deep_contour;
double safety_height;
/* ISO 639-2 code the rules match featureName.language against. The
* catalogue's GetFeatureName returns the entry whose language equals this,
* and falls back to the nameUsage 1 entry when none does. */
const char *preferred_language;
} tg_portray_ctx;

static const tg_portray_ctx tg_default_ctx = {
Expand All @@ -166,6 +170,8 @@ static void tg_set_ctx_globals(lua_State *L, const tg_portray_ctx *ctx) {
TG_SET_REAL("SHALLOW_CONTOUR", ctx->shallow_contour);
TG_SET_REAL("DEEP_CONTOUR", ctx->deep_contour);
TG_SET_REAL("SAFETY_HEIGHT", ctx->safety_height);
lua_pushstring(L, ctx->preferred_language ? ctx->preferred_language : "eng");
lua_setglobal(L, "PREFERRED_LANGUAGE");
#undef TG_SET_BOOL
#undef TG_SET_REAL
}
Expand Down Expand Up @@ -583,7 +589,7 @@ int tile57_diag_portray_demo(const char *dir) {
"cp('ShallowWaterDangers','boolean','false'); cp('SafetyContour','real','30')\n"
"cp('SafetyDepth','real','30'); cp('ShallowContour','real','2')\n"
"cp('DeepContour','real','30'); cp('SafetyHeight','real','0')\n"
"cp('PreferredLanguage','text','eng')\n"
"cp('PreferredLanguage','text', PREFERRED_LANGUAGE)\n"
"PortrayalInitializeContextParameters(cps)\n"
"local out={}\n"
"local ctx=portrayalContext.ContextParameters\n"
Expand Down Expand Up @@ -1107,7 +1113,7 @@ int tg_portray_run(const char *dir, size_t dir_len, const tg_portray_ctx *ctx) {
"cp('ShallowWaterDangers','boolean', b(SHALLOW_WATER_DANGERS)); cp('SafetyContour','real', SAFETY_CONTOUR)\n"
"cp('SafetyDepth','real', SAFETY_DEPTH); cp('ShallowContour','real', SHALLOW_CONTOUR)\n"
"cp('DeepContour','real', DEEP_CONTOUR); cp('SafetyHeight','real', SAFETY_HEIGHT)\n"
"cp('PreferredLanguage','text','eng')\n"
"cp('PreferredLanguage','text', PREFERRED_LANGUAGE)\n"
"PortrayalInitializeContextParameters(cps)\n"
// Drive portrayal through the reference S-100 Part 9a entry point exactly as
// the catalogue intends. HostPortrayalEmit is the framework's per-feature
Expand Down
Loading
Loading