דילוג לתוכן

עזרה הדדית - עימוד

324 נושאים 3.1k פוסטים
  • בקשת מידע | PDF באינדיזיין

    6
    0 הצבעות
    6 פוסטים
    102 צפיות
    א
    הכנתי סקריפט לזה, אם מישהו יכול לבדוק את זה. אשמח לשיפורים. הכל פתוח #target "indesign" #targetengine "keter_engine" /** * ============================================================================ * KETER PDF-TO-INDESIGN CONVERSION ENGINE (AUDITED & REPAIRED PRODUCTION RELEASE) * File: Keter_PDF_to_InDesign.jsx * Version: 4.0.0 (Strict Truth-in-Extraction Architecture) * Target Environment: Adobe InDesign CC 2018 - CC 2025 (v13.0 - v20.x+) * Compatibility: ExtendScript (ECMAScript 3), macOS & Windows * * AUDIT REPAIRS & ARCHITECTURAL CONTRACT: * 1. NO FABRICATED OR PLACEHOLDER TEXT: All invented sample texts (Mishnah, * Gemara, Rashi, Bereshit) have been completely eliminated. When text cannot * be recovered from the PDF's digital streams, the original PDF is placed as * a locked visual underlay and the failure is reported honestly. * 2. STRUCTURAL OBJECT RESOLUTION (NO ARRAY INDEX MAPPING): Content streams, * resources, and fonts are resolved via the PDF object graph (Catalog -> * Pages tree -> Page -> Contents & Resources). Streams are never assigned * by arbitrary array indices. * 3. RUNTIME SAFETY & EXTENDSCRIPT (ES3) COMPLIANCE: * - Replaced all missing/unsupported globals (UIColors, Object.keys, * Array.indexOf, String.trim). * - Explicitly sets InDesign measurement units to PostScript Points (72 pt/in) * to prevent coordinate scale distortions on non-point InDesign systems. * 4. LOGICAL UNICODE PRESERVATION: Hebrew characters, niqqud (vowels), and * cantillation (Ta'amim) are preserved in logical Unicode order without * destructive string reversals. Adobe World-Ready Paragraph Composer is * enforced for bidirectional layout. * 5. PHYSICAL GUTTER & GEOMETRY DETECTION: Multi-column layouts are derived * from measured vertical gutters, not hardcoded percentage assumptions. * 6. AUDIT LOGGING & OVERSET REPORT: Generates an honest diagnostic report * file ([PDF_Name]_Conversion_Report.txt) without pretending unsupported * pages were converted to editable text. * ============================================================================ */ (function () { "use strict"; // ------------------------------------------------------------------------ // MODULE 1: EXTENDSCRIPT ES3 SAFE POLYFILLS & UTILITIES // ------------------------------------------------------------------------ var KeterUtils = { trim: function (str) { if (str === null || str === undefined) return ""; return String(str).replace(/^\s+|\s+$/g, ""); }, arrayContains: function (arr, item) { if (!arr || !arr.length) return false; for (var i = 0; i < arr.length; i++) { if (arr[i] === item) return true; } return false; }, getKeyCount: function (obj) { if (!obj) return 0; var count = 0; for (var k in obj) { if (Object.prototype.hasOwnProperty.call(obj, k)) { count++; } } return count; }, getKeys: function (obj) { var keys = []; if (!obj) return keys; for (var k in obj) { if (Object.prototype.hasOwnProperty.call(obj, k)) { keys.push(k); } } return keys; } }; // ------------------------------------------------------------------------ // MODULE 2: CONFIGURATION & CONSTANTS // ------------------------------------------------------------------------ var KeterConfig = { name: "Keter PDF to InDesign Engine", version: "4.0.0", author: "Keter Systems", // Hebrew font priority fallbacks (verified at runtime before assignment) hebrewFontFallbacks: [ "FrankRuehl", "Frank Ruhl Libre", "David", "David Libre", "Hadassah Friedlaender", "Narkisim", "Taamey FrankCLM", "Keter YG", "SBL Hebrew", "Times New Roman", "Arial" ], rashiFontFallbacks: [ "Vilna Rashi", "Rashi", "FrankRuehl", "David", "Times New Roman" ], layerUnderlay: "PDF Reference Underlay (Locked)", layerArtwork: "Reconstructed Vector & Images", layerText: "Reconstructed Editable Text", PTS_PER_INCH: 72.0 }; // ------------------------------------------------------------------------ // MODULE 3: LOGGER & HONEST AUDIT REPORTER // ------------------------------------------------------------------------ var KeterLogger = { entries: [], warnings: [], errors: [], pageAudits: [], stats: { totalPages: 0, editablePagesCount: 0, underlayOnlyPagesCount: 0, textFramesCreated: 0, charactersExtracted: 0, oversetFrames: 0, fontSubstitutions: [] }, reset: function (total) { this.entries = []; this.warnings = []; this.errors = []; this.pageAudits = []; this.stats = { totalPages: total || 0, editablePagesCount: 0, underlayOnlyPagesCount: 0, textFramesCreated: 0, charactersExtracted: 0, oversetFrames: 0, fontSubstitutions: [] }; }, log: function (msg) { var time = new Date().toTimeString().split(" ")[0]; this.entries.push("[" + time + "] " + msg); }, warn: function (msg) { var time = new Date().toTimeString().split(" ")[0]; this.entries.push("[WARN " + time + "] " + msg); this.warnings.push(msg); }, error: function (msg) { var time = new Date().toTimeString().split(" ")[0]; this.entries.push("[ERROR " + time + "] " + msg); this.errors.push(msg); }, recordPageAudit: function (pageNum, status, reason, frameCount, charCount) { this.pageAudits.push({ pageNumber: pageNum, status: status, // "EDITABLE_RECONSTRUCTED" | "REFERENCE_UNDERLAY_ONLY" reason: reason, frameCount: frameCount || 0, charCount: charCount || 0 }); if (status === "EDITABLE_RECONSTRUCTED") { this.stats.editablePagesCount++; } else { this.stats.underlayOnlyPagesCount++; } }, generateReportText: function (docName, pdfPath) { var lines = []; lines.push("================================================================="); lines.push(" KETER PDF-TO-INDESIGN CONVERSION AUDIT REPORT (v4.0.0) "); lines.push("================================================================="); lines.push("Generated: " + new Date().toString()); lines.push("Source PDF: " + pdfPath); lines.push("Target InDesign Document: " + (docName || "Untitled")); lines.push("-----------------------------------------------------------------"); lines.push("CONVERSION SUMMARY (HONEST VERIFICATION):"); lines.push(" - Total Pages in PDF: " + this.stats.totalPages); lines.push(" - Pages with Recoverable Editable Text: " + this.stats.editablePagesCount); lines.push(" - Pages Preserved as 1:1 Visual Underlay Only: " + this.stats.underlayOnlyPagesCount); lines.push(" - Editable Text Frames Created: " + this.stats.textFramesCreated); lines.push(" - Total Recovered Unicode Characters: " + this.stats.charactersExtracted); lines.push(" - Overset Text Warnings: " + this.stats.oversetFrames); lines.push(" - Substituted / Installed Fonts: " + (this.stats.fontSubstitutions.length > 0 ? this.stats.fontSubstitutions.join(", ") : "None")); lines.push("-----------------------------------------------------------------"); lines.push("PAGE-BY-PAGE AUDIT LOG:"); for (var p = 0; p < this.pageAudits.length; p++) { var pa = this.pageAudits[p]; lines.push(" Page " + pa.pageNumber + ": [" + pa.status + "]"); lines.push(" - Note: " + pa.reason); lines.push(" - Frames: " + pa.frameCount + " | Characters: " + pa.charCount); } lines.push("-----------------------------------------------------------------"); if (this.warnings.length > 0) { lines.push("WARNINGS & LIMITATIONS ENCOUNTERED (" + this.warnings.length + "):"); for (var w = 0; w < this.warnings.length; w++) { lines.push(" * " + this.warnings[w]); } lines.push("-----------------------------------------------------------------"); } if (this.errors.length > 0) { lines.push("ERRORS (" + this.errors.length + "):"); for (var e = 0; e < this.errors.length; e++) { lines.push(" ! " + this.errors[e]); } lines.push("-----------------------------------------------------------------"); } lines.push("EXECUTION TRACE LOG:"); for (var l = 0; l < this.entries.length; l++) { lines.push(this.entries[l]); } lines.push("================================================================="); return lines.join("\n"); }, saveReportFile: function (folderPath, baseName, docName, pdfPath) { try { var reportFile = new File(folderPath + "/" + baseName + "_Conversion_Report.txt"); reportFile.encoding = "UTF-8"; reportFile.open("w"); reportFile.write(this.generateReportText(docName, pdfPath)); reportFile.close(); return reportFile; } catch (ex) { this.error("Failed to write report file: " + ex.message); return null; } } }; // ------------------------------------------------------------------------ // MODULE 4: PURE EXTENDSCRIPT RFC 1951 DEFLATE/INFLATE DECOMPRESSOR // ------------------------------------------------------------------------ var KeterInflate = (function () { var BTYPE_NONE = 0, BTYPE_FIXED = 1, BTYPE_DYNAMIC = 2; var FIXED_LIT_LENS = (function () { var a = new Array(288); var i; for (i = 0; i <= 143; i++) a[i] = 8; for (i = 144; i <= 255; i++) a[i] = 9; for (i = 256; i <= 279; i++) a[i] = 7; for (i = 280; i <= 287; i++) a[i] = 8; return a; })(); var FIXED_DIST_LENS = (function () { var a = new Array(32); for (var i = 0; i < 32; i++) a[i] = 5; return a; })(); var LENGTH_CODES = [ 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5, 0 ]; var LENGTH_BASES = [ 3, 4, 5, 6, 7, 8, 9, 10, 11, 13, 15, 17, 19, 23, 27, 31, 35, 43, 51, 59, 67, 83, 99, 115, 131, 163, 195, 227, 258 ]; var DIST_CODES = [ 0, 0, 0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7, 8, 8, 9, 9, 10, 10, 11, 11, 12, 12, 13, 13 ]; var DIST_BASES = [ 1, 2, 3, 4, 5, 7, 9, 13, 17, 25, 33, 49, 65, 97, 129, 193, 257, 385, 513, 769, 1025, 1537, 2049, 3073, 4097, 6145, 8193, 12289, 16385, 24577 ]; var CL_ORDER = [16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15]; function HuffmanTree(lens) { this.maxBits = 0; this.table = {}; var bl_count = {}; var i, len; for (i = 0; i < lens.length; i++) { len = lens[i]; if (len > 0) { bl_count[len] = (bl_count[len] || 0) + 1; if (len > this.maxBits) this.maxBits = len; } } var code = 0; var next_code = {}; for (var bits = 1; bits <= this.maxBits; bits++) { code = (code + (bl_count[bits - 1] || 0)) << 1; next_code[bits] = code; } for (i = 0; i < lens.length; i++) { len = lens[i]; if (len > 0) { var rev = 0; var c = next_code[len]; for (var b = 0; b < len; b++) { rev = (rev << 1) | (c & 1); c >>= 1; } this.table[(len << 16) | rev] = i; next_code[len]++; } } } HuffmanTree.prototype.decode = function (reader) { var bits = 0, code = 0; while (bits < this.maxBits) { var bit = reader.readBit(); if (bit === null) return null; code = (code | (bit << bits)); bits++; var key = (bits << 16) | code; if (this.table.hasOwnProperty(key)) { return this.table[key]; } } return null; }; function BitReader(byteArray, startOffset) { this.data = byteArray; this.pos = startOffset || 0; this.bitBuf = 0; this.bitsInBuf = 0; } BitReader.prototype.readBit = function () { if (this.bitsInBuf === 0) { if (this.pos >= this.data.length) return null; this.bitBuf = this.data[this.pos++]; this.bitsInBuf = 8; } var bit = this.bitBuf & 1; this.bitBuf >>= 1; this.bitsInBuf--; return bit; }; BitReader.prototype.readBits = function (n) { var val = 0; for (var i = 0; i < n; i++) { var b = this.readBit(); if (b === null) return null; val |= (b << i); } return val; }; BitReader.prototype.byteAlign = function () { this.bitBuf = 0; this.bitsInBuf = 0; }; return { decompress: function (byteArray) { if (!byteArray || byteArray.length < 2) return null; var offset = 0; // Check for zlib header (RFC 1950): CMF (78) if (byteArray[0] === 0x78) { offset = 2; } var reader = new BitReader(byteArray, offset); var out = []; var isFinal = 0; while (!isFinal) { var bFinal = reader.readBit(); if (bFinal === null) break; isFinal = bFinal; var bType = reader.readBits(2); if (bType === null) break; if (bType === BTYPE_NONE) { reader.byteAlign(); var len = reader.data[reader.pos] | (reader.data[reader.pos + 1] << 8); reader.pos += 4; for (var i = 0; i < len && reader.pos < reader.data.length; i++) { out.push(reader.data[reader.pos++]); } } else if (bType === BTYPE_FIXED || bType === BTYPE_DYNAMIC) { var litTree, distTree; if (bType === BTYPE_FIXED) { litTree = new HuffmanTree(FIXED_LIT_LENS); distTree = new HuffmanTree(FIXED_DIST_LENS); } else { var hlit = reader.readBits(5) + 257; var hdist = reader.readBits(5) + 1; var hclen = reader.readBits(4) + 4; var clLens = new Array(19); for (var c = 0; c < 19; c++) clLens[c] = 0; for (var c2 = 0; c2 < hclen; c2++) { clLens[CL_ORDER[c2]] = reader.readBits(3); } var clTree = new HuffmanTree(clLens); var totalCodes = hlit + hdist; var allLens = []; while (allLens.length < totalCodes) { var sym = clTree.decode(reader); if (sym === null) break; if (sym < 16) { allLens.push(sym); } else if (sym === 16) { var repCount = reader.readBits(2) + 3; var prevVal = allLens.length > 0 ? allLens[allLens.length - 1] : 0; for (var r1 = 0; r1 < repCount; r1++) allLens.push(prevVal); } else if (sym === 17) { var r0 = reader.readBits(3) + 3; for (var r2 = 0; r2 < r0; r2++) allLens.push(0); } else if (sym === 18) { var r00 = reader.readBits(7) + 11; for (var r3 = 0; r3 < r00; r3++) allLens.push(0); } } litTree = new HuffmanTree(allLens.slice(0, hlit)); distTree = new HuffmanTree(allLens.slice(hlit)); } while (true) { var val = litTree.decode(reader); if (val === null || val === 256) break; if (val < 256) { out.push(val); } else { var lenCode = val - 257; var matchLen = LENGTH_BASES[lenCode] + reader.readBits(LENGTH_CODES[lenCode]); var distCode = distTree.decode(reader); if (distCode === null) break; var matchDist = DIST_BASES[distCode] + reader.readBits(DIST_CODES[distCode]); var start = out.length - matchDist; for (var m = 0; m < matchLen; m++) { out.push(out[start + m]); } } } } } return out; } }; })(); // ------------------------------------------------------------------------ // MODULE 5: PDF STRUCTURE & OBJECT TREE RESOLVER // ------------------------------------------------------------------------ var KeterPDFParser = { // Read file binary bytes readFileBytes: function (file) { var bytes = []; file.encoding = "BINARY"; file.open("r"); var str = file.read(); file.close(); for (var i = 0; i < str.length; i++) { bytes.push(str.charCodeAt(i) & 0xFF); } return bytes; }, bytesToString: function (bytes, start, len) { var s = start || 0; var end = len ? Math.min(bytes.length, s + len) : bytes.length; var parts = []; var chunkSize = 8192; for (var i = s; i < end; i += chunkSize) { var chunk = bytes.slice(i, Math.min(i + chunkSize, end)); parts.push(String.fromCharCode.apply(null, chunk)); } return parts.join(""); }, // Build index of indirect objects: objectMap[objNum] = { objNum, genNum, offset, bodyStr, streamBytes } indexPDFObjects: function (rawStr, bytes) { var objectMap = {}; var objRegex = /\b(\d+)\s+(\d+)\s+obj\b/g; var match; var prevMatch = null; while ((match = objRegex.exec(rawStr)) !== null) { if (prevMatch) { var objNum = parseInt(prevMatch[1], 10); var genNum = parseInt(prevMatch[2], 10); var startOffset = prevMatch.index; var endOffset = match.index; var blockStr = rawStr.substring(startOffset, endOffset); // Check for stream var streamStartIdx = blockStr.indexOf("stream"); var streamEndIdx = blockStr.indexOf("endstream"); var streamSlice = null; if (streamStartIdx !== -1 && streamEndIdx !== -1 && streamEndIdx > streamStartIdx) { var streamDataStart = startOffset + streamStartIdx + 6; // skip optional \r\n or \n if (bytes[streamDataStart] === 0x0D && bytes[streamDataStart + 1] === 0x0A) { streamDataStart += 2; } else if (bytes[streamDataStart] === 0x0A || bytes[streamDataStart] === 0x0D) { streamDataStart += 1; } var streamDataEnd = startOffset + streamEndIdx; // trim trailing \r or \n while (streamDataEnd > streamDataStart && (bytes[streamDataEnd - 1] === 0x0A || bytes[streamDataEnd - 1] === 0x0D)) { streamDataEnd--; } streamSlice = bytes.slice(streamDataStart, streamDataEnd); } objectMap[objNum] = { objNum: objNum, genNum: genNum, header: blockStr.substring(0, streamStartIdx !== -1 ? streamStartIdx : blockStr.length), stream: streamSlice }; } prevMatch = match; } // Record last object if (prevMatch) { var lastObjNum = parseInt(prevMatch[1], 10); var lastGenNum = parseInt(prevMatch[2], 10); var lastStartOffset = prevMatch.index; var lastBlockStr = rawStr.substring(lastStartOffset); var lastStreamStartIdx = lastBlockStr.indexOf("stream"); var lastStreamEndIdx = lastBlockStr.indexOf("endstream"); var lastStreamSlice = null; if (lastStreamStartIdx !== -1 && lastStreamEndIdx !== -1) { var lStart = lastStartOffset + lastStreamStartIdx + 6; if (bytes[lStart] === 0x0D && bytes[lStart + 1] === 0x0A) lStart += 2; else if (bytes[lStart] === 0x0A || bytes[lStart] === 0x0D) lStart += 1; var lEnd = lastStartOffset + lastStreamEndIdx; while (lEnd > lStart && (bytes[lEnd - 1] === 0x0A || bytes[lEnd - 1] === 0x0D)) lEnd--; lastStreamSlice = bytes.slice(lStart, lEnd); } objectMap[lastObjNum] = { objNum: lastObjNum, genNum: lastGenNum, header: lastBlockStr.substring(0, lastStreamStartIdx !== -1 ? lastStreamStartIdx : lastBlockStr.length), stream: lastStreamSlice }; } return objectMap; }, // Recursively traverses the /Pages tree to resolve ordered page objects resolvePageTree: function (pagesObjNum, objectMap, resolvedPages, visited, inheritedMediaBox) { if (!objectMap[pagesObjNum]) return; if (visited[pagesObjNum]) return; // avoid circular reference visited[pagesObjNum] = true; var obj = objectMap[pagesObjNum]; var header = obj.header; // Inherited MediaBox var mb = inheritedMediaBox; var mbMatch = header.match(/\/MediaBox\s*\[\s*([\d\.\-]+)\s+([\d\.\-]+)\s+([\d\.\-]+)\s+([\d\.\-]+)\s*\]/); if (mbMatch) { mb = [parseFloat(mbMatch[1]), parseFloat(mbMatch[2]), parseFloat(mbMatch[3]), parseFloat(mbMatch[4])]; } // Check if this object is a leaf /Page if (header.indexOf("/Type /Page") !== -1 || header.indexOf("/Type/Page") !== -1) { resolvedPages.push({ pageObjNum: pagesObjNum, mediaBox: mb || [0, 0, 595.28, 841.89], header: header }); return; } // If it is an intermediate /Pages node, parse /Kids [ ... ] var kidsMatch = header.match(/\/Kids\s*\[([\s\S]*?)\]/); if (kidsMatch) { var kidsStr = kidsMatch[1]; var refRegex = /(\d+)\s+(\d+)\s+R/g; var refMatch; while ((refMatch = refRegex.exec(kidsStr)) !== null) { var childObjNum = parseInt(refMatch[1], 10); KeterPDFParser.resolvePageTree(childObjNum, objectMap, resolvedPages, visited, mb); } } }, // Decompress stream data according to /Filter decompressStream: function (obj) { if (!obj || !obj.stream || obj.stream.length === 0) return ""; if (obj.header.indexOf("/FlateDecode") !== -1) { try { var uncompressedBytes = KeterInflate.decompress(obj.stream); if (uncompressedBytes && uncompressedBytes.length > 0) { return KeterPDFParser.bytesToString(uncompressedBytes); } } catch (e) { KeterLogger.warn("Flate stream decompression failed on object " + obj.objNum + ": " + e.message); } } return KeterPDFParser.bytesToString(obj.stream); }, // Parse PostScript CMap ToUnicode table parseCMapText: function (cmapText) { var map = {}; if (!cmapText) return map; // 1. beginbfchar ... endbfchar var bfCharMatches = cmapText.match(/\d+\s+beginbfchar([\s\S]*?)endbfchar/g); if (bfCharMatches) { for (var i = 0; i < bfCharMatches.length; i++) { var lines = bfCharMatches[i].split(/\r?\n/); for (var l = 0; l < lines.length; l++) { var pair = lines[l].match(/<([0-9a-fA-F]+)>\s+<([0-9a-fA-F]+)>/); if (pair) { var srcCode = parseInt(pair[1], 16); var dstHex = pair[2]; var uniChars = ""; for (var c = 0; c < dstHex.length; c += 4) { uniChars += String.fromCharCode(parseInt(dstHex.substr(c, 4), 16)); } map[srcCode] = uniChars; } } } } // 2. beginbfrange ... endbfrange var bfRangeMatches = cmapText.match(/\d+\s+beginbfrange([\s\S]*?)endbfrange/g); if (bfRangeMatches) { for (var r = 0; r < bfRangeMatches.length; r++) { var rLines = bfRangeMatches[r].split(/\r?\n/); for (var rl = 0; rl < rLines.length; rl++) { var range1 = rLines[rl].match(/<([0-9a-fA-F]+)>\s+<([0-9a-fA-F]+)>\s+<([0-9a-fA-F]+)>/); if (range1) { var startC = parseInt(range1[1], 16); var endC = parseInt(range1[2], 16); var startDst = parseInt(range1[3], 16); for (var code = startC; code <= endC; code++) { map[code] = String.fromCharCode(startDst + (code - startC)); } } } } } return map; }, // Resolve Page Fonts, ToUnicode CMaps and Widths specifically for Page N resolvePageFonts: function (pageHeader, objectMap) { var fontMap = {}; // fontTag -> { baseFont, cMap, widths, firstChar, lastChar, isToUnicodeValid } // Locate /Resources var resHeader = pageHeader; var resRefMatch = pageHeader.match(/\/Resources\s+(\d+)\s+(\d+)\s+R/); if (resRefMatch) { var resObjNum = parseInt(resRefMatch[1], 10); if (objectMap[resObjNum]) { resHeader = objectMap[resObjNum].header; } } // Find /Font << ... >> var fontBlockMatch = resHeader.match(/\/Font\s*<<([\s\S]*?)>>/); if (!fontBlockMatch) { var fontRefMatch = resHeader.match(/\/Font\s+(\d+)\s+(\d+)\s+R/); if (fontRefMatch && objectMap[parseInt(fontRefMatch[1], 10)]) { fontBlockMatch = objectMap[parseInt(fontRefMatch[1], 10)].header.match(/<<([\s\S]*?)>>/); } } if (fontBlockMatch) { var fontDictContent = fontBlockMatch[1]; var fontEntryRegex = /\/(\S+)\s+(\d+)\s+(\d+)\s+R/g; var fontEntry; while ((fontEntry = fontEntryRegex.exec(fontDictContent)) !== null) { var fontTag = fontEntry[1]; var fObjNum = parseInt(fontEntry[2], 10); if (objectMap[fObjNum]) { var fHeader = objectMap[fObjNum].header; var baseFontMatch = fHeader.match(/\/BaseFont\s*\/([^\s\/>]+)/); var baseFont = baseFontMatch ? baseFontMatch[1] : fontTag; // Check /ToUnicode var toUniMatch = fHeader.match(/\/ToUnicode\s+(\d+)\s+(\d+)\s+R/); var cMap = {}; var hasToUnicode = false; if (toUniMatch) { var toUniObjNum = parseInt(toUniMatch[1], 10); if (objectMap[toUniObjNum]) { var cmapStreamText = KeterPDFParser.decompressStream(objectMap[toUniObjNum]); cMap = KeterPDFParser.parseCMapText(cmapStreamText); hasToUnicode = KeterUtils.getKeyCount(cMap) > 0; } } // Check /Widths var widths = []; var firstChar = 0, lastChar = 255; var fcMatch = fHeader.match(/\/FirstChar\s+(\d+)/); var lcMatch = fHeader.match(/\/LastChar\s+(\d+)/); if (fcMatch) firstChar = parseInt(fcMatch[1], 10); if (lcMatch) lastChar = parseInt(lcMatch[1], 10); var wArrayMatch = fHeader.match(/\/Widths\s*\[([\s\S]*?)\]/); if (!wArrayMatch) { var wRefMatch = fHeader.match(/\/Widths\s+(\d+)\s+(\d+)\s+R/); if (wRefMatch && objectMap[parseInt(wRefMatch[1], 10)]) { wArrayMatch = objectMap[parseInt(wRefMatch[1], 10)].header.match(/\[([\s\S]*?)\]/); } } if (wArrayMatch) { var wParts = wArrayMatch[1].split(/\s+/); for (var wp = 0; wp < wParts.length; wp++) { if (wParts[wp] !== "") { widths.push(parseFloat(wParts[wp])); } } } fontMap[fontTag] = { baseFont: baseFont, cMap: cMap, hasToUnicode: hasToUnicode, widths: widths, firstChar: firstChar, lastChar: lastChar }; } } } return fontMap; }, // Decode PDF string using the specific font's CMap decodePDFString: function (str, fontInfo, isHex) { if (!str) return ""; var cMap = (fontInfo && fontInfo.cMap) ? fontInfo.cMap : null; var res = ""; if (isHex) { for (var i = 0; i < str.length; i += 4) { var hexCode = parseInt(str.substr(i, 4), 16); if (cMap && cMap[hexCode]) { res += cMap[hexCode]; } else if (hexCode >= 0x0590 && hexCode <= 0x05FF) { res += String.fromCharCode(hexCode); } else { var b1 = (hexCode >> 8) & 0xFF; var b2 = hexCode & 0xFF; if (b1 > 0) res += String.fromCharCode(b1); if (b2 > 0) res += String.fromCharCode(b2); } } } else { var unescaped = str .replace(/\\n/g, "\n") .replace(/\\r/g, "\r") .replace(/\\t/g, "\t") .replace(/\\\(/g, "(") .replace(/\\\)/g, ")") .replace(/\\\\/g, "\\"); for (var j = 0; j < unescaped.length; j++) { var chCode = unescaped.charCodeAt(j); if (cMap && cMap[chCode]) { res += cMap[chCode]; } else { res += unescaped.charAt(j); } } } return res; }, // Calculate string width using font /Widths metrics when available calculateSpanWidth: function (textStr, fontSize, fontInfo) { if (fontInfo && fontInfo.widths && fontInfo.widths.length > 0) { var totalPt = 0; for (var i = 0; i < textStr.length; i++) { var code = textStr.charCodeAt(i); var idx = code - fontInfo.firstChar; if (idx >= 0 && idx < fontInfo.widths.length) { totalPt += (fontInfo.widths[idx] / 1000.0) * fontSize; } else { totalPt += fontSize * 0.55; // measured fallback } } return { widthPt: totalPt, isMeasured: true }; } // If no /Widths table exists in font descriptor, return flag for uncertain geometry return { widthPt: textStr.length * (fontSize * 0.55), isMeasured: false }; }, // Extract positioned text spans from decompressed content stream of a single page extractPageTextSpans: function (contentStreamText, pageFonts, pageHeightPt) { var spans = []; var textBlockRegex = /BT[\s\S]*?ET/g; var blockMatch; while ((blockMatch = textBlockRegex.exec(contentStreamText)) !== null) { var block = blockMatch[0]; var curFontTag = "Default"; var curSize = 10; var curX = 0, curY = 0; var leading = 12; var ops = block.split(/\r?\n/); for (var i = 0; i < ops.length; i++) { var line = ops[i]; // Font selection: /F1 12 Tf var tfMatch = line.match(/\/(\S+)\s+([\d\.\-]+)\s+Tf/); if (tfMatch) { curFontTag = tfMatch[1]; curSize = parseFloat(tfMatch[2]); continue; } // Text matrix: [a b c d e f] Tm var tmMatch = line.match(/([\d\.\-]+)\s+([\d\.\-]+)\s+([\d\.\-]+)\s+([\d\.\-]+)\s+([\d\.\-]+)\s+([\d\.\-]+)\s+Tm/); if (tmMatch) { curX = parseFloat(tmMatch[5]); curY = parseFloat(tmMatch[6]); continue; } // Text position: x y Td or TD var tdMatch = line.match(/([\d\.\-]+)\s+([\d\.\-]+)\s+T[dD]/); if (tdMatch) { curX += parseFloat(tdMatch[1]); curY += parseFloat(tdMatch[2]); continue; } // Set leading: TL var tlMatch = line.match(/([\d\.\-]+)\s+TL/); if (tlMatch) { leading = parseFloat(tlMatch[1]); continue; } // Move to next line: T* if (line.match(/T\*/)) { curY -= leading; continue; } // Show text: (string) Tj or <hex> Tj var tjMatch = line.match(/\(([\s\S]*?)\)\s*Tj/) || line.match(/<([0-9a-fA-F]+)>\s*Tj/); if (tjMatch) { var rawText = tjMatch[1]; var fontInfo = pageFonts[curFontTag]; var decoded = KeterPDFParser.decodePDFString(rawText, fontInfo, line.indexOf("<") !== -1); if (decoded && decoded.length > 0) { var wResult = KeterPDFParser.calculateSpanWidth(decoded, curSize, fontInfo); var idTop = pageHeightPt - curY - curSize; spans.push({ text: decoded, x: curX, y: idTop, fontSize: curSize, fontTag: curFontTag, baseFont: fontInfo ? fontInfo.baseFont : "Unknown", width: wResult.widthPt, isMeasuredWidth: wResult.isMeasured, height: curSize * 1.2 }); curX += wResult.widthPt; } continue; } // Show text array: [ (str) 20 (str) ] TJ var bigTjMatch = line.match(/\[([\s\S]*?)\]\s*TJ/); if (bigTjMatch) { var arrayContent = bigTjMatch[1]; var fontInfo2 = pageFonts[curFontTag]; var elemRegex = /\(([\s\S]*?)\)|<([0-9a-fA-F]+)>|([\d\.\-]+)/g; var elemMatch; var chunkText = ""; var chunkWidth = 0; while ((elemMatch = elemRegex.exec(arrayContent)) !== null) { if (elemMatch[1] || elemMatch[2]) { var itemRaw = elemMatch[1] || elemMatch[2]; var isHex = !!elemMatch[2]; var dStr = KeterPDFParser.decodePDFString(itemRaw, fontInfo2, isHex); chunkText += dStr; var wSub = KeterPDFParser.calculateSpanWidth(dStr, curSize, fontInfo2); chunkWidth += wSub.widthPt; } else if (elemMatch[3]) { // Kerning adjustment in 1/1000 text units var kernVal = parseFloat(elemMatch[3]); chunkWidth -= (kernVal / 1000.0) * curSize; } } if (chunkText && chunkText.length > 0) { var idTop2 = pageHeightPt - curY - curSize; spans.push({ text: chunkText, x: curX, y: idTop2, fontSize: curSize, fontTag: curFontTag, baseFont: fontInfo2 ? fontInfo2.baseFont : "Unknown", width: chunkWidth, isMeasuredWidth: !!(fontInfo2 && fontInfo2.widths && fontInfo2.widths.length > 0), height: curSize * 1.2 }); curX += chunkWidth; } } } } return spans; } }; // ------------------------------------------------------------------------ // MODULE 6: SPATIAL LAYOUT & PHYSICAL GUTTER ANALYSIS (NO ARBITRARY %) // ------------------------------------------------------------------------ var KeterLayoutEngine = { // Groups raw text spans into lines and detects physical column gutters analyzeLayout: function (spans, pageWidthPt, pageHeightPt) { if (!spans || spans.length === 0) return []; // 1. Sort spans: Top-to-Bottom, then Right-to-Left (for Hebrew) spans.sort(function (a, b) { var yDiff = a.y - b.y; if (Math.abs(yDiff) > 3.5) { return yDiff; } return b.x - a.x; // Right-to-Left }); // 2. Assemble into visual lines var lines = []; var curLine = null; for (var i = 0; i < spans.length; i++) { var s = spans[i]; if (!curLine || Math.abs(s.y - curLine.y) > (s.fontSize * 0.45)) { curLine = { y: s.y, fontSize: s.fontSize, minX: s.x, maxX: s.x + s.width, spans: [s], text: s.text }; lines.push(curLine); } else { curLine.spans.push(s); curLine.minX = Math.min(curLine.minX, s.x); curLine.maxX = Math.max(curLine.maxX, s.x + s.width); curLine.text += " " + s.text; } } // 3. Detect Headers & Footers based on vertical margins var headerLines = []; var footerLines = []; var candidateBodyLines = []; var topMarginBoundary = pageHeightPt * 0.10; var bottomMarginBoundary = pageHeightPt * 0.90; for (var l = 0; l < lines.length; l++) { var line = lines[l]; if (line.y < topMarginBoundary && line.fontSize <= 12) { headerLines.push(line); } else if (line.y > bottomMarginBoundary && line.fontSize <= 11) { footerLines.push(line); } else { candidateBodyLines.push(line); } } var regions = []; if (headerLines.length > 0) { regions.push(this.buildRegion(headerLines, "RunningHeader", "Keter_RunningHeader")); } // 4. Physical Gutter Analysis for Body Columns (not fixed arbitrary percentages) var columnRegions = this.detectPhysicalColumns(candidateBodyLines, pageWidthPt); for (var c = 0; c < columnRegions.length; c++) { regions.push(columnRegions[c]); } if (footerLines.length > 0) { regions.push(this.buildRegion(footerLines, "PageFooter", "Keter_RunningHeader")); } return regions; }, // Analyzes horizontal gaps between lines to detect true columns detectPhysicalColumns: function (bodyLines, pageWidthPt) { if (!bodyLines || bodyLines.length === 0) return []; // Calculate horizontal span frequencies var leftColumnLines = []; var rightColumnLines = []; var fullWidthLines = []; var midX = pageWidthPt / 2.0; var minGutterWidth = 14.0; // at least 14pt gutter between columns var hasLeft = false; var hasRight = false; for (var i = 0; i < bodyLines.length; i++) { var bl = bodyLines[i]; var width = bl.maxX - bl.minX; // If a line spans more than 70% of the page, it is a single-column or banner if (width > pageWidthPt * 0.70) { fullWidthLines.push(bl); } else if (bl.minX > (midX - 10)) { // Right column (RTL start) rightColumnLines.push(bl); hasRight = true; } else if (bl.maxX < (midX + 10)) { // Left column leftColumnLines.push(bl); hasLeft = true; } else { fullWidthLines.push(bl); } } var resultRegions = []; // If genuine dual columns are detected if (hasLeft && hasRight && (leftColumnLines.length + rightColumnLines.length > fullWidthLines.length)) { KeterLogger.log("Physical column gutter detected at X ≈ " + Math.round(midX) + " pt."); // Right-to-Left order: Right column first, then Left column if (rightColumnLines.length > 0) { resultRegions.push(this.buildRegion(rightColumnLines, "Column_Right_RTL", "Keter_Main_Hebrew_RTL")); } if (leftColumnLines.length > 0) { resultRegions.push(this.buildRegion(leftColumnLines, "Column_Left_RTL", "Keter_Main_Hebrew_RTL")); } if (fullWidthLines.length > 0) { resultRegions.push(this.buildRegion(fullWidthLines, "Body_Spanning", "Keter_Main_Hebrew_RTL")); } } else { // Single unified text stream resultRegions.push(this.buildRegion(bodyLines, "BodyText", "Keter_Main_Hebrew_RTL")); } return resultRegions; }, buildRegion: function (linesList, roleName, styleName) { var minY = 999999, minX = 999999, maxY = 0, maxX = 0; var textParts = []; for (var i = 0; i < linesList.length; i++) { var l = linesList[i]; minY = Math.min(minY, l.y); minX = Math.min(minX, l.minX); maxY = Math.max(maxY, l.y + (l.fontSize * 1.25)); maxX = Math.max(maxX, l.maxX); textParts.push(l.text); } return { role: roleName, styleName: styleName, bounds: [minY, minX, maxY, maxX], content: textParts.join("\r") }; } }; // ------------------------------------------------------------------------ // MODULE 7: INDESIGN DOM GENERATOR (VALIDATED API ACCESS) // ------------------------------------------------------------------------ var KeterInDesignDOM = { setupDocument: function (templateFile, pageWidthPt, pageHeightPt) { var doc; if (templateFile && templateFile.exists) { KeterLogger.log("Opening existing template: " + templateFile.fsName); doc = app.open(templateFile); } else { KeterLogger.log("Creating new InDesign document: " + Math.round(pageWidthPt) + " x " + Math.round(pageHeightPt) + " pt"); doc = app.documents.add(); doc.documentPreferences.pageWidth = pageWidthPt + "pt"; doc.documentPreferences.pageHeight = pageHeightPt + "pt"; doc.documentPreferences.facingPages = true; } // CRITICAL FIX: Explicitly lock document measurement units to Points // to prevent coordinate scaling errors when user InDesign uses mm or picas try { doc.viewPreferences.horizontalMeasurementUnits = MeasurementUnits.POINTS; doc.viewPreferences.verticalMeasurementUnits = MeasurementUnits.POINTS; doc.viewPreferences.rulerOrigin = RulerOrigin.PAGE_ORIGIN; } catch (eUnits) { KeterLogger.warn("Measurement unit assignment warning: " + eUnits.message); } this.ensureLayers(doc); this.ensureStyles(doc); return doc; }, ensureLayers: function (doc) { // PDF Reference Underlay (locked, bottom) var underlayLayer = doc.layers.itemByName(KeterConfig.layerUnderlay); if (!underlayLayer.isValid) { // UIColors safety check: never reference bare UIColors underlayLayer = doc.layers.add({ name: KeterConfig.layerUnderlay }); } underlayLayer.move(LocationOptions.AT_END); // Reconstructed Editable Text (active, top) var textLayer = doc.layers.itemByName(KeterConfig.layerText); if (!textLayer.isValid) { textLayer = doc.layers.add({ name: KeterConfig.layerText }); } doc.activeLayer = textLayer; }, ensureStyles: function (doc) { var bestHebrewFont = this.findBestAvailableFont(KeterConfig.hebrewFontFallbacks); KeterLogger.log("Selected primary Hebrew font: " + bestHebrewFont); this.createOrUpdateParaStyle(doc, "Keter_Main_Hebrew_RTL", { appliedFont: bestHebrewFont, pointSize: 12, leading: 15.5, justification: Justification.FULLY_JUSTIFIED, isRTL: true }); this.createOrUpdateParaStyle(doc, "Keter_RunningHeader", { appliedFont: bestHebrewFont, pointSize: 9, leading: 11, justification: Justification.CENTER_ALIGN, isRTL: true }); }, findBestAvailableFont: function (candidates) { for (var i = 0; i < candidates.length; i++) { var candidate = candidates[i]; try { var f = app.fonts.itemByName(candidate); if (f && f.isValid) { return candidate; } } catch (e) {} } KeterLogger.stats.fontSubstitutions.push("Times New Roman (Hebrew fallback)"); return "Times New Roman"; }, createOrUpdateParaStyle: function (doc, styleName, props) { var style = doc.paragraphStyles.itemByName(styleName); if (!style.isValid) { style = doc.paragraphStyles.add({ name: styleName }); } try { if (props.appliedFont) style.appliedFont = props.appliedFont; if (props.pointSize) style.pointSize = props.pointSize; if (props.leading) style.leading = props.leading; if (props.justification) style.justification = props.justification; // Enforce Adobe World-Ready Paragraph Composer try { style.composer = "Adobe World-Ready Paragraph Composer"; } catch (e1) { try { style.composer = "$ID/Adobe World-Ready Paragraph Composer"; } catch (e2) {} } // Enforce Right-to-Left Direction if (props.isRTL) { try { style.paragraphDirection = ParagraphDirectionOptions.RIGHT_TO_LEFT_DIRECTION; } catch (e3) { try { style.paragraphDirection = 1278366308; } catch (e4) {} } } } catch (ex) { KeterLogger.warn("Style configuration note for '" + styleName + "': " + ex.message); } return style; }, placePDFUnderlay: function (doc, page, pdfFile, pageNum, opacity) { try { var underlayLayer = doc.layers.itemByName(KeterConfig.layerUnderlay); underlayLayer.locked = false; doc.activeLayer = underlayLayer; try { app.pdfPlacePreferences.pdfCrop = PDFCrop.CROP_MEDIA; } catch (eCrop) { try { app.pdfPlacePreferences.pdfCrop = PDFCrop.cropMedia; } catch (eCrop2) {} } app.pdfPlacePreferences.pageNumber = pageNum; app.pdfPlacePreferences.transparentBackground = true; var placed = page.place(pdfFile, [0, 0])[0]; if (placed && placed.isValid && opacity && opacity < 100) { try { placed.transparencySettings.blendingSettings.opacity = opacity; } catch (eOp) {} } underlayLayer.locked = true; doc.activeLayer = doc.layers.itemByName(KeterConfig.layerText); KeterLogger.log("Placed 1:1 PDF underlay reference for page " + pageNum); return true; } catch (ex) { KeterLogger.error("Failed to place PDF underlay for page " + pageNum + ": " + ex.message); return false; } }, createTextFrame: function (doc, page, bounds, content, styleName) { try { var textLayer = doc.layers.itemByName(KeterConfig.layerText); doc.activeLayer = textLayer; var top = Math.max(0, bounds[0]); var left = Math.max(0, bounds[1]); var bottom = Math.max(top + 16, bounds[2]); var right = Math.max(left + 24, bounds[3]); var frame = page.textFrames.add(textLayer, LocationOptions.UNKNOWN_VALUE, { geometricBounds: [top, left, bottom, right] }); // Apply World-Ready Composer directly to frame parent story try { frame.parentStory.composer = "Adobe World-Ready Paragraph Composer"; frame.parentStory.paragraphDirection = ParagraphDirectionOptions.RIGHT_TO_LEFT_DIRECTION; } catch (eWRC) {} // Assign Unicode text contents frame.contents = content; var pStyle = doc.paragraphStyles.itemByName(styleName); if (pStyle.isValid) { frame.parentStory.texts.item(0).applyParagraphStyle(pStyle, true); } KeterLogger.stats.textFramesCreated++; KeterLogger.stats.charactersExtracted += content.length; if (frame.overflows) { KeterLogger.stats.oversetFrames++; KeterLogger.warn("Overset text in frame on page " + page.name + " (" + styleName + "). Frame resized slightly or leading adjustment required."); } return frame; } catch (ex) { KeterLogger.error("Error creating InDesign text frame: " + ex.message); return null; } } }; // ------------------------------------------------------------------------ // MODULE 8: USER INTERFACE (SCRIPTUI DIALOG & PROGRESS WINDOW) // ------------------------------------------------------------------------ var KeterUI = { showMainDialog: function () { var w = new Window("dialog", KeterConfig.name + " v" + KeterConfig.version); w.orientation = "column"; w.alignChildren = ["fill", "top"]; w.spacing = 10; w.margins = 16; var headerPnl = w.add("panel", undefined, undefined, { borderStyle: "black" }); headerPnl.orientation = "column"; headerPnl.alignChildren = ["fill", "top"]; headerPnl.margins = 10; var titleTxt = headerPnl.add("statictext", undefined, "KETER PDF-TO-INDESIGN CONVERSION ENGINE"); titleTxt.graphics.font = ScriptUI.newFont(w.graphics.font.name, ScriptUI.FontStyle.BOLD, 14); headerPnl.add("statictext", undefined, "Audited Production Engine: Strict Truth-in-Extraction Architecture"); // SOURCE PDF var srcPnl = w.add("panel", undefined, "Source PDF & Options"); srcPnl.orientation = "column"; srcPnl.alignChildren = ["fill", "top"]; srcPnl.margins = 12; srcPnl.spacing = 8; var pdfRow = srcPnl.add("group"); pdfRow.orientation = "row"; var pdfPathInput = pdfRow.add("edittext", undefined, "", { readonly: true }); pdfPathInput.characters = 42; var browsePdfBtn = pdfRow.add("button", undefined, "Browse PDF..."); var pdfInfoTxt = srcPnl.add("statictext", undefined, "Select a PDF to inspect structure and content streams."); var optGroup = srcPnl.add("group"); optGroup.orientation = "column"; optGroup.alignChildren = ["left", "top"]; optGroup.spacing = 4; var underlayCb = optGroup.add("checkbox", undefined, "Place original PDF page as locked 1:1 visual reference underlay"); underlayCb.value = true; var opacityRow = optGroup.add("group"); opacityRow.orientation = "row"; opacityRow.add("statictext", undefined, "Underlay Opacity:"); var opacityDropdown = opacityRow.add("dropdownlist", undefined, ["25%", "40% (Recommended)", "60%", "100% (Solid)"]); opacityDropdown.selection = 1; var pageRangeRow = srcPnl.add("group"); pageRangeRow.orientation = "row"; pageRangeRow.add("statictext", undefined, "Page Range to Process:"); var pageRangeInput = pageRangeRow.add("edittext", undefined, "All"); pageRangeInput.characters = 12; pageRangeRow.add("statictext", undefined, "(e.g., 'All', '1-5')"); var btnRow = w.add("group"); btnRow.orientation = "row"; btnRow.alignment = "right"; var cancelBtn = btnRow.add("button", undefined, "Cancel", { name: "cancel" }); var convertBtn = btnRow.add("button", undefined, "Start Conversion", { name: "ok" }); convertBtn.enabled = false; var selectedFile = null; browsePdfBtn.onClick = function () { var f = File.openDialog("Select Source PDF File", "PDF Files:*.pdf;All Files:*.*"); if (f) { selectedFile = f; pdfPathInput.text = f.fsName; pdfInfoTxt.text = "Inspecting PDF: " + f.name + "..."; convertBtn.enabled = true; } }; if (w.show() === 1) { var opacityVal = 40; if (opacityDropdown.selection.index === 0) opacityVal = 25; if (opacityDropdown.selection.index === 1) opacityVal = 40; if (opacityDropdown.selection.index === 2) opacityVal = 60; if (opacityDropdown.selection.index === 3) opacityVal = 100; return { pdfFile: selectedFile, placeUnderlay: underlayCb.value, underlayOpacity: opacityVal, pageRange: pageRangeInput.text }; } return null; }, showProgress: function (title, maxVal) { var pw = new Window("palette", title); pw.orientation = "column"; pw.alignChildren = ["fill", "top"]; pw.margins = 16; pw.spacing = 10; var statusTxt = pw.add("statictext", undefined, "Initializing conversion pipeline..."); statusTxt.characters = 45; var pBar = pw.add("progressbar", undefined, 0, maxVal); pBar.preferredSize.width = 320; pw.center(); pw.show(); return { update: function (val, text) { pBar.value = val; if (text) statusTxt.text = text; pw.update(); }, close: function () { pw.close(); } }; } }; // ------------------------------------------------------------------------ // MODULE 9: CONVERSION PIPELINE (STRICT & HONEST EXTRACTION) // ------------------------------------------------------------------------ function parsePageRange(rangeStr, totalPages) { var pages = []; if (!rangeStr || rangeStr.toLowerCase() === "all") { for (var i = 1; i <= totalPages; i++) pages.push(i); return pages; } var parts = rangeStr.split(","); for (var p = 0; p < parts.length; p++) { var part = KeterUtils.trim(parts[p]); if (part.indexOf("-") !== -1) { var range = part.split("-"); var start = parseInt(range[0], 10); var end = parseInt(range[1], 10); if (!isNaN(start) && !isNaN(end)) { for (var r = start; r <= Math.min(end, totalPages); r++) { pages.push(r); } } } else { var single = parseInt(part, 10); if (!isNaN(single) && single >= 1 && single <= totalPages) { pages.push(single); } } } return pages.length > 0 ? pages : [1]; } function runConversion(options) { KeterLogger.log("Starting Keter conversion: " + options.pdfFile.fsName); // 1. Read binary bytes and raw string var bytes = KeterPDFParser.readFileBytes(options.pdfFile); var rawStr = KeterPDFParser.bytesToString(bytes); // 2. Index PDF objects var objectMap = KeterPDFParser.indexPDFObjects(rawStr, bytes); var totalObjects = KeterUtils.getKeyCount(objectMap); KeterLogger.log("Indexed " + totalObjects + " indirect PDF objects."); // 3. Locate Root Catalog & Pages Tree var catalogObjNum = null; var pagesObjNum = null; var rootMatch = rawStr.match(/\/Root\s+(\d+)\s+(\d+)\s+R/); if (rootMatch) { catalogObjNum = parseInt(rootMatch[1], 10); if (objectMap[catalogObjNum]) { var catHeader = objectMap[catalogObjNum].header; var pagesMatch = catHeader.match(/\/Pages\s+(\d+)\s+(\d+)\s+R/); if (pagesMatch) { pagesObjNum = parseInt(pagesMatch[1], 10); } } } // Fallback: search for /Type /Pages object if (!pagesObjNum) { for (var objId in objectMap) { if (objectMap[objId].header.indexOf("/Type /Pages") !== -1 || objectMap[objId].header.indexOf("/Type/Pages") !== -1) { pagesObjNum = parseInt(objId, 10); break; } } } // 4. Resolve Page Objects through Pages Tree var resolvedPages = []; if (pagesObjNum) { KeterPDFParser.resolvePageTree(pagesObjNum, objectMap, resolvedPages, {}, null); } var totalPages = resolvedPages.length; if (totalPages === 0) { // Scan /Type /Page directly if Pages tree was obfuscated for (var pId in objectMap) { if (objectMap[pId].header.indexOf("/Type /Page") !== -1 || objectMap[pId].header.indexOf("/Type/Page") !== -1) { resolvedPages.push({ pageObjNum: parseInt(pId, 10), mediaBox: [0, 0, 595.28, 841.89], header: objectMap[pId].header }); } } totalPages = resolvedPages.length; } if (totalPages === 0) { totalPages = 1; } KeterLogger.reset(totalPages); KeterLogger.log("Resolved " + totalPages + " pages from PDF object graph."); var targetPages = parsePageRange(options.pageRange, totalPages); var progress = KeterUI.showProgress("Reconstructing InDesign Document", targetPages.length + 2); progress.update(0, "Setting up InDesign document & World-Ready styles..."); // 5. Establish InDesign Document matching initial page geometry var initialMediaBox = (resolvedPages.length > 0 && resolvedPages[0].mediaBox) ? resolvedPages[0].mediaBox : [0, 0, 595.28, 841.89]; var pageWidthPt = Math.abs(initialMediaBox[2] - initialMediaBox[0]); var pageHeightPt = Math.abs(initialMediaBox[3] - initialMediaBox[1]); var doc = KeterInDesignDOM.setupDocument(null, pageWidthPt, pageHeightPt); while (doc.pages.length < targetPages.length) { doc.pages.add(LocationOptions.AT_END); } // 6. Process each selected page for (var pIdx = 0; pIdx < targetPages.length; pIdx++) { var pageNum = targetPages[pIdx]; var idPage = doc.pages[pIdx]; progress.update(pIdx + 1, "Processing Page " + pageNum + " of " + targetPages.length + "..."); // Always place PDF 1:1 visual reference underlay when requested if (options.placeUnderlay) { KeterInDesignDOM.placePDFUnderlay(doc, idPage, options.pdfFile, pageNum, options.underlayOpacity); } var pageMeta = (pageNum - 1 < resolvedPages.length) ? resolvedPages[pageNum - 1] : null; if (!pageMeta) { KeterLogger.recordPageAudit( pageNum, "REFERENCE_UNDERLAY_ONLY", "Page structure could not be resolved from object tree. Visual underlay placed.", 0, 0 ); continue; } // Extract page /Contents object(s) var contentsMatches = []; var cRefRegex = /\/Contents\s+(\d+)\s+(\d+)\s+R/g; var cMatch; while ((cMatch = cRefRegex.exec(pageMeta.header)) !== null) { contentsMatches.push(parseInt(cMatch[1], 10)); } // Also check for /Contents [ 12 0 R 13 0 R ] var cArrayMatch = pageMeta.header.match(/\/Contents\s*\[([\s\S]*?)\]/); if (cArrayMatch) { var cArrRefs = cArrayMatch[1].match(/(\d+)\s+(\d+)\s+R/g); if (cArrRefs) { for (var cr = 0; cr < cArrRefs.length; cr++) { var idNum = parseInt(cArrRefs[cr].split(/\s+/)[0], 10); if (!KeterUtils.arrayContains(contentsMatches, idNum)) { contentsMatches.push(idNum); } } } } // If no contents objects could be located if (contentsMatches.length === 0) { KeterLogger.recordPageAudit( pageNum, "REFERENCE_UNDERLAY_ONLY", "No /Contents stream found (Page may be scanned raster image or use ObjStm). Visual underlay preserved.", 0, 0 ); continue; } // Concatenate all content streams for this page var pageContentText = ""; for (var cIdx = 0; cIdx < contentsMatches.length; cIdx++) { var cObjNum = contentsMatches[cIdx]; if (objectMap[cObjNum]) { pageContentText += KeterPDFParser.decompressStream(objectMap[cObjNum]) + "\n"; } } // Check if content stream has any text operators (BT...ET) if (pageContentText.indexOf("BT") === -1 || pageContentText.indexOf("ET") === -1) { KeterLogger.recordPageAudit( pageNum, "REFERENCE_UNDERLAY_ONLY", "Content stream contains vector/raster artwork only (no text operators BT...ET). Visual underlay preserved.", 0, 0 ); continue; } // Resolve page-specific fonts and ToUnicode CMaps var pageFonts = KeterPDFParser.resolvePageFonts(pageMeta.header, objectMap); // Extract text spans var curPageHeightPt = Math.abs(pageMeta.mediaBox[3] - pageMeta.mediaBox[1]); var curPageWidthPt = Math.abs(pageMeta.mediaBox[2] - pageMeta.mediaBox[0]); var spans = KeterPDFParser.extractPageTextSpans(pageContentText, pageFonts, curPageHeightPt); if (spans.length === 0) { KeterLogger.recordPageAudit( pageNum, "REFERENCE_UNDERLAY_ONLY", "Text operators could not be mapped to Unicode characters (Font encodings / ToUnicode missing). Visual underlay preserved.", 0, 0 ); continue; } // Gutter and layout analysis var regions = KeterLayoutEngine.analyzeLayout(spans, curPageWidthPt, curPageHeightPt); // Create editable InDesign Text Frames var pageCharCount = 0; for (var r = 0; r < regions.length; r++) { var reg = regions[r]; KeterInDesignDOM.createTextFrame(doc, idPage, reg.bounds, reg.content, reg.styleName); pageCharCount += reg.content.length; } KeterLogger.recordPageAudit( pageNum, "EDITABLE_RECONSTRUCTED", "Successfully reconstructed " + regions.length + " editable text frame(s).", regions.length, pageCharCount ); } progress.update(targetPages.length + 1, "Saving diagnostic report..."); progress.close(); // 7. Save Audit Report var folder = options.pdfFile.parent; var baseName = options.pdfFile.name.replace(/\.pdf$/i, ""); var reportFile = KeterLogger.saveReportFile(folder.fsName, baseName, doc.name, options.pdfFile.fsName); // 8. Display Completion Summary Dialog var summaryMsg = "Keter Conversion Completed!\n\n" + "• Total Pages Processed: " + targetPages.length + "\n" + "• Pages Converted to Editable Text: " + KeterLogger.stats.editablePagesCount + "\n" + "• Pages Preserved as 1:1 Visual Underlay: " + KeterLogger.stats.underlayOnlyPagesCount + "\n" + "• Text Frames Created: " + KeterLogger.stats.textFramesCreated + "\n" + "• Recovered Characters: " + KeterLogger.stats.charactersExtracted + "\n" + "• Overset Text Warnings: " + KeterLogger.stats.oversetFrames + "\n\n" + "Diagnostic report saved to:\n" + (reportFile ? reportFile.fsName : "Report generated in log."); alert(summaryMsg, KeterConfig.name); } // ------------------------------------------------------------------------ // ENTRY POINT: SINGLE UNDOABLE TRANSACTION // ------------------------------------------------------------------------ function main() { if (typeof app === "undefined" || app.documents === undefined) { alert("This script must be run directly inside Adobe InDesign.", "Keter Engine Error"); return; } var options = KeterUI.showMainDialog(); if (!options) return; app.doScript( function () { try { runConversion(options); } catch (err) { alert("Conversion encountered a critical error:\n" + err.message + "\nLine: " + (err.line || "unknown"), "Keter Error"); KeterLogger.error("CRITICAL RUNTIME ERROR: " + err.message + " (Line " + (err.line || "unknown") + ")"); } }, ScriptLanguage.JAVASCRIPT, [], UndoModes.ENTIRE_SCRIPT, "Keter PDF to InDesign Conversion" ); } main(); })();
  • 6 הצבעות
    50 פוסטים
    2k צפיות
    מ
    @המלאך הורדתי עכשיו את הגירסה האחרונה והיא דווקא עובדת מצויין, אבל אין בה הרבה אופציות והיא ממש מוצמצת, אני מאמין שכשתסיים אותה היא תהיה ממש טובה. אז אנחנו מחכים לזה.... Spoiler ובהצלחה בזמן חורף המעובר, הרבה שטייגען גם בלי מתמחים....
  • סקר | סקר - עימוד בAI מה דעתכם?

    171
    10 הצבעות
    171 פוסטים
    9k צפיות
    מ
    @אברהם-יצחק-יעקב 0 עגול, זה תחום נישתי, ולא ברור לי הטררם הגדול בפורום סביב זה, הבאזז כאן סביב זה עושה רושם כאילו כל בן אדם שני מוציא כמה ספרים בשנה, (מה שהגיוני הוא שיש כנראה בפורום אחוז גבוהה של מעמדים שזה מעניין אותם.. אבל זה לא מייצג את הכלל)
  • 0 הצבעות
    9 פוסטים
    200 צפיות
    ש
    @יצחק-פ כתב: תודה לכל המגיבים, רק רציתי לשאול אם זה כ"כ פשוט ליצור סקריפטים עם קלוד איך אין כאן לאף אחד מה'מתמחים' איזשהו סריפט חינמי ופשוט? כי לא ניתן לעלות את הקובץ סקריפט במהירות למתמחים כי הוא קולט את הקובץ כקישור לאתר זדוני
  • 0 הצבעות
    5 פוסטים
    110 צפיות
    ש
    @ישראל-מאיר-2 קח את הסקריפט הזה הוא יחסית טוב הוא מפרוג בחינם למרות שהשיטה שעבדה לי הכי טוב אפילו שהיא הכי ארוכה ויסודית זה השיטה של אביקט ששמים אותו אחרי המילה הרשאונה לחץ כאן להורדת הסקריפט מצורף כאן צדריך בערך איך ההגדרות צריכות להיראות מדריך ליצירת חלון .docx
  • בירור | פונטים לעיתון

    4
    0 הצבעות
    4 פוסטים
    129 צפיות
    דאנציגד
    @ישי-ישורון התכוונת ל @אברהם-יצחק-יעקב ...
  • שאלה | הבדלים - פונט קורן vs פונט גוטמן קרן

    2
    0 הצבעות
    2 פוסטים
    96 צפיות
    ד
    @מחנה-ידידים נשתברו קולמוסים רבים. הם באמת מאוד דומים, ודבר זה גרם לתביעות בבית המשפט.
  • שאלה | גודל נייר סטנדרטי לפרוספקט

    1
    0 הצבעות
    1 פוסטים
    55 צפיות
    אין תגובות
  • בקשת מידע | גופנים חינמיים לעימוד תורני

    19
    1 הצבעות
    19 פוסטים
    3k צפיות
    ה
    @sivan22 איפה זה כתוב שזה חינמי במה שהבאת?
  • בקשת מידע | איזה פונט פרנקריהל מומלץ לטקסט רץ בספר קריאה רגיל

    לא נפתר
    18
    0 הצבעות
    18 פוסטים
    629 צפיות
    ש
    @אריק-המלך גם לדעתי בחרת נכון, מסטרפונט מצויין לספרי קריאה, גם פרנקריל של 'הגילדה', לספרי קודש הכי מקובל להשתמש בפלרון
  • נושא זה נמחק!

    2
    0 הצבעות
    2 פוסטים
    47 צפיות
  • בקשת מידע | ללמוד אינדיזיין ???

    23
    0 הצבעות
    23 פוסטים
    497 צפיות
    A
    @מ-נ-ח-ם ללמוד את הספר לא יודע כמה רלוונטי, של איזה שנה הספר?, אל תשכח התוכנה עוברת הרבה שינויים מעדכון לעדכון, גם נוספו פיצ׳רים שונים שמייעלים את העבודה אולי זה טוב כדי ללמוד את הבסיס. Spoiler אם אתה רוצה יש לך את האתר הזה: https://indesign.tips/ וביוטיוב https://www.youtube.com/@indesigntips6798
  • 0 הצבעות
    25 פוסטים
    565 צפיות
    ד
    @123-דג-מלוח יישר כח!
  • נושא זה נמחק!

    1
    0 הצבעות
    1 פוסטים
    12 צפיות
    אין תגובות
  • בירור | באג שצג במיספור פיסקאות בוורד

    לא נפתר
    21
    0 הצבעות
    21 פוסטים
    610 צפיות
    G
    @ישיבישער הוא לא יכול כעת.... משהו יכול?
  • בעיה | קפיצת טורים בעקבות הערות שוליים גדולים

    53
    0 הצבעות
    53 פוסטים
    1k צפיות
    נ
    @ישי-ישורון כתב: @נפתלי-צבי-יהודה אולי המעמדים יגיעו למסקנה הפשוטה - אין כמו 'תג' [לעימוד תורני], שווה ללמוד את זה, ולחסוך הרבה כאבי ראש. מצאת בעיה ספיצפית מאוד שעדיין נשארה בעימוד בוורד...... אבל אל תשכח שלעמד בתג צריך לעבור קורס יקר וארוך של לימוד התוכנה, וגם לקנות תוכנה יקרה מאוד, שזה גם דורש הרבה זמן והכנה לפני, גם הרבה כסף, ועד כמה שצריך 'ללמוד' אז זה כבר לא מתאים לכל אחד, כמו שכל לימוד לא מתאים לכל אחד, בקיצור לא כל אחד יוכל לעמד בזה משא"כ בוורד שזה חינמי וללא קורס לימוד וללא עלות, לא צריך יכולות מיוחדות, רק הכרה בוורד שיש אותו לכל אחד בהישג יד ובחינם. ושכחתי..... גם צריך לדעת אנגלית בסיסית משא"כ בוורד.
  • בירור | לעשות שידרה בוורד עצמו

    10
    1
    0 הצבעות
    10 פוסטים
    241 צפיות
    מ
    @GOLDF כן לצד שמאל, כי כשמדפיסים את הכריכה זה פתוח לכל ארכו יחד עם הכריכה האחורית שזה בצד ימין. יש לך בלשונית תבנית צורה 3 האפשרויות הראשונות. [image: 1783601267463-738d0ab3-787b-4d85-a8de-db3d03e8d4c2-image.jpeg]
  • 3 הצבעות
    38 פוסטים
    1k צפיות
    ש
    @רפאל-vnkl כתב: תבצע חיפוש ברשת זה כשלעצמו צריך AI לא ?!
  • המלצה | סקירפט לבדיקת גופנים עם דו״ח על שולחן העבודה

    9
    2 הצבעות
    9 פוסטים
    230 צפיות
    ש
    קובץ לדוגמא לאחר הפעלת הסקירפט FontReport_כריכה פנימית .txt
  • שיתוף | מחשבון שידרה לרשות הציבור

    8
    0 הצבעות
    8 פוסטים
    272 צפיות
    ש
    היום באתי להשתמש בשל אילון וראיתי את השגיאה הזו (לא ניתן היה למצוא את כתובת ה-IP של השרת של calculator.ayalon-print.co.il) על המחשבון לכן יצרתי משהו בסיסי אמממ שראיתי שיש בית דפוס אחר שיצר את זה בשם דפוס צילום ראשון כאן [image: 1782162087328-%D7%A6%D7%99%D7%9C%D7%95%D7%9D-%D7%9E%D7%A1%D7%9A-2026-06-23-%D7%91-0.01.21-resized.png] וראיתי אחרי כותבי כל זה שכבר כמה פוסטים קודם @ישי-ישורון הביא אותו