mirror of
https://github.com/pbakaus/impeccable.git
synced 2026-09-11 21:57:14 +03:00
font-fingerprint: full-height inked columns leave the row profile; forceAllowed needs the user's reported words and a downgrade
Staff rules and a black page edge fused eight track rows into one 389px 'line'. A session forced two gates by quoting a brief line ('should feel like an extension of her artwork') as permission; a force now needs the user's words reported or quoted, a downgrade verb, and the comp noun in one reason.
AI-assisted (Claude Code).
This commit is contained in:
@@ -586,7 +586,17 @@ export function forceAllowed(reason) {
|
||||
// translation is what the gate measures; it is not a downgrade.
|
||||
const aboutComp = /\b(comp|mock|mockup|composition|fidelity|plate|region)\b/i.test(reason);
|
||||
const isTranslationDodge = /truthful|semantic|pixel-level|prioriti[sz]e (facts|semantics|accessibility)/i.test(reason) && !/(drop|skip|remove|without|not needed|don't need|do not need|ignore) (the )?(comp|plate|region|fidelity)/i.test(reason);
|
||||
return namesUser && aboutComp && !isTranslationDodge;
|
||||
// The user's words have to be a downgrade of the comp, said as such: a
|
||||
// brief line quoted back ("should feel like an extension of her artwork")
|
||||
// is the direction the comp already serves, not permission to leave it.
|
||||
// One session forced two gates on that sentence. Ask for a downgrade verb
|
||||
// in the same reason as the comp noun.
|
||||
const downgrades = /\b(don't|do not|doesn't|does not|no longer|not) (need|have to|want|care|require|match|follow|hold)|\b(drop|skip|remove|ignore|waive|relax|override|approve|approved|accept|accepted|fine|okay|ok|good enough|ship it|move on|proceed|go ahead|instead of|rather than)\b/i.test(reason);
|
||||
// and the user's words are quoted or reported, not paraphrased into a
|
||||
// principle ("the user made the artwork binding" is the model's reading)
|
||||
const reported = /["'\u201c\u2018].{6,}["'\u201d\u2019]|\b(user|they|paul) (said|says|asked|asks|told|wrote|replied|answered|chose|picked|approved|confirmed)\b/i.test(reason);
|
||||
const briefQuoteOnly = /\b(should feel|feel like|not a .* page|extension of)\b/i.test(reason) && !/\b(comp|mock|fidelity|gate|plate)\b.*\b(approved|accept|fine|ok|okay|skip|drop|waive|relax|override|move on|proceed)\b/i.test(reason);
|
||||
return namesUser && aboutComp && downgrades && reported && !isTranslationDodge && !briefQuoteOnly;
|
||||
}
|
||||
|
||||
export function advance(state, { force = false, reason = null, gateOpts = {} } = {}) {
|
||||
|
||||
@@ -88,8 +88,17 @@ function binarize(img) {
|
||||
/** Text lines from the row-ink profile (same rules as font-match v1). */
|
||||
function findLines(bin) {
|
||||
const { W, H, ink } = bin;
|
||||
// Columns inked top to bottom (a rule, a black margin, a page edge) span
|
||||
// every line and would fuse them into one run: leave them out of the row
|
||||
// profile. Lettering never fills a column for more than ~85% of the crop.
|
||||
const colInk = new Uint32Array(W);
|
||||
for (let y = 0; y < H; y++) { const o = y * W; for (let x = 0; x < W; x++) colInk[x] += ink[o + x]; }
|
||||
const colOk = new Uint8Array(W);
|
||||
let okCount = 0;
|
||||
for (let x = 0; x < W; x++) { if (colInk[x] < H * 0.85) { colOk[x] = 1; okCount++; } }
|
||||
if (!okCount) return { lines: [], rowInk: new Uint32Array(H) };
|
||||
const rowInk = new Uint32Array(H);
|
||||
for (let y = 0; y < H; y++) { let c = 0; const o = y * W; for (let x = 0; x < W; x++) c += ink[o + x]; rowInk[y] = c; }
|
||||
for (let y = 0; y < H; y++) { let c = 0; const o = y * W; for (let x = 0; x < W; x++) if (colOk[x]) c += ink[o + x]; rowInk[y] = c; }
|
||||
const floor = Math.max(1, W * 0.004);
|
||||
const runs = [];
|
||||
let y = 0;
|
||||
|
||||
Reference in New Issue
Block a user