mirror of
https://github.com/storytold/designcraft.git
synced 2026-10-26 03:06:56 -06:00
CJK-MOJI9: preserve CID preferences and report unsupported glyph classification
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
# DesignCraft roadmap
|
||||
|
||||
- CJK-MOJI9: retain the CID Mojikumi compatibility preference and diagnose actual Adobe-Japan1 font use; exact CID class mapping remains unsupported.
|
||||
- CJK-MOJI8: preserve shaped narrow punctuation and manual Tsume instead of restoring nonexistent full-width blank space.
|
||||
- CJK-MOJI7: reject malformed Mojikumi interchange values and check nested note/cell paragraph rules in preflight.
|
||||
- CJK-MOJI6: apply spacing at shaped-cluster boundaries and distinguish centered Traditional Chinese punctuation from Simplified Chinese side bearings.
|
||||
|
||||
@@ -352,6 +352,10 @@ pub struct DocSettings {
|
||||
/// Documents saved before the setting existed read as on, the way they were drawn.
|
||||
#[serde(default = "yes")]
|
||||
pub glyph_fallback: bool,
|
||||
/// Preserve IDML's CID-based Mojikumi request. Unicode classification is
|
||||
/// currently used; preflight reports actual Adobe-Japan1 font use.
|
||||
#[serde(default)]
|
||||
pub use_cid_mojikumi: bool,
|
||||
}
|
||||
|
||||
impl Default for DocSettings {
|
||||
@@ -384,6 +388,7 @@ impl Default for DocSettings {
|
||||
overprint_black: true,
|
||||
track_changes: false,
|
||||
glyph_fallback: false,
|
||||
use_cid_mojikumi: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -110,6 +110,19 @@ fn overset_location(s: &Session, target: designcraft_doc::ItemId) -> Option<(des
|
||||
None
|
||||
}
|
||||
|
||||
fn rendered_faces(cs: &ComposedStory, faces: &mut Vec<FaceRef>) {
|
||||
let mut pending: Vec<_> = cs.frames.iter().collect();
|
||||
while let Some(frame) = pending.pop() {
|
||||
for g in frame.lines.iter().flat_map(|line| &line.glyphs).filter(|g| g.visible) {
|
||||
if !faces.contains(&g.face) {
|
||||
faces.push(g.face);
|
||||
}
|
||||
}
|
||||
pending.extend(frame.notes.iter().flat_map(|n| &n.text.frames));
|
||||
pending.extend(frame.tables.iter().flat_map(|t| t.cells.iter().flat_map(|c| &c.text.frames)));
|
||||
}
|
||||
}
|
||||
|
||||
pub fn check(s: &Session, min_ppi: f64) -> Vec<Issue> {
|
||||
let Some(st) = s.active() else { return vec![] };
|
||||
let d = &st.doc;
|
||||
@@ -117,10 +130,15 @@ pub fn check(s: &Session, min_ppi: f64) -> Vec<Issue> {
|
||||
// Stories: overset and fonts.
|
||||
let mut missing_fonts: Vec<String> = Vec::new();
|
||||
let mut unsupported_typography = std::collections::BTreeSet::new();
|
||||
let mut cid_faces = Vec::new();
|
||||
let mut uses_mojikumi = false;
|
||||
let mut missing: Vec<(FaceRef, Vec<char>, designcraft_doc::ItemId)> = Vec::new();
|
||||
for story in d.stories.values() {
|
||||
let cs = s.cache.get(d, story.id, None);
|
||||
missing_glyphs(&cs, None, 0, &mut missing);
|
||||
if d.settings.use_cid_mojikumi {
|
||||
rendered_faces(&cs, &mut cid_faces);
|
||||
}
|
||||
if cs.is_overset() {
|
||||
let last = story.frames.last().copied();
|
||||
let location = last.and_then(|f| overset_location(s, f));
|
||||
@@ -144,6 +162,7 @@ pub fn check(s: &Session, min_ppi: f64) -> Vec<Issue> {
|
||||
let ranges = story.para_ranges();
|
||||
for (pi, pf) in story.paras.iter().enumerate() {
|
||||
let (para, base) = d.styles.resolve_para(pf);
|
||||
uses_mojikumi |= !matches!(para.mojikumi.as_str(), "" | "Nothing" | "None");
|
||||
let Some(range) = ranges.get(pi) else { continue };
|
||||
let has_rtl = story.text.get(range.clone()).is_some_and(|text| text.chars().any(designcraft_fonts::is_rtl));
|
||||
if has_rtl {
|
||||
@@ -207,6 +226,14 @@ pub fn check(s: &Session, min_ppi: f64) -> Vec<Issue> {
|
||||
for f in missing_fonts {
|
||||
out.push(Issue { severity: "error", kind: "missingFont", message: format!("Missing font: {f}"), item: None, page: None });
|
||||
}
|
||||
if uses_mojikumi {
|
||||
for face in cid_faces.iter().filter(|face| face.uses_japan1_cids()) {
|
||||
unsupported_typography.insert(format!(
|
||||
"CID-based Mojikumi requested for {} {}; Unicode classes are used because CID class mapping is not implemented",
|
||||
face.family, face.style
|
||||
));
|
||||
}
|
||||
}
|
||||
for message in unsupported_typography {
|
||||
out.push(Issue { severity: "warning", kind: "unsupportedTypography", message, item: None, page: None });
|
||||
}
|
||||
@@ -428,3 +455,20 @@ mod mojikumi_nested_tests {
|
||||
assert_eq!(issues.iter().filter(|i| i.kind == "unsupportedTypography" && i.message.contains("Unrecognized")).count(), 1);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod cid_preference_tests {
|
||||
use super::*;
|
||||
#[test]
|
||||
fn cid_request_is_undoable_and_does_not_warn_for_unicode_only_fonts() {
|
||||
let mut s = Session::new();
|
||||
s.execute("file.new", &json!({})).unwrap();
|
||||
let r = s.execute("frame.create", &json!({"rect":[0,0,300,200], "content":"text", "text":"ABC"})).unwrap();
|
||||
s.execute("text.select", &json!({"story":r["story"],"anchor":0,"focus":3})).unwrap();
|
||||
s.execute("type.para", &json!({"attrs":{"mojikumi":"SimpChineseDefault"}})).unwrap();
|
||||
assert_eq!(s.execute("document.preferences", &json!({"useCidMojikumi":true})).unwrap()["useCidMojikumi"], true);
|
||||
assert!(!check(&s, 150.0).iter().any(|i| i.message.contains("CID-based")));
|
||||
s.execute("edit.undo", &json!({})).unwrap();
|
||||
assert_eq!(s.execute("document.preferences", &json!({})).unwrap()["useCidMojikumi"], false);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,6 +21,7 @@ const DOC_KEYS: &[&str] = &[
|
||||
"advancedType",
|
||||
"overprintBlack",
|
||||
"glyphFallback",
|
||||
"useCidMojikumi",
|
||||
];
|
||||
|
||||
pub fn specs() -> Vec<CommandSpec> {
|
||||
@@ -62,7 +63,7 @@ pub fn specs() -> Vec<CommandSpec> {
|
||||
"Document Preferences",
|
||||
[],
|
||||
None,
|
||||
"{horizontalUnits?, verticalUnits?: points|picas|inches|millimeters|…, keyboardIncrement? (pt), baselineGrid?: {start, increment, relativeTo, color, viewThreshold}, grid?: {horizontal, vertical, subdivisions, color, inBack}, pasteboard?: [h, v], marginColor?, columnColor?, bleedColor?, slugColor?: [r, g, b], advancedType?: {superscriptSize, superscriptPosition, subscriptSize, subscriptPosition} (%), overprintBlack?, glyphFallback? (draw characters the font lacks from fallback fonts; off: as the font's missing-glyph box)} → those settings",
|
||||
"{horizontalUnits?, verticalUnits?: points|picas|inches|millimeters|…, keyboardIncrement? (pt), baselineGrid?: {start, increment, relativeTo, color, viewThreshold}, grid?: {horizontal, vertical, subdivisions, color, inBack}, pasteboard?: [h, v], marginColor?, columnColor?, bleedColor?, slugColor?: [r, g, b], advancedType?: {superscriptSize, superscriptPosition, subscriptSize, subscriptPosition} (%), overprintBlack?, useCidMojikumi? (preserve CID classification request; unsupported font collections are diagnosed), glyphFallback? (draw characters the font lacks from fallback fonts; off: as the font's missing-glyph box)} → those settings",
|
||||
has_doc,
|
||||
|s, p| {
|
||||
let cur = serde_json::to_value(&s.doc()?.doc.settings).map_err(|e| bad("document.preferences", e.to_string()))?;
|
||||
|
||||
@@ -181,6 +181,13 @@ impl std::fmt::Debug for FontFace {
|
||||
}
|
||||
|
||||
impl FontFace {
|
||||
/// Adobe-Japan1 CFF fonts are the collection for CID-based Mojikumi.
|
||||
/// Other CID collections still use Unicode classes under Adobe's policy.
|
||||
pub fn uses_japan1_cids(&self) -> bool {
|
||||
use skrifa::raw::TableProvider;
|
||||
self.skrifa().and_then(|f| f.cff().ok()).is_some_and(cff_uses_japan1_cids)
|
||||
}
|
||||
|
||||
pub fn data(&self) -> &[u8] {
|
||||
match &self.bytes {
|
||||
FontBytes::Static(b) => b,
|
||||
@@ -1567,3 +1574,31 @@ mod tests_docfonts;
|
||||
#[cfg(test)]
|
||||
#[path = "tests_vertical.rs"]
|
||||
mod tests_vertical;
|
||||
|
||||
fn cff_uses_japan1_cids(cff: skrifa::raw::tables::cff::Cff<'_>) -> bool {
|
||||
use skrifa::raw::ps::cff::dict;
|
||||
let Some(top) = cff.top_dicts().get(0) else { return false };
|
||||
dict::entries(top, None).any(|entry| match entry {
|
||||
Ok(dict::Entry::Ros { registry, ordering, .. }) => cff.string(registry) == Some(b"Adobe") && cff.string(ordering) == Some(b"Japan1"),
|
||||
_ => false,
|
||||
})
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod mojikumi_cid_tests {
|
||||
use super::*;
|
||||
use skrifa::raw::{FontData, FontRead, tables::cff::Cff};
|
||||
#[test]
|
||||
fn recognizes_the_collection_in_cff_metadata_without_reading_outlines() {
|
||||
// Minimal synthetic CFF indices: one name, a ROS dict, two strings,
|
||||
// and one return-only subroutine. SID 391/392 reference the two strings.
|
||||
let mut data = vec![1, 0, 4, 4, 0, 1, 1, 1, 2, b'T', 0, 1, 1, 1, 8, 248, 27, 248, 28, 139, 12, 30, 0, 2, 1, 1, 6, 12];
|
||||
data.extend_from_slice(b"AdobeJapan1");
|
||||
data.extend_from_slice(&[0, 1, 1, 1, 2, 11]);
|
||||
assert!(cff_uses_japan1_cids(Cff::read(FontData::new(&data)).unwrap()));
|
||||
let end = data.len() - 6;
|
||||
data[end - 6..end].copy_from_slice(b"CNS1-X");
|
||||
assert!(!cff_uses_japan1_cids(Cff::read(FontData::new(&data)).unwrap()));
|
||||
assert!(!FontDb::global().face(crate::DEFAULT_FAMILY, "Regular").uses_japan1_cids());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -799,6 +799,7 @@ impl<'a> Ex<'a> {
|
||||
let a = &s.advanced_type;
|
||||
root.push(
|
||||
El::new("TextPreference")
|
||||
.attr("UseCidMojikumi", s.use_cid_mojikumi)
|
||||
.attr("SuperscriptSize", num(a.superscript_size))
|
||||
.attr("SuperscriptPosition", num(a.superscript_position))
|
||||
.attr("SubscriptSize", num(a.subscript_size))
|
||||
|
||||
@@ -273,6 +273,9 @@ impl<'r> Importer<'r> {
|
||||
}
|
||||
}
|
||||
"TextPreference" => {
|
||||
if let Some(v) = e.boolean("UseCidMojikumi") {
|
||||
self.settings.use_cid_mojikumi = v;
|
||||
}
|
||||
let a = &mut self.settings.advanced_type;
|
||||
for (k, v) in [
|
||||
("SuperscriptSize", &mut a.superscript_size),
|
||||
|
||||
@@ -968,3 +968,11 @@ fn malformed_mojikumi_rows_are_not_silently_coerced() {
|
||||
assert!(err.contains("Mojikumi"), "{key}={value}: {err}");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn cid_mojikumi_preference_survives_interchange() {
|
||||
let mut d = import_idml(&fixture_with_story(STORY)).unwrap();
|
||||
d.settings.use_cid_mojikumi = true;
|
||||
let restored = import_idml(&export_idml(&d)).unwrap();
|
||||
assert!(restored.settings.use_cid_mojikumi);
|
||||
}
|
||||
|
||||
+25
-4
@@ -26,8 +26,29 @@ Regression tests cover preset boundary differences, custom override precedence,
|
||||
IDML reference round trips, and unsupported names. The implementation must not be
|
||||
represented as a byte-for-byte reproduction of Adobe's full preset matrices.
|
||||
|
||||
## Audit work
|
||||
## Composition and validation
|
||||
|
||||
Further audit areas are regional punctuation metrics, shaped cluster boundaries,
|
||||
equal-priority allocation, discrete line fitting, input validation, and diagnostics
|
||||
for compatibility modes whose glyph classification cannot yet be reproduced.
|
||||
Spacing is attached to complete shaped clusters. Regional punctuation side
|
||||
bearings, shaped narrow forms, tracking, manual Tsume and explicit character aki
|
||||
are handled before line fitting. Continuous gaps of equal priority share one
|
||||
allocation pool; non-floating gaps reach discrete endpoints. Leading-only
|
||||
compression is included for ragged lines. Custom rows retain their dimensions.
|
||||
|
||||
Import rejects malformed integers, non-finite amounts, inverted ranges and invalid
|
||||
priorities. Native document validation uses the same range policy. Unknown integer
|
||||
classes remain preservable and receive diagnostics, including in nested notes and
|
||||
cells. The bounded supported spacing range is -1 to 100 em.
|
||||
|
||||
## Compatibility limits
|
||||
|
||||
[Adobe's TextPreference API](https://developer.adobe.com/indesign/uxp/omv/t/TextPreference/)
|
||||
defines CID-based classification. The IDML `UseCidMojikumi` preference now survives
|
||||
round trips and is accessible through `document.preferences`. The engine currently
|
||||
uses Unicode classes. Preflight reports the incompatibility when the document
|
||||
requests CID mode and actually renders Adobe-Japan1 CFF fonts with Mojikumi in use;
|
||||
it does not flag ordinary Unicode fonts merely because the preference is enabled.
|
||||
|
||||
Exact Adobe CID-to-Mojikumi class mapping, complete proprietary preset pair matrices,
|
||||
and legacy pre-CS2 vertical-scaling compatibility have not been reproduced or
|
||||
certified. Public CMap resources map character codes to CIDs; they are not a
|
||||
CID-to-Mojikumi-class oracle. No claim of complete Adobe output parity is made.
|
||||
|
||||
Reference in New Issue
Block a user