Viewing is open to everyone. Downloading and starring need a GitHub sign-in. Sign in with GitHub

api_render.inc

v0.1.0-build.85 Latest Commit ede38f5

Size
27 KB
Lines
351
Language
cpp
SHA-256
f24fb4b98851…
View on GitHub

Sign in with GitHub to download. Each download is recorded with your GitHub username.

1// Public C ABI: render functions. Included after core_internal.inc.
2
3extern "C" {
4
5ckey_status ckey_render_svg(const ckey_symbol *s, uint32_t scale, char *out,
6 size_t cap, size_t *required) {
7 if (!s || !required || scale == 0 || scale > CKEY_MAX_RENDER_SCALE || s->abi_version != CKEY_ABI_VERSION || s->side != kSide) return CKEY_INVALID_ARGUMENT;
8 const auto &nodes = data_nodes();
9 if (nodes.size() < kBits) return CKEY_INTERNAL_ERROR;
10 std::ostringstream o; o << std::fixed << std::setprecision(4);
11 std::ostringstream c; c << std::fixed << std::setprecision(4); // Cartesian payload layer
12 o << "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"" << 31 * scale << "\" height=\"" << 31 * scale << "\" viewBox=\"-1 -1 31 31\" role=\"img\" aria-label=\"CKey locator symbol\">"
13 << "<rect x=\"-1\" y=\"-1\" width=\"31\" height=\"31\" fill=\"#ffffff\"/><g fill=\"#000000\" stroke=\"#000000\">"
14 << "<g class=\"ckey-frame\"><rect x=\"0.5\" y=\"0.5\" width=\"28\" height=\"28\" rx=\"3.5\" fill=\"none\" stroke-width=\"0.65\"/></g>"
15 << "<g class=\"ckey-finders\">";
16 svg_corner_finders(o, 29.0, 4.0, {2.30, 0.0, 0.0}, .80, .95);
17 // Grid Key: fixed 2x2 Cartesian reference block at the left matrix edge.
18 o << "</g><g class=\"ckey-grid-key\">";
19 for (int r : {11, 12}) for (int c : {2, 3}) o << "<rect x=\"" << c + .13 << "\" y=\"" << r + .13 << "\" width=\"0.74\" height=\"0.74\" rx=\"0.16\" stroke=\"none\"/>";
20 o << "</g><g class=\"ckey-polar-keys\">";
21 // Permanent Ray Key is contained in the outer dial band (r=7.6..9.5).
22 for (const auto &[slot, fraction] : {std::pair{0, 1.0}, {54, .5}, {55, .75}}) {
23 const double a = kTau * slot / 72.0, r0 = 7.6, r1 = r0 + 1.9 * fraction;
24 o << "<path d=\"M " << kCenter + r0 * std::sin(a) << ' ' << kCenter - r0 * std::cos(a) << " L " << kCenter + r1 * std::sin(a) << ' ' << kCenter - r1 * std::cos(a)
25 << "\" fill=\"none\" stroke=\"#000000\" stroke-width=\"0.22\"/>";
26 }
27 // Permanent Ring Key on the outermost inner data ring.
28 const double rr = 6.0;
29 for (int slot : {18, 19, 21}) { const double a = kTau * slot / 72.0; svg_circle(o, kCenter + rr * std::sin(a), kCenter - rr * std::cos(a), polar_dot_radius()); }
30 const double ring_key_dot_diameter = 2.0 * polar_dot_radius();
31 // Round caps add one dot diameter to the visible length. Shorten the arc
32 // centerline accordingly so its total visible length remains four slots.
33 const double cap_angle = polar_dot_radius() / rr;
34 const double a0 = kTau * 21.5 / 72.0 + cap_angle;
35 const double a1 = kTau * 25.5 / 72.0 - cap_angle;
36 o << "<path d=\"M " << kCenter + rr * std::sin(a0) << ' ' << kCenter - rr * std::cos(a0) << " A " << rr << ' ' << rr << " 0 0 1 "
37 << kCenter + rr * std::sin(a1) << ' ' << kCenter - rr * std::cos(a1) << "\" fill=\"none\" stroke=\"#000000\" stroke-width=\"" << ring_key_dot_diameter << "\" stroke-linecap=\"round\"/>";
38 o << "</g><g class=\"ckey-data-polar\">";
39 for (size_t i = 0; i < kBits; ++i) {
40 const Node &n = nodes[i];
41 if (n.dial) {
42 const double a = kTau * n.slot / 72.0;
43 // Every data-bearing dial slot is marked. A short ray encodes 0;
44 // a long ray encodes 1. Both remain distinct from reserved keys.
45 const double length = code_bit(s, i) ? 1.35 : 0.65;
46 o << "<path d=\"M " << kCenter + 7.6 * std::sin(a) << ' ' << kCenter - 7.6 * std::cos(a) << " L " << kCenter + (7.6 + length) * std::sin(a) << ' ' << kCenter - (7.6 + length) * std::cos(a)
47 << "\" fill=\"none\" stroke=\"#000000\" stroke-width=\"0.15\" stroke-linecap=\"round\"/>";
48 continue;
49 }
50 if (!code_bit(s, i)) continue;
51 if (n.polar) svg_circle(o, n.x, n.y, polar_dot_radius());
52 else c << "<rect x=\"" << n.col + .13 << "\" y=\"" << n.row + .13 << "\" width=\"0.74\" height=\"0.74\" rx=\"0.16\" stroke=\"none\"/>";
53 }
54 o << "</g><g class=\"ckey-data-cartesian\">" << c.str() << "</g></g></svg>";
55 const std::string text = o.str(); *required = text.size() + 1;
56 if (!out || cap < *required) return CKEY_INSUFFICIENT_BUFFER;
57 std::memcpy(out, text.c_str(), *required);
58 return CKEY_OK;
59}
60
61ckey_status ckey_render_text_svg(const ckey_text_symbol *s, uint32_t scale, char *out,
62 size_t cap, size_t *required) {
63 if (!s || !required || scale == 0 || scale > CKEY_MAX_RENDER_SCALE ||
64 s->abi_version != CKEY_ABI_VERSION || s->side != kSide || s->payload_length > CKEY_TEXT_PAYLOAD_MAX_BYTES) return CKEY_INVALID_ARGUMENT;
65 const auto &nodes = text_nodes();
66 if (nodes.size() < kTextCodewordBytes * 8) return CKEY_INTERNAL_ERROR;
67 std::ostringstream o; o << std::fixed << std::setprecision(4);
68 std::ostringstream c; c << std::fixed << std::setprecision(4); // Cartesian payload layer
69 o << "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"" << 31 * scale << "\" height=\"" << 31 * scale << "\" viewBox=\"-1 -1 31 31\" role=\"img\" aria-label=\"CKey text symbol profile 2\">"
70 << "<rect x=\"-1\" y=\"-1\" width=\"31\" height=\"31\" fill=\"#ffffff\"/><g fill=\"#000000\" stroke=\"#000000\">"
71 << "<g class=\"ckey-frame\"><rect x=\"0.5\" y=\"0.5\" width=\"28\" height=\"28\" rx=\"3.5\" fill=\"none\" stroke-width=\"0.65\"/></g>"
72 << "<g class=\"ckey-finders\">";
73 svg_corner_finders(o, 29.0, 4.0, {2.30, 0.0, 0.0}, .80, .95);
74 o << "</g><g class=\"ckey-grid-key\">";
75 for (int r : {11,12}) for (int c : {2,3}) o << "<rect x=\"" << c+.13 << "\" y=\"" << r+.13 << "\" width=\"0.74\" height=\"0.74\" rx=\"0.16\" stroke=\"none\"/>";
76 o << "</g><g class=\"ckey-polar-keys\">";
77 for (const auto &[slot, fraction] : {std::pair{0,1.0}, std::pair{108,.5}, std::pair{110,.75}}) {
78 const double a=kTau*slot/144.0, r0=7.6, r1=r0+1.9*fraction;
79 o << "<path d=\"M " << kCenter+r0*std::sin(a) << ' ' << kCenter-r0*std::cos(a) << " L " << kCenter+r1*std::sin(a) << ' ' << kCenter-r1*std::cos(a) << "\" fill=\"none\" stroke-width=\"0.22\" stroke-linecap=\"round\"/>";
80 }
81 constexpr double key_radius=6.41, dot_radius=.10;
82 for (int slot : {36,38,42}) { const double a=kTau*slot/144.0; svg_circle(o,kCenter+key_radius*std::sin(a),kCenter-key_radius*std::cos(a),dot_radius); }
83 svg_ring_run(o,kCenter,key_radius,43.5,51.5,144.0,dot_radius,"0.2000");
84 o << "</g><g class=\"ckey-data-polar\">";
85 std::array<std::array<bool,kTextSlots>,kTextRings> ring_bits{};
86 for (size_t i=0;i<nodes.size();++i) {
87 const auto &n=nodes[i]; const bool bit=text_site_value(s,i);
88 if (n.cart) { if (bit) c << "<rect x=\"" << n.x-.17 << "\" y=\"" << n.y-.17 << "\" width=\"0.34\" height=\"0.34\" rx=\"0.08\" stroke=\"none\"/>"; }
89 else if (n.dial) { const double a=kTau*n.slot/144.0, length=bit?1.20:.52; o << "<path d=\"M " << kCenter+7.6*std::sin(a) << ' ' << kCenter-7.6*std::cos(a) << " L " << kCenter+(7.6+length)*std::sin(a) << ' ' << kCenter-(7.6+length)*std::cos(a) << "\" fill=\"none\" stroke-width=\"0.1000\" stroke-linecap=\"round\"/>"; }
90 else ring_bits[n.ring][n.slot]=bit;
91 }
92 for (int ring=0;ring<kTextRings;++ring) {
93 const double radius=3.4+.43*ring;
94 for (int slot=0;slot<kTextSlots;) {
95 if (!ring_bits[ring][slot]) { ++slot; continue; }
96 int end=slot+1; while (end<kTextSlots && ring_bits[ring][end]) ++end;
97 if (end-slot==1) { const double a=kTau*slot/kTextSlots; svg_circle(o,kCenter+radius*std::sin(a),kCenter-radius*std::cos(a),dot_radius); }
98 else svg_ring_run(o,kCenter,radius,slot-.5,end-.5,kTextSlots,dot_radius,"0.2000");
99 slot=end;
100 }
101 }
102 o << "</g><g class=\"ckey-data-cartesian\">" << c.str() << "</g></g></svg>";
103 const std::string text=o.str(); *required=text.size()+1;
104 if (!out || cap<*required) return CKEY_INSUFFICIENT_BUFFER;
105 std::memcpy(out,text.c_str(),*required); return CKEY_OK;
106}
107
108ckey_status ckey_render_extended_svg(const ckey_extended_symbol *s, uint32_t scale, char *out,
109 size_t cap, size_t *required) {
110 if (!s || !required || scale == 0 || scale > CKEY_MAX_RENDER_SCALE || s->abi_version != CKEY_ABI_VERSION || s->side != kExtendedSide || s->payload_length == 0 || s->payload_length > CKEY_EXTENDED_PAYLOAD_MAX_BYTES) return CKEY_INVALID_ARGUMENT;
111 const auto &nodes=extended_nodes(); if (nodes.size()<kExtendedBits) return CKEY_INTERNAL_ERROR;
112 constexpr double center=58.0, dot_radius=.075;
113 std::ostringstream o; o << std::fixed << std::setprecision(4);
114 std::ostringstream c; c << std::fixed << std::setprecision(4); // Cartesian payload layer
115 o << "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"" << 124*scale << "\" height=\"" << 124*scale << "\" viewBox=\"-4 -4 124 124\" role=\"img\" aria-label=\"CKey extended symbol profile 3\">"
116 << "<rect x=\"-4\" y=\"-4\" width=\"124\" height=\"124\" fill=\"#ffffff\"/><g fill=\"#000000\" stroke=\"#000000\">"
117 << "<g class=\"ckey-frame\"><rect x=\"2\" y=\"2\" width=\"112\" height=\"112\" rx=\"14\" fill=\"none\" stroke-width=\"2.6\"/></g>"
118 << "<g class=\"ckey-finders\">";
119 svg_corner_finders(o, 116.0, 16.0, {9.20, 0.0, 0.0}, 3.2, 3.8);
120 // Fixed Grid Key: four ordinary 0.34-unit Cartesian modules on the same
121 // 0.5-unit pitch as payload, so it calibrates payload pitch and contrast.
122 o << "</g><g class=\"ckey-grid-key\">";
123 for (int r=0;r<2;++r) for (int c=0;c<2;++c) o << "<rect x=\"" << 8.08+c*.5 << "\" y=\"" << 45.08+r*.5 << "\" width=\".34\" height=\".34\" rx=\".08\" stroke=\"none\"/>";
124 // Fixed Ray Key: 12:00 full; 9:00 half; 9:05 three-quarter length.
125 o << "</g><g class=\"ckey-polar-keys\">";
126 for (const auto &[slot,fraction] : {std::pair{0,1.0},std::pair{432,.5},std::pair{440,.75}}) {
127 const double a=kTau*slot/576.0,r0=26.30,r1=r0+3.6*fraction;
128 o << "<path d=\"M " << center+r0*std::sin(a) << ' ' << center-r0*std::cos(a) << " L " << center+r1*std::sin(a) << ' ' << center-r1*std::cos(a) << "\" fill=\"none\" stroke-width=\".28\" stroke-linecap=\"round\"/>";
129 }
130 // Fixed Ring Key: two adjacent dots, one empty slot, one dot, then a
131 // continuous four-slot arc of equal angular occupancy.
132 constexpr double key_r=kExtendedRingKeyRadius;
133 for (int slot : {72,73,75}) { const double a=kTau*slot/kExtendedRingSlots; svg_circle(o,center+key_r*std::sin(a),center-key_r*std::cos(a),dot_radius); }
134 svg_ring_run(o,center,key_r,75.5,79.5,kExtendedRingSlots,dot_radius,".1500");
135 o << "</g><g class=\"ckey-data-polar\">";
136 std::array<std::array<bool,kExtendedRingSlots>,kExtendedRings> ring_bits{}; std::array<std::array<bool,3>,kExtendedRaySlots> ray_bits{};
137 for (size_t i=0;i<nodes.size();++i) {
138 const auto &n=nodes[i]; const bool bit=extended_site_value(s,i);
139 if (n.kind==ExtendedKind::cart) { if (bit) c << "<rect x=\"" << n.x-.17 << "\" y=\"" << n.y-.17 << "\" width=\".34\" height=\".34\" rx=\".08\" stroke=\"none\"/>"; }
140 else if (n.kind==ExtendedKind::ring) ring_bits[n.ring][n.slot]=bit;
141 else ray_bits[n.slot][n.component]=bit;
142 }
143 // The ray alphabet is (Gray length, width): 4 calibrated lengths x 2
144 // calibrated weights, three site values per ray. No state is an absent ray.
145 for (int slot=0;slot<kExtendedRaySlots;++slot) {
146 if (extended_reserved_ray(slot)) continue;
147 const int gray=(ray_bits[slot][0]?2:0)|(ray_bits[slot][1]?1:0), level=gray^(gray>>1);
148 const double a=kTau*slot/576.0,r0=26.30,length=.80+.85*level,width=ray_bits[slot][2]?.28:.16;
149 o << "<path d=\"M " << center+r0*std::sin(a) << ' ' << center-r0*std::cos(a) << " L " << center+(r0+length)*std::sin(a) << ' ' << center-(r0+length)*std::cos(a) << "\" fill=\"none\" stroke-width=\"" << width << "\" stroke-linecap=\"round\"/>";
150 }
151 for (int ring=0;ring<kExtendedRings;++ring) {
152 const double radius=kExtendedRingInner+kExtendedRingPitch*ring;
153 for (int slot=0;slot<kExtendedRingSlots;) {
154 if (!ring_bits[ring][slot]) { ++slot; continue; }
155 int end=slot+1; while(end<kExtendedRingSlots && ring_bits[ring][end]) ++end;
156 if (end-slot==1) { const double a=kTau*slot/kExtendedRingSlots; svg_circle(o,center+radius*std::sin(a),center-radius*std::cos(a),dot_radius); }
157 else svg_ring_run(o,center,radius,slot-.5,end-.5,kExtendedRingSlots,dot_radius,".1500");
158 slot=end;
159 }
160 }
161 o << "</g><g class=\"ckey-data-cartesian\">" << c.str() << "</g></g></svg>"; const std::string text=o.str(); *required=text.size()+1;
162 if (!out || cap<*required) return CKEY_INSUFFICIENT_BUFFER;
163 std::memcpy(out,text.c_str(),*required);
164 return CKEY_OK;
165}
166
167void ckey_style_default(ckey_style *st) { if (st) fill_default_style(*st); }
168
169ckey_status ckey_style_preset_fill(ckey_style *st, ckey_style_preset preset) {
170 if (!st || (preset != CKEY_PRESET_PLAIN && preset != CKEY_PRESET_CONCEPT)) return CKEY_INVALID_ARGUMENT;
171 ckey_style_default(st);
172 if (preset == CKEY_PRESET_CONCEPT) {
173 st->background = {0x14, 0x16, 0x1e}; st->background_edge = {0x07, 0x08, 0x0c};
174 st->ink = {0xf6, 0xee, 0xe0}; st->ink_shade = {0xc8, 0xb6, 0x9a};
175 st->accents[0] = {0xa9, 0xb8, 0x96}; st->accents[1] = {0x9e, 0xa1, 0xa6}; st->accent_count = 2; st->accent_percent = 18;
176 st->bevel = 0.6; st->shadow = 0.5; st->texture = 0.4;
177 }
178 return CKEY_OK;
179}
180
181double ckey_scalable_logo_max_radius(uint32_t version, ckey_scalable_layout layout) {
182 return version >= 1 && version <= CKEY_SCALABLE_VERSION_COUNT && static_cast<int>(layout) >= 0 && static_cast<int>(layout) < kScalableFamilies
183 ? logo_max_radius(scalable_layout(static_cast<int>(version), static_cast<int>(layout))) : -1.0;
184}
185
186double ckey_scalable_logo_load(const ckey_scalable_symbol *s, double radius) {
187 if (!s || s->version < 1 || s->version > CKEY_SCALABLE_VERSION_COUNT || s->ecc_level > 3 || s->layout >= kScalableFamilies || !std::isfinite(radius) || radius < 0) return -1.0;
188 return logo_load(scalable_layout(static_cast<int>(s->version), static_cast<int>(s->layout)), static_cast<int>(s->ecc_level), radius);
189}
190
191ckey_status ckey_check_scalable_style(const ckey_scalable_symbol *s, const ckey_style *st, ckey_diagnostics *diag) {
192 if (!s || !st || s->version < 1 || s->version > CKEY_SCALABLE_VERSION_COUNT || s->ecc_level > 3 || s->layout >= kScalableFamilies) {
193 set_status(diag, CKEY_INVALID_ARGUMENT, "style check needs a Profile 0 symbol and a style"); return CKEY_INVALID_ARGUMENT;
194 }
195 double contrast = 0, load = 0;
196 const ScalableLayout &L = scalable_layout(static_cast<int>(s->version), static_cast<int>(s->layout));
197 const std::string structural = structural_problem(L, *st);
198 if (!structural.empty()) { set_status(diag, CKEY_INVALID_ARGUMENT, structural.c_str()); return CKEY_INVALID_ARGUMENT; }
199 ckey_style eff = *st; uint32_t adjusted = 0;
200 if (st->policy == CKEY_POLICY_ADAPTIVE && !safety_problem(L, *s, eff, {}, contrast, load).empty()) adjusted = adapt_style(L, *s, eff, {});
201 std::string advice;
202 const std::string problem = safety_problem(L, *s, eff, {}, contrast, load, &advice);
203 char message[96];
204 if (!problem.empty()) {
205 if (st->policy != CKEY_POLICY_OVERRIDE) { set_status(diag, CKEY_INVALID_ARGUMENT, problem.c_str()); return CKEY_INVALID_ARGUMENT; }
206 std::snprintf(message, sizeof(message), "override, NOT scan-safe: %s", problem.c_str());
207 set_status(diag, CKEY_OK, message);
208 return CKEY_OK;
209 }
210 if (!advice.empty()) std::snprintf(message, sizeof(message), "style OK%s, but %s; certify it to check that it scans", adjusted ? " after adaptive changes" : "", advice.c_str());
211 else std::snprintf(message, sizeof(message), "style OK%s: contrast >= %.1f:1; logo uses %.0f%% of block correction",
212 adjusted ? " after adaptive changes" : "", contrast, 100 * load);
213 set_status(diag, CKEY_OK, message);
214 return CKEY_OK;
215}
216
217ckey_status ckey_render_scalable_svg(const ckey_scalable_symbol *s, uint32_t scale, char *out, size_t cap, size_t *required) {
218 ckey_style st; ckey_style_default(&st);
219 return ckey_render_scalable_styled_svg(s, &st, scale, out, cap, required);
220}
221
222ckey_status ckey_render_scalable_styled_svg(const ckey_scalable_symbol *s, const ckey_style *style_in, uint32_t scale,
223 char *out, size_t cap, size_t *required) {
224 if (!s || !style_in || !required || scale == 0 || scale > CKEY_MAX_RENDER_SCALE || s->abi_version != CKEY_ABI_VERSION ||
225 s->version < 1 || s->version > CKEY_SCALABLE_VERSION_COUNT || s->layout >= kScalableFamilies) return CKEY_INVALID_ARGUMENT;
226 const ScalableLayout &L = scalable_layout(static_cast<int>(s->version), static_cast<int>(s->layout));
227 if (s->codeword_length != L.codeword_bytes) return CKEY_INVALID_ARGUMENT;
228 const ckey_style eff = effective_style(L, *s, *style_in);
229 const ckey_style *st = &eff;
230 double contrast = 0, load = 0;
231 if (!check_style(L, *s, eff, contrast, load).empty()) return CKEY_INVALID_ARGUMENT;
232 const double p = L.pitch, dot = L.dot;
233 const std::string dot_stroke = fixed4(2 * dot), cap_style = st->rays == CKEY_RAY_FLAT ? "butt" : "round";
234 const bool sheen = !same_colour(st->ink, st->ink_shade), vignette = !same_colour(st->background, st->background_edge);
235 const bool depth = st->bevel > 0 || st->shadow > 0;
236 const std::string ink = sheen ? "url(#ckey-ink)" : hex_colour(st->ink);
237 std::ostringstream o; o << std::fixed << std::setprecision(4);
238 std::ostringstream c; c << std::fixed << std::setprecision(4);
239 o << "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"" << 31 * scale << "\" height=\"" << 31 * scale << "\" viewBox=\"-1 -1 31 31\" role=\"img\" aria-label=\"CKey scalable symbol profile 0" << (L.family ? ".1" : "") << " version " << s->version << "\">";
240 // ---- Materials ----
241 if (sheen || vignette || depth || st->texture > 0 || (st->logo_href && *st->logo_href)) {
242 o << "<defs>";
243 if (sheen) o << "<linearGradient id=\"ckey-ink\" gradientUnits=\"userSpaceOnUse\" x1=\"0\" y1=\"0\" x2=\"29\" y2=\"29\"><stop offset=\"0\" stop-color=\"" << hex_colour(st->ink)
244 << "\"/><stop offset=\"0.55\" stop-color=\"" << hex_colour(st->ink_shade) << "\"/><stop offset=\"1\" stop-color=\"" << hex_colour(st->ink) << "\"/></linearGradient>";
245 if (vignette) o << "<radialGradient id=\"ckey-bg\" gradientUnits=\"userSpaceOnUse\" cx=\"14.5\" cy=\"14.5\" r=\"21\"><stop offset=\"0\" stop-color=\"" << hex_colour(st->background)
246 << "\"/><stop offset=\"1\" stop-color=\"" << hex_colour(st->background_edge) << "\"/></radialGradient>";
247 if (st->texture > 0) o << "<filter id=\"ckey-grain\" x=\"0\" y=\"0\" width=\"100%\" height=\"100%\"><feTurbulence type=\"fractalNoise\" baseFrequency=\"1.7\" numOctaves=\"2\" seed=\"7\"/><feColorMatrix type=\"saturate\" values=\"0\"/></filter>";
248 if (depth) {
249 o << "<filter id=\"ckey-depth\" x=\"-2%\" y=\"-2%\" width=\"104%\" height=\"104%\" color-interpolation-filters=\"sRGB\">";
250 std::string base = "SourceGraphic";
251 if (st->shadow > 0) { o << "<feDropShadow dx=\"0.05\" dy=\"0.09\" stdDeviation=\"0.07\" flood-color=\"#000000\" flood-opacity=\"" << 0.75 * st->shadow << "\" result=\"shadowed\"/>"; base = "shadowed"; }
252 if (st->bevel > 0)
253 o << "<feGaussianBlur in=\"SourceAlpha\" stdDeviation=\"0.045\" result=\"bump\"/><feSpecularLighting in=\"bump\" surfaceScale=\"3\" specularConstant=\"" << 1.1 * st->bevel
254 << "\" specularExponent=\"16\" lighting-color=\"#ffffff\" result=\"spec\"><feDistantLight azimuth=\"225\" elevation=\"40\"/></feSpecularLighting>"
255 << "<feComposite in=\"spec\" in2=\"SourceAlpha\" operator=\"in\" result=\"shine\"/><feComposite in=\"shine\" in2=\"" << base << "\" operator=\"arithmetic\" k1=\"0\" k2=\"1\" k3=\"1\" k4=\"0\"/>";
256 o << "</filter>";
257 }
258 if (st->logo_href && *st->logo_href) {
259 const double r = st->logo_radius > 0 ? st->logo_radius : logo_quiet_radius(L);
260 o << "<clipPath id=\"ckey-logo-clip\"><circle cx=\"14.5\" cy=\"14.5\" r=\"" << r << "\"/></clipPath>";
261 }
262 o << "</defs>";
263 }
264 o << "<rect x=\"-1\" y=\"-1\" width=\"31\" height=\"31\" fill=\"" << (vignette ? "url(#ckey-bg)" : hex_colour(st->background)) << "\"/>";
265 if (st->texture > 0) o << "<rect x=\"-1\" y=\"-1\" width=\"31\" height=\"31\" filter=\"url(#ckey-grain)\" opacity=\"" << 0.18 * st->texture << "\"/>";
266 o << "<g fill=\"" << ink << "\" stroke=\"" << ink << "\"" << (depth ? " filter=\"url(#ckey-depth)\"" : "") << ">"
267 << "<g class=\"ckey-frame\"><rect x=\"0.5\" y=\"0.5\" width=\"28\" height=\"28\" rx=\"3.5\" fill=\"none\" stroke-width=\"0.65\"/></g>"
268 << "<g class=\"ckey-finders\">";
269 svg_corner_finders(o, 29.0, 4.0, {2.30, 0.0, 0.0}, .80, .95);
270 o << "</g><g class=\"ckey-grid-key\">";
271 for (int r = 0; r < 2; ++r) for (int cc = 0; cc < 2; ++cc) emit_module(o, (L.col0 + cc + .5) * p, (L.row0 + r + .5) * p, p, st->modules, "");
272 o << "</g><g class=\"ckey-polar-keys\">";
273 for (const auto &[slot, fraction] : {std::pair{0, 1.0}, std::pair{L.ray_key_half(), 0.5}, std::pair{L.ray_key_three(), 0.75}}) {
274 const double a = kTau * slot / L.dial_slots, r0 = kScalableDialInner, r1 = r0 + L.key_ray() * fraction;
275 o << "<path d=\"M " << kCenter + r0 * std::sin(a) << ' ' << kCenter - r0 * std::cos(a) << " L " << kCenter + r1 * std::sin(a) << ' ' << kCenter - r1 * std::cos(a)
276 << "\" fill=\"none\" stroke-width=\"" << 0.44 * p << "\" stroke-linecap=\"" << cap_style << "\"/>";
277 }
278 // The Ring Key keeps its dot/gap/run pattern in every style: it is a key.
279 const double rk = L.ring_radius(L.rings - 1); const int k = L.ring_key_slot(); const double SK = L.ring_slots.back();
280 for (int slot : L.ring_key_dots()) { const double a = kTau * (k + slot) / SK; svg_circle(o, kCenter + rk * std::sin(a), kCenter - rk * std::cos(a), dot); }
281 svg_ring_run(o, kCenter, rk, k + L.ring_key_run().first, k + L.ring_key_run().second, SK, dot, dot_stroke.c_str());
282 o << "</g><g class=\"ckey-data-polar\">";
283 const size_t stride = static_cast<size_t>(L.ring_stride);
284 std::vector<uint8_t> ring_bits(static_cast<size_t>(L.rings) * stride, 0);
285 std::vector<int> ring_accent(static_cast<size_t>(L.rings) * stride, -1);
286 for (size_t i = 0; i < L.nodes.size(); ++i) {
287 const auto &n = L.nodes[i]; const bool bit = scalable_site_value(L, s, i);
288 const int accent = accent_of(n.id, *st);
289 if (n.kind == 0) { if (bit) emit_module(c, n.x, n.y, p, st->modules, accent >= 0 ? hex_colour(st->accents[accent]) : ""); }
290 else if (n.kind == 1) {
291 const double a = kTau * n.slot / L.dial_slots, r0 = kScalableDialInner, r1 = r0 + (bit ? L.long_ray() : L.short_ray());
292 o << "<path d=\"M " << kCenter + r0 * std::sin(a) << ' ' << kCenter - r0 * std::cos(a) << " L " << kCenter + r1 * std::sin(a) << ' ' << kCenter - r1 * std::cos(a)
293 << "\" fill=\"none\" stroke-width=\"" << L.ray_width << "\" stroke-linecap=\"" << cap_style << "\"/>";
294 } else { ring_bits[static_cast<size_t>(n.ring) * stride + n.slot] = bit; ring_accent[static_cast<size_t>(n.ring) * stride + n.slot] = accent; }
295 }
296 for (int ring = 0; ring < L.rings; ++ring) {
297 const double radius = L.ring_radius(ring); const int S = L.ring_slots[static_cast<size_t>(ring)];
298 for (int slot = 0; slot < S;) {
299 const size_t at = static_cast<size_t>(ring) * stride + slot;
300 if (!ring_bits[at]) { ++slot; continue; }
301 int end = slot + 1;
302 if (st->rings == CKEY_RING_JOINED) while (end < S && ring_bits[static_cast<size_t>(ring) * stride + end]) ++end;
303 const int accent = ring_accent[at];
304 const std::string colour = accent >= 0 ? hex_colour(st->accents[accent]) : "";
305 if (end - slot == 1) {
306 const double a = kTau * slot / S;
307 o << "<circle cx=\"" << kCenter + radius * std::sin(a) << "\" cy=\"" << kCenter - radius * std::cos(a) << "\" r=\"" << dot << "\" stroke=\"none\""
308 << (colour.empty() ? "" : " fill=\"" + colour + "\"") << "/>";
309 } else if (colour.empty()) svg_ring_run(o, kCenter, radius, slot - .5, end - .5, S, dot, dot_stroke.c_str());
310 else { o << "<g stroke=\"" << colour << "\">"; svg_ring_run(o, kCenter, radius, slot - .5, end - .5, S, dot, dot_stroke.c_str()); o << "</g>"; }
311 slot = end;
312 }
313 }
314 o << "</g><g class=\"ckey-data-cartesian\">" << c.str() << "</g></g>";
315 if (st->logo_href && *st->logo_href) {
316 const double r = st->logo_radius > 0 ? st->logo_radius : logo_quiet_radius(L);
317 if (st->logo_plate) o << "<g class=\"ckey-logo-plate\"><circle cx=\"14.5\" cy=\"14.5\" r=\"" << r + 0.1 << "\" fill=\"" << hex_colour(st->background) << "\"/></g>";
318 o << "<image href=\"" << xml_escape(st->logo_href) << "\" x=\"" << 14.5 - r << "\" y=\"" << 14.5 - r << "\" width=\"" << 2 * r << "\" height=\"" << 2 * r
319 << "\" preserveAspectRatio=\"xMidYMid meet\" clip-path=\"url(#ckey-logo-clip)\"/>";
320 }
321 o << "</svg>";
322 const std::string text = o.str(); *required = text.size() + 1;
323 if (!out || cap < *required) return CKEY_INSUFFICIENT_BUFFER;
324 std::memcpy(out, text.c_str(), *required);
325 return CKEY_OK;
326}
327
328ckey_status ckey_render_scalable_print(const ckey_scalable_symbol *s, const ckey_style *style, double width_in, double dpi,
329 uint8_t *out, size_t cap, size_t *out_side) {
330 if (!s || !out_side || !std::isfinite(width_in) || !std::isfinite(dpi) || width_in <= 0 || dpi < 72 || dpi > 4800 || width_in * dpi > 20000)
331 return CKEY_INVALID_ARGUMENT;
332 ckey_style st; ckey_style_default(&st);
333 if (style) { st.modules = style->modules; st.rings = style->rings; st.rays = style->rays; } // shapes only
334 const int side = std::max(16, static_cast<int>(std::lround(width_in * dpi)));
335 if (side > 16384) return CKEY_INVALID_ARGUMENT;
336 // Direct geometry -> printer dots: black where at least half of a 3x3
337 // sample grid is ink (no SVG in this path; specification §2.2).
338 ckey_raster_options o; ckey_raster_options_default(&o);
339 o.size_px = static_cast<uint32_t>(side); o.policy = CKEY_RASTER_BINARY; o.format = CKEY_PIXELS_GRAY8;
340 o.supersample = 3; o.materials = 0;
341 size_t needed = 0;
342 const ckey_status probe = ckey_render_scalable_raster(s, &st, &o, nullptr, 0, &needed);
343 if (probe != CKEY_INSUFFICIENT_BUFFER) return probe == CKEY_OK ? CKEY_INTERNAL_ERROR : probe;
344 *out_side = static_cast<size_t>(side);
345 if (!out || cap < needed) return CKEY_INSUFFICIENT_BUFFER;
346 return ckey_render_scalable_raster(s, &st, &o, out, cap, &needed);
347}
348
349} // extern "C"
350
351#include "scalable_render.inc"