/+ - Name: SisuDoc Spine, Doc Reform [a part of] - Description: documents, structuring, processing, publishing, search - static content generator - Author: Ralph Amissah [ralph.amissah@gmail.com] - Copyright: (C) 2015 (continuously updated, current 2026) Ralph Amissah, All Rights Reserved. - License: AGPL 3 or later: Spine (SiSU), a framework for document structuring, publishing and search Copyright (C) Ralph Amissah This program is free software: you can redistribute it and/or modify it under the terms of the GNU AFERO General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see [https://www.gnu.org/licenses/]. If you have Internet connection, the latest version of the AGPL should be available at these locations: [https://www.fsf.org/licensing/licenses/agpl.html] [https://www.gnu.org/licenses/agpl.html] - Spine (by Doc Reform, related to SiSU) uses standard: - docReform markup syntax - standard SiSU markup syntax with modified headers and minor modifications - docReform object numbering - standard SiSU object citation numbering & system - Homepages: [https://www.sisudoc.org] [https://www.doc-reform.org] - Git [https://git.sisudoc.org/] +/ module read.images; import std.array : join; import std.conv : to; import std.stdio : writeln, File; import ocda.model; /+ ↓ the .image records of a document, gathered per distinct file, with the objects referencing each file, in order of first appearance +/ struct ImageEntry { string name; string sha256; ulong bytes; int width; int height; bool missing; string[] refs; string[] other_sha256; // same filename, differing digest } struct ImageRecords { int records; // .image lines, an image referenced twice counts twice ImageEntry[] entries; // distinct files } ImageRecords gatherImageRecords(OcdaDocument doc) { ImageRecords ret; size_t[string] seen; foreach (section; sectionNames) { auto objs = doc.sectionByName(section); if (objs is null) continue; foreach (obj; *objs) { foreach (img; obj.images) { ret.records += 1; string _ref = section ~ "[" ~ obj.ocn.to!string ~ "]"; if (auto at = img.name in seen) { if (ret.entries[*at].sha256 != img.sha256 && img.sha256.length > 0 && ret.entries[*at].sha256.length > 0) { ret.entries[*at].other_sha256 ~= img.sha256; } ret.entries[*at].refs ~= _ref; } else { ImageEntry e_; e_.name = img.name; e_.sha256 = img.sha256; e_.bytes = img.bytes; e_.width = img.width; e_.height = img.height; e_.missing = img.missing; e_.refs = [_ref]; seen[img.name] = ret.entries.length; ret.entries ~= e_; } } } } return ret; } /+ ↓ image manifest, line oriented and greppable: refs: body[97] body[412] MISSING refs: body[840] The point of the record in the .ssp is verification (is the image shipped in this pod the image the abstraction was built from), so this output is the shape a verifier wants: one line per distinct file. +/ void outputImages(OcdaDocument doc, string output_file) { auto gathered = gatherImageRecords(doc); string[] lines; int missing = 0; foreach (e; gathered.entries) { if (e.missing) missing += 1; } lines ~= "Images: " ~ gathered.records.to!string ~ " record" ~ ((gathered.records == 1) ? "" : "s") ~ ", " ~ gathered.entries.length.to!string ~ " distinct file" ~ ((gathered.entries.length == 1) ? "" : "s") ~ ", " ~ missing.to!string ~ " missing"; lines ~= ""; foreach (e; gathered.entries) { string px = (e.width > 0 && e.height > 0) ? e.width.to!string ~ "x" ~ e.height.to!string : "-"; lines ~= (e.missing) ? "MISSING" ~ " " ~ e.name ~ " refs: " ~ e.refs.join(" ") : e.sha256 ~ " " ~ e.bytes.to!string ~ " " ~ px ~ " " ~ e.name ~ " refs: " ~ e.refs.join(" "); /+ ↓ one filename, more than one digest: the .ssp is inconsistent, or two distinct files share a name +/ foreach (other; e.other_sha256) { lines ~= " WARNING differing digest for same filename: " ~ other; } } /+ ↓ write output +/ if (output_file.length > 0) { auto f = File(output_file, "w"); foreach (line; lines) f.writeln(line); } else { foreach (line; lines) writeln(line); } } /+ ↓ count of .image records in a document, for the parse summary +/ int imageRecordCount(OcdaDocument doc) { return gatherImageRecords(doc).records; } package immutable string[] sectionNames = [ "head", "toc", "body", "endnotes", "glossary", "bibliography", "bookindex", "blurb", ];