I need to list detailed information about the fonts used in a set of inDesign documents. The information I need is essentially accessible through the menu item Type › Find Fonts… (as explained here) but going through each font in every document and writing down the information is not feasible.
I can find much of the information in the Font objects underdocument.fonts and my question is how to access or generate the extended properties found in the panel below:
Character count for the given font
Pages where the font occurs
Edit: The document.fonts array also doesn't seem to include missing fonts.
Well, here's a brute-force strategy for character counting. It iterates through every character textStyleRange in the document and checks its applied font. Edit: Updated to use textStyleRanges. Much faster than going through every character.
var document = app.open(new File(Folder.desktop.fsName + "/test/test.indd"));
try {
var fontMultiset = countCharsInFonts(document);
// For each font, display its character count.
var fonts = document.fonts.everyItem().getElements();
for (var i = 0; i < fonts.length; i++) {
var fontName = fonts[i].fullName;
$.writeln(fontName + ": " + fontMultiset[fontName]);
}
}
finally {
document.close();
}
function countCharsInFonts(document) {
// Create the font multiset.
var fontMultiset = {
add: function add(fontName, number) {
if (this.hasOwnProperty(fontName)) {
this[fontName] += number;
}
else {
this[fontName] = number;
}
},
};
// For every textStyleRange in the document, add its applied font to the multiset.
var stories = document.stories.everyItem().getElements();
for (var i = 0; i < stories.length; i++) {
var story = stories[i];
var textStyleRanges = story.textStyleRanges.everyItem().getElements();
for (var j = 0; j < textStyleRanges.length; j++) {
fontMultiset.add(textStyleRanges[j].appliedFont.fullName, textStyleRanges[j].length);
}
}
// For any fonts that aren't applied in the document, set the character count to 0.
var fonts = document.fonts.everyItem().getElements();
for (var i = 0; i < fonts.length; i++) {
var fontName = fonts[i].fullName;
if (!fontMultiset.hasOwnProperty(fontName)) {
fontMultiset[fontName] = 0;
}
}
return fontMultiset;
}
Related
How can I reduce the line spacing between "Section 1" and "Alert" using IText 7?
These are values stored in the table of database
<h3 style=color:#0000ff;><strong>Section 1</strong></h3>
<h4><strong>- Alert</strong></h4>
I have tried without success these links because don't changing the line spacing between "Section 1" and "Alert"
https://kb.itextpdf.com/home/it7kb/faq/how-to-change-the-line-spacing-of-text
How to adjust spacing between paragraphs in iText7
My code below
if (dt.Rows.Count > 0)
{
for (int i = 0; i < dt.Rows.Count; i++)
{
contents = new Paragraph(dt.Rows[i]["contents"].ToString())
.SetTextAlignment(TextAlignment.JUSTIFIED)
.SetFontSize(12)
.SetMultipliedLeading(0.0f);
List<IElement> lst = HtmlConverter.ConvertToElements(dt.Rows[i]["contents"].ToString()).ToList();
for (int j = 0; j < lst.Count; j++)
{
IBlockElement element = (IBlockElement)lst[j];
if (dt.Rows[i]["contents"].ToString().StartsWith("<h3 style=color:#0000ff;><strong>Section"))
{
contents.SetFontSize(12)
.SetBold()
.SetFontColor(ColorConstants.BLUE)
.SetMultipliedLeading(0.0f);
}
else if (dt.Rows[i]["contents"].ToString().StartsWith("<h4><strong>- "))
{
contents.SetFontSize(10)
.SetBold()
.SetFontColor(ColorConstants.BLACK)
.SetMultipliedLeading(0.0f);
}
else
{
contents.SetFontSize(10)
.SetFontColor(ColorConstants.BLACK)
.SetMultipliedLeading(0.0f);
}
document.Add(element);
}
}
dest = filename.ToString();
}
You're creating a Paragraph objects (called contents) from the HTML strings and applying properties to it, but not adding those objects to the document. You're also creating a List of elements by having HtmlConverter process the HTML strings. Those elements are added to the documents.
So it's expected that none of the properties that are set on contents are visible in the PDF document.
You can simply rely on HtmlConverter to process the CSS properties.
String[] htmls = {
"<h3 style=\"color:#0000ff;\"><strong>Section 1</strong></h3>",
"<h4><strong>- Alert</strong></h4>"
};
PdfWriter writer = new PdfWriter("SO66694693.pdf");
PdfDocument pdfDoc = new PdfDocument(writer);
Document document = new Document(pdfDoc);
for (int i = 0; i < htmls.Length; i++)
{
IList<IElement> lst = HtmlConverter.ConvertToElements(htmls[i]);
for (int j = 0; j < lst.Count; j++)
{
IBlockElement element = (IBlockElement)lst[j];
document.Add(element);
}
}
document.Close();
Output:
When adjusting the bottom margin on the first element and the top margin on the second element:
"<h3 style=\"color:#0000ff;margin-bottom: 0px;\"><strong>Section 1</strong></h3>",
"<h4 style=\"margin-top: 0px;\"><strong>- Alert</strong></h4>"
Output:
If you prefer to change the properties using SetMargin(), SetMarginBottom(), etc, instead of CSS properties, make sure you're doing that on the objects you're actually adding to the document.
I've written a pretty simple script that successfully takes information from one sheet in a Google Spreadsheet, and replaces information in a column in another sheet in the same spreadsheet pending satisfaction of two criteria: the receiving row has the same "Customer ID" and "Product Type." I say "simple" because it's intuitive, but extremely computationally demanding (taking nearly 30 seconds to run!).
From what I've read online, it's the sequential read and write operations that are causing the slowdown. I'm assuming that if I sort the sheets in question on the two criteria and THEN do a function that writes over subsequent rows, I may be able to speed it up. I'm a little weak on algorithms, so I'm still scratching my head on how to do this elegantly.
Does anyone have any suggestions? Below is my original script, and I've already made sure that the spreadsheet collapses empty rows, so time isn't wasted iterating over nothing.
function replaceRawWithRepChanges(receivedSheet) {
var ss = SpreadsheetApp.openById(receivedSheet);
var repchanges = ss.getSheetByName('repchanges');
var rawSheet = ss.getSheetByName('Sheet1');
var rawTMtoReplace = rawSheet.getRange('P2:P');
var repCustID = repchanges.getRange('A1:A').getValues();
var repTM = repchanges.getRange('F1:F').getValues();
var repCategory = repchanges.getRange('G1:G').getValues();
var rawCustID = rawSheet.getRange('A2:A').getValues();
var rawTM = rawSheet.getRange('P2:P').getValues();
var rawCategory = rawSheet.getRange('U2:U').getValues();
var repInfo = [repCustID, repTM, repCategory];
var rawInfo = [rawCustID, rawTM, rawCategory];
for (var i=0; i < rawInfo[0].length; i++) {
for (var j=0; j < repInfo[0].length; j++) {
// var thisRawCust = rawInfo[0][i];
// var thisRepCust = repInfo[0][j];
if (rawInfo[0][i].toString() == repInfo[0][j].toString()) {
// var thisRawCategory = rawInfo[2][i];
// var thisRepCategory = repInfo[2][j];
if (rawInfo[2][i].toString() == repInfo[2][j].toString()) {
// var repvalue = repInfo[1][j];
rawInfo[1][i] = repInfo[1][j];
// var newRawValue = rawInfo[1][i];
}
}
}
}
return rawInfo[1];
}
Yes, you should sort the data (perhaps using the SORT command, which does work with multiple columns). Then, using two pointers, you only have to go down the columns once, rather than checking the entirety of repInfo for matches for every single row in rawInfo.
Once you've sorted the information, your loop might look like the following:
var i = 0;
var j = 0;
while (i < rawInfo[0].length && j < repInfo[0].length) {
if (rawInfo[0][i].toString() == repInfo[0][j].toString()) {
if (rawInfo[2][i].toString() == repInfo[2][j].toString()) {
rawInfo[1][i]=repInfo[1][j];
i++;
j++;
} else if (rawInfo[2][i].toString() < repInfo[2][j].toString()) {
i++;
} else {
j++;
}
} else if (rawInfo[0][i].toString() < repInfo[0][j].toString()) {
i++;
} else {
j++;
}
}
Let's say, I have following template.
Hello, {I'm|he is} a {notable|famous} person.
Result should be
Hello, I'm a notable person.
Hello, I'm a famous person.
Hello, he is a notable person.
Hello, he is a famous person.
The only possible solution I have in mind - full search, but it is not effective.
May be there is a good algorithm for such kind of job but I do not know what task about. All permutations in array is very close to this but I have no idea how to use it here.
Here is working solution (it's part of object, so here is only relevant part).
generateText() parses string and converts 'Hello, {1|2}, here {3,4}' into ['Hello', ['1', '2'], 'here', ['3', '4']]]
extractText() takes this multidimensional array and creates all possible strings
STATE_TEXT: 'TEXT',
STATE_INSIDE_BRACKETS: 'INSIDE_BRACKETS',
generateText: function(text) {
var result = [];
var state = this.STATE_TEXT;
var length = text.length;
var simpleText = '';
var options = [];
var singleOption = '';
var i = 0;
while (i < length) {
var symbol = text[i];
switch(symbol) {
case '{':
if (state === this.STATE_TEXT) {
simpleText = simpleText.trim();
if (simpleText.length) {
result.push(simpleText);
simpleText = '';
}
state = this.STATE_INSIDE_BRACKETS;
}
break;
case '}':
if (state === this.STATE_INSIDE_BRACKETS) {
singleOption = singleOption.trim();
if (singleOption.length) {
options.push(singleOption);
singleOption = '';
}
if (options.length) {
result.push(options);
options = [];
}
state = this.STATE_TEXT;
}
break;
case '|':
if (state === this.STATE_INSIDE_BRACKETS) {
singleOption = singleOption.trim();
if (singleOption.length) {
options.push(singleOption);
singleOption = '';
}
}
break;
default:
if (state === this.STATE_TEXT) {
simpleText += symbol;
} else if (state === this.STATE_INSIDE_BRACKETS) {
singleOption += symbol;
}
break;
}
i++;
}
return result;
},
extractStrings(generated) {
var lengths = {};
var currents = {};
var permutations = 0;
var length = generated.length;
for (var i = 0; i < length; i++) {
if ($.isArray(generated[i])) {
lengths[i] = generated[i].length;
currents[i] = lengths[i];
permutations += lengths[i];
}
}
var strings = [];
for (var i = 0; i < permutations; i++) {
var string = [];
for (var k = 0; k < length; k++) {
if (typeof lengths[k] === 'undefined') {
string.push(generated[k]);
continue;
}
currents[k] -= 1;
if (currents[k] < 0) {
currents[k] = lengths[k] - 1;
}
string.push(generated[k][currents[k]]);
}
strings.push(string.join(' '));
}
return strings;
},
The only possible solution I have in mind - full search, but it is not effective.
If you must provide full results, you must run full search. There is simply no way around it. You don't need all permutations, though: the number of results is equal to the product of the number of alternatives in each template.
Although there are multiple ways to implement this, recursion is among the most popular approaches. Here is some pseudo-code to get you started:
string[][] templates = {{"I'm", "he is"}, {"notable", "famous", "boring"}}
int[] pos = new int[templates.Length]
string[] fills = new string[templates.Length]
recurse(templates, fills, 0)
...
void recurse(string[][] templates, string[] fills, int pos) {
if (pos == fills.Length) {
formatResult(fills);
} else {
foreach option in templates[pos] {
fills[pos] = option
recurse(templates, fills, pos+1);
}
}
}
It seems like the best solution here is going to be n*m where n=the first array and m= the second array . There are nm required lines of output, which means that as long as you are only doing nm you aren't doing any extra work
The generic running time for this is where there is more than 2 arrays with options, it would be
n1*n2...*nm where each of those is equal to the size of the respective list
A nested loop where you just print out the value for the current index of the outer loop along with the current value for the index of the inner loop should do this properly
i'm using websupergoos abcpdf to convert html pages to pdf via addimageurl.
Works great, but the resulting pdf does not allow the user to select text and copy. All is one 'image'.
Is it possible to do this? Which are the settings to use?
This is my current code. The commented "flatten" does not seem to do anything relevant. The HttpStream simply forewards the pdf to users as a doc.
var doc = new Doc();
doc.HtmlOptions.UseScript = true;
doc.Units = "mm";
doc.MediaBox.String = "0 0 210 297";
doc.Rect.String = doc.MediaBox.String;
doc.Rect.Inset(10.0, 10.0);
doc.SetInfo(0, "License", abcpdfkey);
doc.HtmlOptions.UseScript = true;
doc.HtmlOptions.AddMovies = true;
doc.HtmlOptions.RetryCount = 0;
doc.HtmlOptions.ContentCount = 1;
doc.Page = doc.AddPage();
for (int i = doc.AddImageUrl(url); doc.Chainable(i); i = doc.AddImageToChain(i))
{
doc.Page = doc.AddPage();
}
int pageCount = doc.PageCount;
for (int j = 1; j <= pageCount; j++)
{
doc.PageNumber = j;
// doc.Flatten();
}
this.HttpStream(doc.GetData(), filename);
Before sending the PDF to the HTTP stream, you can set the encryption properties
The CanCopy Property sets if the user can copy text from the PDF
To set it add the following code:
doc.Encryption.CanCopy = true;
You may need to set doc.Encryption.CanExtract as well
I have an editable <iframe> with the some HTML code in it. I need get all <a> tags in my range. I tried this code but it doesn't work:
var select = document.getElementById(iframe_id).contentWindow.getSelection();
var range = select.getRangeAt(0);
//HERE I WANT TO FIND ALL TAGS IN THIS RANGE AND IF IT "A" - ADD NEW ATTRIBUTE "CLASS". SOMETHING LIKE THIS
var parent = rng.commonAncestorContainer;
for(var i=0; i<parent.childNodes.length; i++)
{
if(parent.childNodes[i].tagName.toLowerCase() == "a")
parent.childNodes[i].setAttribute("class", "href_class");
}
You can use getElementsByTagName() to get all <a> tags of the range container and then check for each of them whether it actually belongs to the range using range.compareBoundaryPoints() (only parts of the container might be selected). Something like this:
var links = rng.commonAncestorContainer.getElementsByTagName("a");
for (var i = 0; i < links.length; i++)
{
var linkRange = document.createRange();
linkRange.selectNode(links[i]);
if (rng.compareBoundaryPoints(Range.START_TO_START, linkRange) <= 0 && rng.compareBoundaryPoints(Range.END_TO_END, linkRange) >= 0)
{
links[i].className = "href_class";
}
}
This should get you started in the right direction. This code does not do any null reference checks on the iframe, selection, range or list.
function addAnchorClass(targetFrameId) {
var targetIframe = document.getElementById(targetFrameId).contentWindow;
var selection = targetIframe.getSelection();
var range = selection.getRangeAt(0);
var alist = range.commonAncestorContainer.getElementsByTagName("a");
for (var i=0, item; item = alist[i]; i++) {
if (selection.containsNode(item, true) ) {
item.className += "PUT YOUR CSS CLASS NAME HERE";
}
}
}